From 511180ec5ea50cfe7d9073222a8e372a889f4728 Mon Sep 17 00:00:00 2001 From: ringo-but-quantum Date: Fri, 3 Jan 2025 09:51:50 +0000 Subject: [PATCH 1/2] [no ci] bump nightly version --- pennylane/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/_version.py b/pennylane/_version.py index 2dd29caa0f3..dea49e7e73d 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.40.0-dev49" +__version__ = "0.40.0-dev50" From 447f8501c9ea5a5663d1785151d33da7821672ae Mon Sep 17 00:00:00 2001 From: Korbinian Kottmann <43949391+Qottmann@users.noreply.github.com> Date: Fri, 3 Jan 2025 18:03:08 +0100 Subject: [PATCH 2/2] [docs] Fixing minor docs issues in pennylane.labs.dla module (#6747) Fixing - [x] links to demos - [x] latex in `variational_kak` - [x] consistently refer to horizontal cartan subalgebra as $\mathfrak{a}$ instead of $\mathfrak{h}$ - [x] module description --------- Co-authored-by: Pietropaolo Frisoni --- doc/releases/changelog-dev.md | 6 +- pennylane/labs/dla/__init__.py | 4 +- pennylane/labs/dla/cartan_subalgebra.py | 92 +++++++++---------- .../labs/dla/structure_constants_dense.py | 2 +- pennylane/labs/dla/variational_kak.py | 16 ++-- pennylane/pauli/dla/lie_closure.py | 4 +- 6 files changed, 66 insertions(+), 58 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 3680c3dd6ea..75c1ecd9977 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -454,7 +454,7 @@ such as `shots`, `rng` and `prng_key`. symbolic operation classes. [(#6592)](https://github.com/PennyLaneAI/pennylane/pull/6592) -

Functionality for handling dynamical Lie algebras (DLAs)

+

Experimental functionality for handling dynamical Lie algebras (DLAs)

* Added a dense implementation of computing the Lie closure in a new function `lie_closure_dense` in `pennylane.labs.dla`. @@ -484,6 +484,10 @@ such as `shots`, `rng` and `prng_key`. decomposition and the ordered adjoint representation of the Lie algebra. [(#6446)](https://github.com/PennyLaneAI/pennylane/pull/6446) +* Improved documentation by fixing broken links and latex issues. Also consistently use `$\mathfrak{a}$` + for the horizontal Cartan subalgebra instead of `$\mathfrak{h}$`. + [(#6747)](https://github.com/PennyLaneAI/pennylane/pull/6747) +

Breaking changes 💔

* The default graph coloring method of `qml.dot`, `qml.sum`, and `qml.pauli.optimize_measurements` for grouping observables was changed diff --git a/pennylane/labs/dla/__init__.py b/pennylane/labs/dla/__init__.py index f1df7442e84..7a01f41abfb 100644 --- a/pennylane/labs/dla/__init__.py +++ b/pennylane/labs/dla/__init__.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Experimental Lie theory features -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Experimental dynamical Lie algebra (DLA) functionality +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: pennylane.labs.dla diff --git a/pennylane/labs/dla/cartan_subalgebra.py b/pennylane/labs/dla/cartan_subalgebra.py index cc97dceb0f7..abd6b1e67ee 100644 --- a/pennylane/labs/dla/cartan_subalgebra.py +++ b/pennylane/labs/dla/cartan_subalgebra.py @@ -37,21 +37,21 @@ def _is_independent(v, A, tol=1e-14): return np.linalg.norm(v) > tol -def _orthogonal_complement_basis(h, m, tol): - """find mtilde = m - h""" - # Step 1: Find the span of h - h = np.array(h) +def _orthogonal_complement_basis(a, m, tol): + """find mtilde = m - a""" + # Step 1: Find the span of a + a = np.array(a) m = np.array(m) - # Compute the orthonormal basis of h using QR decomposition + # Compute the orthonormal basis of a using QR decomposition - Q = _gram_schmidt(h) + Q = _gram_schmidt(a) - # Step 2: Project each vector in m onto the orthogonal complement of span(h) + # Step 2: Project each vector in m onto the orthogonal complement of span(a) projections = m - np.dot(np.dot(m, Q.T), Q) assert np.allclose( - np.tensordot(h, projections, axes=[[1], [1]]), 0.0 - ), f"{np.tensordot(h, projections, axes=[[1], [1]])}" + np.tensordot(a, projections, axes=[[1], [1]]), 0.0 + ), f"{np.tensordot(a, projections, axes=[[1], [1]])}" # Step 3: Find a basis for the non-zero projections # We'll use SVD to find the basis @@ -61,8 +61,8 @@ def _orthogonal_complement_basis(h, m, tol): rank = np.sum(S > tol) basis = U[:, :rank] assert np.allclose( - np.tensordot(h, basis, axes=[[1], [0]]), 0.0 - ), f"{np.tensordot(h, basis, axes=[[1], [0]])}" + np.tensordot(a, basis, axes=[[1], [0]]), 0.0 + ), f"{np.tensordot(a, basis, axes=[[1], [0]])}" return basis.T # Transpose to get row vectors @@ -71,7 +71,7 @@ def cartan_subalgebra( g, k, m, ad, start_idx=0, tol=1e-10, verbose=0, return_adjvec=False, is_orthogonal=True ): r""" - Compute a Cartan subalgebra (CSA) :math:`\mathfrak{h} \subseteq \mathfrak{m}`. + Compute a Cartan subalgebra (CSA) :math:`\mathfrak{a} \subseteq \mathfrak{m}`. A non-unique CSA is a maximal Abelian subalgebra in the horizontal subspace :math:`\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 `__. @@ -93,9 +93,9 @@ def cartan_subalgebra( Returns: Tuple(np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray): A tuple of adjoint vector representations - ``(newg, k, mtilde, h, new_adj)``, corresponding to - :math:`\mathfrak{g}`, :math:`\mathfrak{k}`, :math:`\tilde{\mathfrak{m}}`, :math:`\mathfrak{h}` and the new adjoint representation. - The dimensions are ``(|g|, |g|)``, ``(|k|, |g|)``, ``(|mtilde|, |g|)``, ``(|h|, |g|)`` and ``(|g|, |g|, |g|)``, respectively. + ``(newg, k, mtilde, a, new_adj)``, corresponding to + :math:`\mathfrak{g}`, :math:`\mathfrak{k}`, :math:`\tilde{\mathfrak{m}}`, :math:`\mathfrak{a}` and the new adjoint representation. + The dimensions are ``(|g|, |g|)``, ``(|k|, |g|)``, ``(|mtilde|, |g|)``, ``(|a|, |g|)`` and ``(|g|, |g|, |g|)``, respectively. **Example** @@ -109,26 +109,26 @@ def cartan_subalgebra( >>> 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, h, new_adj = cartan_subalgebra(g, k, m, adj) - >>> newg == k + mtilde + h + >>> newg, k, mtilde, a, new_adj = cartan_subalgebra(g, k, m, adj) + >>> newg == k + mtilde + a True - >>> h - [-1.0 * Z(0) @ Z(1), -1.0 * Y(0) @ Y(1), 1.0 * X(0) @ X(1)] + >>> 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(h) + >>> check_all_commuting(a) True We can opt-in to return what we call adjoint vectors of dimension :math:`|\mathfrak{g}|`, where each component corresponds to an entry in (the ordered) ``g``. - The adjoint vectors for the Cartan subalgebra are in ``np_h``. + The adjoint vectors for the Cartan subalgebra are in ``np_a``. - >>> np_newg, np_k, np_mtilde, np_h, new_adj = cartan_subalgebra(g, k, m, adj, return_adjvec=True) + >>> 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 :math:`\hat{O}_v = \sum_i v_i g_i` for an adjoint vector :math:`v` and :math:`g_i \in \mathfrak{g}`. - >>> v = np_h[0] + >>> v = np_a[0] >>> op = sum(v_i * g_i for v_i, g_i in zip(v, g)) >>> op.simplify() >>> op @@ -137,9 +137,9 @@ def cartan_subalgebra( For convenience, we provide a helper function :func:`~adjvec_to_op` for the collections of adjoint vectors in the returns. >>> from pennylane.labs.dla import adjvec_to_op - >>> h = adjvec_to_op(np_h, g) - >>> h - [-1.0 * Z(0) @ Z(1), -1.0 * Y(0) @ Y(1), 1.0 * X(0) @ X(1)] + >>> 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)] .. details:: :title: Usage Details @@ -177,21 +177,21 @@ def cartan_subalgebra( >>> 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 :math:`\mathfrak{h}`, a maximal Abelian subalgebra of :math:`\mathfrak{m}`. + Finally, we can compute a Cartan subalgebra :math:`\mathfrak{a}`, a maximal Abelian subalgebra of :math:`\mathfrak{m}`. - >>> newg, k, mtilde, h, new_adj = cartan_subalgebra(g, k, m, adj, start_idx=3) + >>> 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``, ``h``. Each component is returned in the original input format. + 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 :math:`8\times 8` matrices (``numpy`` arrays), as this is what we started from. - >>> newg.shape, k.shape, mtilde.shape, h.shape, new_adj.shape + >>> 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_h, new_adj = cartan_subalgebra(g, k, m, adj, **kwargs) - >>> np_newg.shape, np_k.shape, np_mtilde.shape, np_h.shape, new_adj.shape + >>> 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 :math:`|\mathfrak{g}|`, in which each entry corresponds to the respective operator in :math:`\mathfrak{g}`. @@ -202,8 +202,8 @@ def cartan_subalgebra( Because we used dense matrices in this example, we transform the operators back to PennyLane operators using :func:`~pauli_decompose`. >>> from pennylane.labs.dla import adjvec_to_op - >>> h = adjvec_to_op(np_h, g) - >>> h_op = [qml.pauli_decompose(op).pauli_rep for op in h] + >>> 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)] @@ -217,14 +217,14 @@ def cartan_subalgebra( g_copy = copy.deepcopy(g) np_m = op_to_adjvec(m, g, is_orthogonal=is_orthogonal) - np_h = op_to_adjvec([m[start_idx]], g, is_orthogonal=is_orthogonal) + np_a = op_to_adjvec([m[start_idx]], g, is_orthogonal=is_orthogonal) iteration = 1 while True: if verbose: - print(f"iteration {iteration}: Found {len(np_h)} independent Abelian operators.") + print(f"iteration {iteration}: Found {len(np_a)} independent Abelian operators.") kernel_intersection = np_m - for h_i in np_h: + for h_i in np_a: # obtain adjoint rep of candidate h_i adjoint_of_h_i = np.tensordot(ad, h_i, axes=[[1], [0]]) @@ -236,8 +236,8 @@ def cartan_subalgebra( kernel_intersection = _gram_schmidt(kernel_intersection) # orthogonalize for vec in kernel_intersection: - if _is_independent(vec, np.array(np_h).T, tol): - np_h = np.vstack([np_h, vec]) + if _is_independent(vec, np.array(np_a).T, tol): + np_a = np.vstack([np_a, vec]) break else: # No new vector was added from all the kernels @@ -245,15 +245,15 @@ def cartan_subalgebra( iteration += 1 - np_h = _gram_schmidt(np_h) # orthogonalize Abelian subalgebra + np_a = _gram_schmidt(np_a) # orthogonalize Abelian subalgebra np_k = op_to_adjvec( k, g, is_orthogonal=is_orthogonal ) # adjoint vectors of k space for re-ordering np_oldg = np.vstack([np_k, np_m]) np_k = _gram_schmidt(np_k) - np_mtilde = _orthogonal_complement_basis(np_h, np_m, tol=tol) # the "rest" of m without h - np_newg = np.vstack([np_k, np_mtilde, np_h]) + np_mtilde = _orthogonal_complement_basis(np_a, np_m, tol=tol) # the "rest" of m without a + np_newg = np.vstack([np_k, np_mtilde, np_a]) # Instead of recomputing the adjoint representation, take the basis transformation # oldg -> newg and transform the adjoint representation accordingly @@ -261,11 +261,11 @@ def cartan_subalgebra( new_adj = change_basis_ad_rep(ad, basis_change) if return_adjvec: - return np_newg, np_k, np_mtilde, np_h, new_adj + return np_newg, np_k, np_mtilde, np_a, new_adj - newg, k, mtilde, h = [ + newg, k, mtilde, a = [ adjvec_to_op(adjvec, g_copy, is_orthogonal=is_orthogonal) - for adjvec in [np_newg, np_k, np_mtilde, np_h] + for adjvec in [np_newg, np_k, np_mtilde, np_a] ] - return newg, k, mtilde, h, new_adj + return newg, k, mtilde, a, new_adj diff --git a/pennylane/labs/dla/structure_constants_dense.py b/pennylane/labs/dla/structure_constants_dense.py index 30d21b86240..c0803968464 100644 --- a/pennylane/labs/dla/structure_constants_dense.py +++ b/pennylane/labs/dla/structure_constants_dense.py @@ -32,7 +32,7 @@ def structure_constants_dense(g: TensorLike, is_orthonormal: bool = True) -> Ten .. seealso:: For details on the mathematical definitions, see :func:`~structure_constants` and the section "Lie algebra basics" in our `g-sim demo `__. Args: - g (np.array): The (dynamical) Lie algebra provided as dense matrices, as generated from :func:`pennylane.labs.lie.lie_closure_dense`. + g (np.array): The (dynamical) Lie algebra provided as dense matrices, as generated from :func:`~lie_closure_dense`. ``g`` should have shape ``(d, 2**n, 2**n)`` where ``d`` is the dimension of the algebra and ``n`` is the number of qubits. Each matrix ``g[i]`` should be Hermitian. is_orthonormal (bool): Whether or not the matrices in ``g`` are orthonormal with respect to the Hilbert-Schmidt inner product on (skew-)Hermitian matrices. If the inputs are orthonormal, it is recommended to set ``is_orthonormal`` to ``True`` to reduce diff --git a/pennylane/labs/dla/variational_kak.py b/pennylane/labs/dla/variational_kak.py index 2efa3e0c8ac..04d950f1d21 100644 --- a/pennylane/labs/dla/variational_kak.py +++ b/pennylane/labs/dla/variational_kak.py @@ -41,20 +41,22 @@ def variational_kak_adj(H, g, dims, adj, verbose=False, opt_kwargs=None, pick_mi r""" Variational KaK decomposition of Hermitian ``H`` using the adjoint representation. - Given a Cartan decomposition (:func:`~cartan_decomp`) :math:`\mathfrak{g} = \mathfrak{k} \oplus (\tilde{\mathfrak{m}} \oplus \mathfrak{a})` - and a Hermitian operator :math:`H \in \tilde{\mathfrak{m}} \oplus \mathfrak{a}`, this function computes + Given a Cartan decomposition (:func:`~cartan_decomp`) :math:`\mathfrak{g} = \mathfrak{k} \oplus \mathfrak{m}`, + a Hermitian operator :math:`H \in \mathfrak{m}`, + and a horizontal Cartan subalgebra (:func:`~cartan_subalgebra`) :math:`\mathfrak{a} \subset \mathfrak{m}`, + this function computes :math:`a \in \mathfrak{a}` and :math:`K_c \in e^{i\mathfrak{k}}` such that .. math:: H = K_c a K_c^\dagger. - :math:`\mathfrak{a}` is a maximal Abelian subalgebra of :math:`\mathfrak{g}` (i.e., a :func:`~cartan_subalgebra`). In particular, :math:`a = \sum_j c_j a_j` is decomposed in terms of commuting operators :math:`a_j \in \mathfrak{a}`. This allows for the immediate decomposition - .. math:: e^{-i t H} = K_c e^{-i t a} K_c^\dagger = K_c \prod_j \left[ e^{-i t c_j a_j} \left] K_c^\dagger. + .. math:: e^{-i t H} = K_c e^{-i t a} K_c^\dagger = K_c \left(\prod_j e^{-i t c_j a_j} \right) K_c^\dagger. The result is provided in terms of the adjoint vector representation of :math:`a \in \mathfrak{a}` - (see :func:`adjvec_to_op`), i.e. the ordered coefficients :math:`c_j` in :math:`a = \sum_j c_j m_j` with the basis elements :math:`m_j \in (\tilde{\mathfrak{m}} \oplus \mathfrak{a})` and + (see :func:`adjvec_to_op`), i.e. the ordered coefficients :math:`c_j` in :math:`a = \sum_j c_j m_j` + with the basis elements :math:`m_j \in (\tilde{\mathfrak{m}} \oplus \mathfrak{a})` and the optimal parameters :math:`\theta` such that .. math:: K_c = \prod_{j=|\mathfrak{k}|}^{1} e^{-i \theta_j k_j} @@ -76,11 +78,11 @@ def Kc(theta_opt: Iterable[float], k: Iterable[Operator]): .. math:: f(\theta) = \langle H, K(\theta) e^{-i \sum_{j=1}^{|\mathfrak{a}|} \pi^j a_j} K(\theta)^\dagger \rangle, - see eq. (6) therein and our `demo `__ for more details. + see eq. (6) therein and our `demo `__ for more details. Instead of relying on having Pauli words, we use the adjoint representation for a more general evaluation of the cost function. The rest is the same. - .. seealso:: `Theory demo on KAK theorem `__, `demo on KAK decomposition in practice `__, + .. seealso:: `Theory demo on KAK theorem `__, `demo on KAK decomposition in practice `__, Args: H (Union[Operator, PauliSentence, np.ndarray]): Hamiltonian to decompose diff --git a/pennylane/pauli/dla/lie_closure.py b/pennylane/pauli/dla/lie_closure.py index b5db0194889..b65cf24cc5e 100644 --- a/pennylane/pauli/dla/lie_closure.py +++ b/pennylane/pauli/dla/lie_closure.py @@ -375,7 +375,9 @@ def is_independent(self, pauli_sentence, tol=None): return is_independent @staticmethod - def _check_independence(M, pauli_sentence, pw_to_idx, rank, num_pw, tol): + def _check_independence( + M, pauli_sentence, pw_to_idx, rank, num_pw, tol + ): # pylint: disable=too-many-positional-arguments r""" Checks if :class:`~PauliSentence` ``pauli_sentence`` is linearly independent and provides the updated class attributes in case the vector is added.