Source code for pennylane.pauli.dla.structure_constants

# Copyright 2024 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A function to compute the adjoint representation of a Lie algebra"""
from itertools import combinations
from typing import Union

import numpy as np

from pennylane.operation import Operator
from pennylane.typing import TensorLike

from ..pauli_arithmetic import PauliSentence, PauliWord


def _all_commutators(ops):
    commutators = {}
    for (j, op1), (k, op2) in combinations(enumerate(ops), r=2):
        res = op1.commutator(op2)
        if res != PauliSentence({}):
            commutators[(j, k)] = res

    return commutators


[docs]def structure_constants( g: list[Union[Operator, PauliWord, PauliSentence]], pauli: bool = False ) -> TensorLike: r""" Compute the structure constants that make up the adjoint representation of a Lie algebra. Given a DLA :math:`\{iG_1, iG_2, .. iG_d \}` of dimension :math:`d`, the structure constants yield the decomposition of all commutators in terms of DLA elements, .. math:: [i G_\alpha, i G_\beta] = \sum_{\gamma = 0}^{d-1} f^\gamma_{\alpha, \beta} iG_\gamma. The adjoint representation :math:`\left(\text{ad}(iG_\gamma)\right)_{\alpha, \beta} = f^\gamma_{\alpha, \beta}` is given by those structure constants, which can be computed via .. math:: f^\gamma_{\alpha, \beta} = \frac{\text{tr}\left(i G_\gamma \cdot \left[i G_\alpha, i G_\beta \right] \right)}{\text{tr}\left( iG_\gamma iG_\gamma \right)}. Note that this is just the projection of the commutator on the DLA element :math:`iG_\gamma` via the trace inner product. The inputs are assumed to be orthogonal. However, we neither assume nor enforce normalization of the DLA elements :math:`G_\alpha`, hence the normalization factor :math:`\text{tr}\left( iG_\gamma iG_\gamma \right)` in the projection. Args: g (List[Union[Operator, PauliWord, PauliSentence]]): The (dynamical) Lie algebra for which we want to compute its adjoint representation. DLAs can be generated by a set of generators via :func:`~lie_closure`. pauli (bool): Indicates whether it is assumed that :class:`~.PauliSentence` or :class:`~.PauliWord` instances are input. This can help with performance to avoid unnecessary conversions to :class:`~pennylane.operation.Operator` and vice versa. Default is ``False``. Returns: TensorLike: The adjoint representation of shape ``(d, d, d)``, corresponding to indices ``(gamma, alpha, beta)``. .. seealso:: :func:`~lie_closure`, :func:`~center`, :class:`~pennylane.pauli.PauliVSpace`, `Demo: Introduction to Dynamical Lie Algebras for quantum practitioners <https://pennylane.ai/qml/demos/tutorial_liealgebra/>`__ **Example** Let us generate the DLA of the transverse field Ising model using :func:`~lie_closure`. >>> n = 2 >>> gens = [X(i) @ X(i+1) for i in range(n-1)] >>> gens += [Z(i) for i in range(n)] >>> dla = qml.lie_closure(gens) >>> print(dla) [X(0) @ X(1), Z(0), Z(1), -1.0 * (Y(0) @ X(1)), -1.0 * (X(0) @ Y(1)), -1.0 * (Y(0) @ Y(1))] The dimension of the DLA is :math:`d = 6`. Hence, the structure constants have shape ``(6, 6, 6)``. >>> adjoint_rep = qml.structure_constants(dla) >>> adjoint_rep.shape (6, 6, 6) The structure constants tell us the commutation relation between operators in the DLA via .. math:: [i G_\alpha, i G_\beta] = \sum_{\gamma = 0}^{d-1} f^\gamma_{\alpha, \beta} iG_\gamma. Let us confirm those with an example. Take :math:`[iG_1, iG_3] = [iZ_0, -iY_0 X_1] = -i 2 X_0 X_1 = -i 2 G_0`, so we should have :math:`f^0_{1, 3} = -2`, which is indeed the case. >>> adjoint_rep[0, 1, 3] -2.0 We can also look at the overall adjoint action of the first element :math:`G_0 = X_{0} \otimes X_{1}` of the DLA on other elements. In particular, at :math:`\left(\text{ad}(iG_0)\right)_{\alpha, \beta} = f^0_{\alpha, \beta}`, which corresponds to the following matrix. >>> adjoint_rep[0] array([[ 0., 0., 0., 0., 0., 0.], [-0., 0., 0., -2., 0., 0.], [-0., 0., 0., 0., -2., 0.], [-0., 2., -0., 0., 0., 0.], [-0., -0., 2., 0., 0., 0.], [ 0., -0., -0., -0., -0., 0.]]) Note that we neither enforce nor assume normalization by default. """ if any((op.pauli_rep is None) for op in g): raise ValueError( f"Cannot compute adjoint representation of non-pauli operators. Received {g}." ) if not pauli: g = [op.pauli_rep for op in g] commutators = _all_commutators(g) rep = np.zeros((len(g), len(g), len(g)), dtype=float) for i, op in enumerate(g): for (j, k), res in commutators.items(): value = (1j * (op @ res).trace()).real value = value / (op @ op).trace() # v = ∑ (v · e_j / ||e_j||^2) * e_j rep[i, j, k] = value rep[i, k, j] = -value return rep