-
Notifications
You must be signed in to change notification settings - Fork 585
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[DLA 3] Add
qml.dla.adjoint_repr
function (#5406)
A function to compute the adjoint representation of a DLA - [x] Basic functionality - [x] Basic testing - [x] Docs - [x] changelog [sc-51422] #5161 (DLA1) #5169 (DLA2) #5406 (DLA3) --------- Co-authored-by: David Wierichs <david.wierichs@xanadu.ai> Co-authored-by: Vincent Michaud-Rioux <vincentm@nanoacademic.com> Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai> Co-authored-by: Thomas R. Bromley <49409390+trbromley@users.noreply.github.com>
- Loading branch information
1 parent
44018e9
commit a8bba50
Showing
9 changed files
with
255 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
# 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 typing import List, Union | ||
from itertools import combinations | ||
|
||
import numpy as np | ||
|
||
from pennylane.typing import TensorLike | ||
from pennylane.operation import Operator | ||
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 | ||
|
||
|
||
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)``. | ||
**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.pauli.lie_closure(gens) | ||
>>> print(dla) | ||
[X(1) @ X(0), Z(0), Z(1), -1.0 * (X(1) @ Y(0)), -1.0 * (Y(1) @ X(0)), -1.0 * (Y(1) @ Y(0))] | ||
The dimension of the DLA is :math:`d = 6`. Hence, the structure constants have shape ``(6, 6, 6)``. | ||
>>> adjoint_rep = qml.pauli.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. | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# 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. | ||
"""Tests for pennylane/pauli/dla/structure_constants.py functionality""" | ||
import pytest | ||
import numpy as np | ||
|
||
import pennylane as qml | ||
|
||
from pennylane.pauli import PauliWord, PauliSentence, structure_constants | ||
|
||
## Construct some example DLAs | ||
# TFIM | ||
gens = [PauliSentence({PauliWord({i: "X", i + 1: "X"}): 1.0}) for i in range(2)] | ||
gens += [PauliSentence({PauliWord({i: "Z"}): 1.0}) for i in range(3)] | ||
Ising3 = qml.pauli.lie_closure(gens, pauli=True) | ||
|
||
# XXZ-type DLA, i.e. with true PauliSentences | ||
gens2 = [ | ||
PauliSentence( | ||
{ | ||
PauliWord({i: "X", i + 1: "X"}): 1.0, | ||
PauliWord({i: "Y", i + 1: "Y"}): 1.0, | ||
} | ||
) | ||
for i in range(2) | ||
] | ||
gens2 += [PauliSentence({PauliWord({i: "Z"}): 1.0}) for i in range(3)] | ||
XXZ3 = qml.pauli.lie_closure(gens2, pauli=True) | ||
|
||
|
||
class TestAdjointRepr: | ||
"""Tests for structure_constants""" | ||
|
||
def test_structure_constants_dim(self): | ||
"""Test the dimension of the adjoint repr""" | ||
d = len(Ising3) | ||
adjoint = structure_constants(Ising3, pauli=True) | ||
assert adjoint.shape == (d, d, d) | ||
assert adjoint.dtype == float | ||
|
||
@pytest.mark.parametrize("dla", [Ising3, XXZ3]) | ||
def test_structure_constants_elements(self, dla): | ||
r"""Test relation :math:`[i G_\alpha, i G_\beta] = \sum_{\gamma = 0}^{\mathfrak{d}-1} f^\gamma_{\alpha, \beta} iG_\gamma`.""" | ||
|
||
d = len(dla) | ||
ad_rep = structure_constants(dla, pauli=True) | ||
for i in range(d): | ||
for j in range(d): | ||
|
||
comm_res = 1j * dla[i].commutator(dla[j]) | ||
|
||
res = sum( | ||
np.array(c, dtype=complex) * dla[gamma] | ||
for gamma, c in enumerate(ad_rep[:, i, j]) | ||
) | ||
res.simplify() | ||
assert comm_res == res | ||
|
||
@pytest.mark.parametrize("dla", [Ising3, XXZ3]) | ||
def test_use_operators(self, dla): | ||
"""Test that operators can be passed and lead to the same result""" | ||
ad_rep_true = structure_constants(dla, pauli=True) | ||
|
||
ops = [op.operation() for op in dla] | ||
ad_rep = structure_constants(ops, pauli=False) | ||
assert qml.math.allclose(ad_rep, ad_rep_true) | ||
|
||
def test_raise_error_for_non_paulis(self): | ||
"""Test that an error is raised when passing operators that do not have a pauli_rep""" | ||
generators = [qml.Hadamard(0), qml.X(0)] | ||
with pytest.raises( | ||
ValueError, match="Cannot compute adjoint representation of non-pauli operators" | ||
): | ||
qml.pauli.structure_constants(generators) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters