qml.labs.dla.cartan_subalgebra¶
- cartan_subalgebra(g, k, m, ad, start_idx=0, tol=1e-10, verbose=0, return_adjvec=False, is_orthogonal=True)[source]¶
Compute a Cartan subalgebra (CSA) \(\mathfrak{a} \subseteq \mathfrak{m}\).
A non-unique CSA is a maximal Abelian subalgebra in the horizontal subspace \(\mathfrak{m}\) of a Cartan decomposition. Note that this is sometimes called a horizontal CSA, and is different from the definition of a CSA on Wikipedia.
See also
cartan_decomp()
,structure_constants()
, The KAK decomposition theory(demo), The KAK decomposition in practice (demo).- Parameters
g (List[Union[PauliSentence, np.ndarray]]) – Lie algebra \(\mathfrak{g}\), which is assumed to be ordered as \(\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}\)
k (List[Union[PauliSentence, np.ndarray]]) – Vertical space \(\mathfrak{k}\) from Cartan decomposition \(\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}\)
m (List[Union[PauliSentence, np.ndarray]]) – Horizontal space \(\mathfrak{m}\) from Cartan decomposition \(\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}\)
ad (Array) – The \(|\mathfrak{g}| \times |\mathfrak{g}| \times |\mathfrak{g}|\) dimensional adjoint representation of \(\mathfrak{g}\) (see
structure_constants()
)start_idx (bool) – Indicates from which element in
m
the CSA computation starts.tol (float) – Numerical tolerance for linear independence check
verbose (bool) – Whether or not to output progress during computation
return_adjvec (bool) – The output format. If
False
, returns operators in their original input format (matrices orPauliSentence
). IfTrue
, returns the spaces as adjoint representation vectors.is_orthogonal (bool) – Whether the basis elements are all orthogonal, both within and between
g
,k
andm
.
- Returns
A tuple of adjoint vector representations
(newg, k, mtilde, a, new_adj)
, corresponding to \(\mathfrak{g}\), \(\mathfrak{k}\), \(\tilde{\mathfrak{m}}\), \(\mathfrak{a}\) and the new adjoint representation. The dimensions are(|g|, |g|)
,(|k|, |g|)
,(|mtilde|, |g|)
,(|a|, |g|)
and(|g|, |g|, |g|)
, respectively.- Return type
Tuple(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray)
Example
A quick example computing a Cartan subalgebra of \(\mathfrak{su}(4)\) using the Cartan involution
even_odd_involution()
.>>> import pennylane as qml >>> from pennylane.labs.dla import cartan_decomp, cartan_subalgebra, even_odd_involution >>> g = list(qml.pauli.pauli_group(2)) # u(4) >>> g = g[1:] # remove identity -> su(4) >>> g = [op.pauli_rep for op in g] # optional; turn to PauliSentence for convenience >>> k, m = cartan_decomp(g, even_odd_involution) >>> g = k + m # re-order g to separate k and m >>> adj = qml.structure_constants(g) >>> newg, k, mtilde, a, new_adj = cartan_subalgebra(g, k, m, adj) >>> newg == k + mtilde + a True >>> a [-1.0 * Z(0) @ Z(1), 1.0 * Y(0) @ Y(1), -1.0 * X(0) @ X(1)]
We can confirm that these all commute with each other, as the CSA is Abelian (= all operators commute).
>>> from pennylane.labs.dla import check_all_commuting >>> check_all_commuting(a) True
We can opt-in to return what we call adjoint vectors of dimension \(|\mathfrak{g}|\), where each component corresponds to an entry in (the ordered)
g
. The adjoint vectors for the Cartan subalgebra are innp_a
.>>> np_newg, np_k, np_mtilde, np_a, new_adj = cartan_subalgebra(g, k, m, adj, return_adjvec=True)
We can reconstruct an operator by computing \(\hat{O}_v = \sum_i v_i g_i\) for an adjoint vector \(v\) and \(g_i \in \mathfrak{g}\).
>>> v = np_a[0] >>> op = sum(v_i * g_i for v_i, g_i in zip(v, g)) >>> op.simplify() >>> op -1.0 * Z(0) @ Z(1)
For convenience, we provide a helper function
adjvec_to_op()
for the collections of adjoint vectors in the returns.>>> from pennylane.labs.dla import adjvec_to_op >>> a = adjvec_to_op(np_a, g) >>> a [-1.0 * Z(0) @ Z(1), 1.0 * Y(0) @ Y(1), -1.0 * X(0) @ X(1)]
Usage Details
Let us walk through an example of computing the Cartan subalgebra. The basis for computing the Cartan subalgebra is having the Lie algebra \(\mathfrak{g}\), a Cartan decomposition \(\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}\) and its adjoint representation.
We start by computing these ingredients using
cartan_decomp()
andstructure_constants()
. As an example, we take the Lie algebra of the Heisenberg model with generators \(\{X_i X_{i+1}, Y_i Y_{i+1}, Z_i Z_{i+1}\}\).>>> from pennylane.labs.dla import lie_closure_dense, cartan_decomp >>> from pennylane import X, Y, Z >>> n = 3 >>> gens = [X(i) @ X(i+1) for i in range(n-1)] >>> gens += [Y(i) @ Y(i+1) for i in range(n-1)] >>> gens += [Z(i) @ Z(i+1) for i in range(n-1)] >>> g = lie_closure_dense(gens)
Taking the Heisenberg Lie algebra, we can perform the Cartan decomposition. We take the
even_odd_involution()
as a valid Cartan involution. The resulting vertical and horizontal subspaces \(\mathfrak{k}\) and \(\mathfrak{m}\) need to fulfill the commutation relations \([\mathfrak{k}, \mathfrak{k}] \subseteq \mathfrak{k}\), \([\mathfrak{k}, \mathfrak{m}] \subseteq \mathfrak{m}\) and \([\mathfrak{m}, \mathfrak{m}] \subseteq \mathfrak{k}\), which we can check using the helper functioncheck_cartan_decomp()
.>>> from pennylane.labs.dla import even_odd_involution, check_cartan_decomp >>> k, m = cartan_decomp(g, even_odd_involution) >>> check_cartan_decomp(k, m) # check commutation relations of Cartan decomposition True
Our life is easier when we use a canonical ordering of the operators. This is why we re-define
g
with the new ordering in terms of operators ink
first, and then all remaining operators fromm
.>>> import numpy as np >>> from pennylane.labs.dla import structure_constants_dense >>> g = np.vstack([k, m]) # re-order g to separate k and m operators >>> adj = structure_constants_dense(g) # compute adjoint representation of g
Finally, we can compute a Cartan subalgebra \(\mathfrak{a}\), a maximal Abelian subalgebra of \(\mathfrak{m}\).
>>> newg, k, mtilde, a, new_adj = cartan_subalgebra(g, k, m, adj, start_idx=3)
The new DLA
newg
is just the concatenation ofk
,mtilde
,a
. Each component is returned in the original input format. Here we obtain collections of \(8\times 8\) matrices (numpy
arrays), as this is what we started from.>>> newg.shape, k.shape, mtilde.shape, a.shape, new_adj.shape ((15, 8, 8), (6, 8, 8), (6, 8, 8), (3, 8, 8), (15, 15, 15))
We can also let the function return what we call adjoint representation vectors.
>>> kwargs = {"start_idx": 3, "return_adjvec": True} >>> np_newg, np_k, np_mtilde, np_a, new_adj = cartan_subalgebra(g, k, m, adj, **kwargs) >>> np_newg.shape, np_k.shape, np_mtilde.shape, np_a.shape, new_adj.shape ((15, 15), (6, 15), (6, 15), (3, 15), (15, 15, 15))
These are dense vector representations of dimension \(|\mathfrak{g}|\), in which each entry corresponds to the respective operator in \(\mathfrak{g}\). Given an adjoint representation vector \(v\), we can reconstruct the respective operator by simply computing \(\hat{O}_v = \sum_i v_i g_i\), where \(g_i \in \mathfrak{g}\) (hence the need for a canonical ordering).
We provide a convenience function
adjvec_to_op()
that works with bothg
represented as dense matrices or PL operators. Because we used dense matrices in this example, we transform the operators back to PennyLane operators usingpauli_decompose()
.>>> from pennylane.labs.dla import adjvec_to_op >>> a = adjvec_to_op(np_a, g) >>> h_op = [qml.pauli_decompose(op).pauli_rep for op in a] >>> h_op [-1.0 * Y(1) @ Y(2), -1.0 * Z(1) @ Z(2), 1.0 * X(1) @ X(2)]
In that case we chose a Cartan subalgebra from which we can readily see that it is commuting, but we also provide a small helper function to check that.
>>> from pennylane.labs.dla import check_all_commuting >>> assert check_all_commuting(h_op)
Last but not least, the adjoint representation
new_adj
is updated to represent the new basis and its ordering ofg
.