Processing math: 100%

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) am.

A non-unique CSA is a maximal Abelian subalgebra in the horizontal subspace 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.

Parameters
  • g (List[Union[PauliSentence, np.ndarray]]) – Lie algebra g, which is assumed to be ordered as g=km

  • k (List[Union[PauliSentence, np.ndarray]]) – Vertical space k from Cartan decomposition g=km

  • m (List[Union[PauliSentence, np.ndarray]]) – Horizontal space m from Cartan decomposition g=km

  • ad (Array) – The |g|×|g|×|g| dimensional adjoint representation of 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 or PauliSentence). If True, returns the spaces as adjoint representation vectors.

  • is_orthogonal (bool) – Whether the basis elements are all orthogonal, both within and between g, k and m.

Returns

A tuple of adjoint vector representations (newg, k, mtilde, a, new_adj), corresponding to g, k, ˜m, 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 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 |g|, where each component corresponds to an entry in (the ordered) g. The adjoint vectors for the Cartan subalgebra are in np_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 ˆOv=ivigi for an adjoint vector v and gig.

>>> 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)]

Let us walk through an example of computing the Cartan subalgebra. The basis for computing the Cartan subalgebra is having the Lie algebra g, a Cartan decomposition g=km and its adjoint representation.

We start by computing these ingredients using cartan_decomp() and structure_constants(). As an example, we take the Lie algebra of the Heisenberg model with generators {XiXi+1,YiYi+1,ZiZi+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 k and m need to fulfill the commutation relations [k,k]k, [k,m]m and [m,m]k, which we can check using the helper function check_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 in k first, and then all remaining operators from m.

>>> 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 a, a maximal Abelian subalgebra of m.

>>> newg, k, mtilde, a, new_adj = cartan_subalgebra(g, k, m, adj, start_idx=3)

The new DLA newg is just the concatenation of k, mtilde, a. Each component is returned in the original input format. Here we obtain collections of 8×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 |g|, in which each entry corresponds to the respective operator in g. Given an adjoint representation vector v, we can reconstruct the respective operator by simply computing ˆOv=ivigi, where gig (hence the need for a canonical ordering).

We provide a convenience function adjvec_to_op() that works with both g represented as dense matrices or PL operators. Because we used dense matrices in this example, we transform the operators back to PennyLane operators using pauli_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 of g.