From 96e10ea5cd98dc0beb4563c25c5176d9c1c382ef Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 13 Sep 2023 16:03:11 -0400 Subject: [PATCH 01/34] trotter --- pennylane/templates/subroutines/trotter.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 pennylane/templates/subroutines/trotter.py diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py new file mode 100644 index 00000000000..d6ac5a378fc --- /dev/null +++ b/pennylane/templates/subroutines/trotter.py @@ -0,0 +1,44 @@ +# Copyright 2018-2021 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. +""" +Contains templates for Suzuki-Trotter approximation based subroutines. +""" + +import pennylane as qml + +# from pennylane.math ... +from pennylane import numpy as np +from pennylane.operation import Operation + + +def _scaler(order): + """Assumes that order is an even integer > 2""" + return (4 - 4 ** (order - 1))**-1 + +def + + +class TrotterProduct(Operation): + """Representing the Suzuki-Trotter product approximation""" + + def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): + + if isinstance(hamiltonian, qml.Hamiltonian): + coeffs, ops = hamiltonian.terms() + else: + + + if check_hermitian: + pass + From d28f1fd2f791a71d9b70055c4891476ba02e543c Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 21 Sep 2023 10:46:58 -0400 Subject: [PATCH 02/34] testing --- pennylane/templates/subroutines/trotter.py | 53 ++++++++++++++++++++-- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index d6ac5a378fc..d5100eb8faf 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -14,31 +14,76 @@ """ Contains templates for Suzuki-Trotter approximation based subroutines. """ +import copy import pennylane as qml # from pennylane.math ... from pennylane import numpy as np from pennylane.operation import Operation +from pennylane.ops import Sum -def _scaler(order): +def _scalar(order): """Assumes that order is an even integer > 2""" - return (4 - 4 ** (order - 1))**-1 + root = 1 / (order - 1) + return (4 - 4**root) ** -1 -def + +def _recursive_op(x, order, ops): + """Generate a list of operators.""" + if order == 1: + return [qml.exp(op, x * 1j) for op in ops] + + if order == 2: + return [qml.exp(op, x * 0.5j) for op in (ops + ops[::-1])] + + if order > 2 and order % 2 == 0: + scalar_1 = _scalar(order) + scalar_2 = 1 - 4 * scalar_1 + + ops_lst_1 = _recursive_op(scalar_1 * x, order - 2, ops) + ops_lst_2 = _recursive_op(scalar_2 * x, order - 2, ops) + + return ( + copy.deepcopy(ops_lst_1) + + copy.deepcopy(ops_lst_1) + + ops_lst_2 + + copy.deepcopy(ops_lst_1) + + ops_lst_1 + ) + + raise ValueError(f"The order of a Trotter Product must be 1 or an even integer, got {order}.") class TrotterProduct(Operation): """Representing the Suzuki-Trotter product approximation""" def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): + """Init method for the TrotterProduct class""" if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() - else: + hamiltonian = qml.dot(coeffs, ops) + if not isinstance(hamiltonian, Sum): + raise ValueError(f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum got {hamiltonian}") if check_hermitian: pass + self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} + wires = hamiltonian.wires + super().__init__(time, wires=wires, id=id) + + @staticmethod + def compute_decomposition(*args, **kwargs): + time = args[0] + n = kwargs["num_steps"] + order = kwargs["order"] + ops = kwargs["base"].operands + + with qml.QueuingManager.stop_recording(): + decomp = _recursive_op(time, order, ops) + + return decomp From a604d90335f0a8f783054b7c3caf9d7efe970e9a Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 4 Oct 2023 14:54:35 -0400 Subject: [PATCH 03/34] Adding test file and doc-strings --- pennylane/templates/subroutines/__init__.py | 1 + pennylane/templates/subroutines/trotter.py | 115 +++++++++++++++--- .../test_subroutines/test_trotter.py | 20 +++ 3 files changed, 121 insertions(+), 15 deletions(-) create mode 100644 tests/templates/test_subroutines/test_trotter.py diff --git a/pennylane/templates/subroutines/__init__.py b/pennylane/templates/subroutines/__init__.py index bff83756839..70c64d46fc6 100644 --- a/pennylane/templates/subroutines/__init__.py +++ b/pennylane/templates/subroutines/__init__.py @@ -35,3 +35,4 @@ from .basis_rotation import BasisRotation from .qsvt import QSVT, qsvt from .select import Select +from .trotter import TrotterProduct diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index d5100eb8faf..19c5db15b7b 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -27,9 +27,10 @@ def _scalar(order): """Assumes that order is an even integer > 2""" root = 1 / (order - 1) - return (4 - 4**root) ** -1 + return (4 - 4 ** root) ** -1 +@qml.QueuingManager.stop_recording() def _recursive_op(x, order, ops): """Generate a list of operators.""" if order == 1: @@ -45,32 +46,94 @@ def _recursive_op(x, order, ops): ops_lst_1 = _recursive_op(scalar_1 * x, order - 2, ops) ops_lst_2 = _recursive_op(scalar_2 * x, order - 2, ops) - return ( - copy.deepcopy(ops_lst_1) - + copy.deepcopy(ops_lst_1) - + ops_lst_2 - + copy.deepcopy(ops_lst_1) - + ops_lst_1 - ) + return (2 * ops_lst_1) + ops_lst_2 + (2 * ops_lst_1) - raise ValueError(f"The order of a Trotter Product must be 1 or an even integer, got {order}.") + raise ValueError(f"The order of a Trotter Product must be 1 or a positive even integer, got {order}.") class TrotterProduct(Operation): - """Representing the Suzuki-Trotter product approximation""" + """An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential + of a given hamiltonian. + + The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of hamiltonian + expressed as a linear combination of terms which in general do not commute. Consider the hamiltonian + :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using symmetrized products of the terms + in the hamiltonian. The symmetrized products of order :math: `m \in [1, 2, 4, ..., 2k] | k \in \mathbb{N}` + are given by: + + .. math:: + + \begin{align} + S_{m=1}(t) &= \Pi_{j=0}^{N} \ exp(i t O_{j}) \\ + S_{m=2}(t) &= \Pi_{j=0}^{N} \ exp(i \frac{t}{2} O_{j}) \cdot \Pi_{j=N}^{0} \ exp(i \frac{t}{2} O_{j}) \\ + &\vdots + S_{m=2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2} + \end{align} + + Where the coefficient is :math:`p_{2k} = \frac{1}{4 - \sqrt[2k - 1]{4}}`. + + The :math:`2k`th order, :math:`n`-step Suzuki-Trotter approximation is then defined as: + + .. math:: exp(iHt) \approx (S_{2k}(\frac{t}{n}))^{n} + + Args: + hamiltonian (Union[~.Hamiltonian, ~.Sum]): + time (Union[float, complex]): + + Keyword Args: + n (int): An integer representing the number of Trotter steps to perform. + order (int): An integer representing the order of the approximation (must be 1 or even). + check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator. + + Raises: + ValueError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. + ValueError: One or more of the terms in 'hamiltonian' are not Hermitian. + ValueError: The 'order' is not one or a positive even integer. + + **Example** + + .. code-block:: python3 + + coeffs = [0.25, 0.75] + ops = [qml.PauliX(0), qml.PauliZ(0)] + H = qml.dot(coeffs, ops) + + dev = qml.device("default.qubit", wires=2) + @qml.qnode(dev) + def my_circ(): + # Prepare some state + qml.Hadamard(0) + qml.Hadamard(1) + + # Evolve according to H + qml.TrotterProduct(H, time=0.5, order=2) + + # Measure some quantity + return qml.state() + + .. details:: + :title: Usage Details + + // + + """ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): - """Init method for the TrotterProduct class""" + """Initialize the TrotterProduct class""" if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() hamiltonian = qml.dot(coeffs, ops) if not isinstance(hamiltonian, Sum): - raise ValueError(f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum got {hamiltonian}") + raise ValueError( + f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum got {hamiltonian}" + ) if check_hermitian: - pass + for op in hamiltonian.operands: + if not op.is_hermitian: + raise ValueError(f"One or more of the terms in the Hamiltonian may not be hermitian") self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} wires = hamiltonian.wires @@ -78,12 +141,34 @@ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=Non @staticmethod def compute_decomposition(*args, **kwargs): + r"""Representation of the operator as a product of other operators (static method). + + .. math:: O = O_1 O_2 \dots O_n. + + .. note:: + + Operations making up the decomposition should be queued within the + ``compute_decomposition`` method. + + .. seealso:: :meth:`~.Operator.decomposition`. + + Args: + *params (list): trainable parameters of the operator, as stored in the ``parameters`` attribute + wires (Iterable[Any], Wires): wires that the operator acts on + **hyperparams (dict): non-trainable hyperparameters of the operator, as stored in the ``hyperparameters`` attribute + + Returns: + list[Operator]: decomposition of the operator + """ time = args[0] n = kwargs["num_steps"] order = kwargs["order"] ops = kwargs["base"].operands - with qml.QueuingManager.stop_recording(): - decomp = _recursive_op(time, order, ops) + decomp = _recursive_op(time / n, order, ops) * n + + if qml.QueuingManager.recording(): + for op in decomp: + qml.apply(op) return decomp diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py new file mode 100644 index 00000000000..a62a0aa9263 --- /dev/null +++ b/tests/templates/test_subroutines/test_trotter.py @@ -0,0 +1,20 @@ +# Copyright 2018-2023 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 the TrotterProduct template and helper functions. +""" +import pytest +import pennylane as qml +from pennylane import numpy as np + From 176b274808942f6ef930e734d3eedc698789a9ee Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 10 Oct 2023 12:56:49 -0400 Subject: [PATCH 04/34] Adding tests for TrotterProduct --- pennylane/templates/subroutines/trotter.py | 62 +++- .../test_subroutines/test_trotter.py | 317 +++++++++++++++++- 2 files changed, 360 insertions(+), 19 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 19c5db15b7b..0cb5f43f9e0 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -27,11 +27,11 @@ def _scalar(order): """Assumes that order is an even integer > 2""" root = 1 / (order - 1) - return (4 - 4 ** root) ** -1 + return (4 - 4**root) ** -1 @qml.QueuingManager.stop_recording() -def _recursive_op(x, order, ops): +def _recursive_decomposition(x, order, ops): """Generate a list of operators.""" if order == 1: return [qml.exp(op, x * 1j) for op in ops] @@ -39,16 +39,13 @@ def _recursive_op(x, order, ops): if order == 2: return [qml.exp(op, x * 0.5j) for op in (ops + ops[::-1])] - if order > 2 and order % 2 == 0: - scalar_1 = _scalar(order) - scalar_2 = 1 - 4 * scalar_1 + scalar_1 = _scalar(order) + scalar_2 = 1 - 4 * scalar_1 - ops_lst_1 = _recursive_op(scalar_1 * x, order - 2, ops) - ops_lst_2 = _recursive_op(scalar_2 * x, order - 2, ops) + ops_lst_1 = _recursive_decomposition(scalar_1 * x, order - 2, ops) + ops_lst_2 = _recursive_decomposition(scalar_2 * x, order - 2, ops) - return (2 * ops_lst_1) + ops_lst_2 + (2 * ops_lst_1) - - raise ValueError(f"The order of a Trotter Product must be 1 or a positive even integer, got {order}.") + return (2 * ops_lst_1) + ops_lst_2 + (2 * ops_lst_1) class TrotterProduct(Operation): @@ -78,7 +75,6 @@ class TrotterProduct(Operation): Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): - time (Union[float, complex]): Keyword Args: n (int): An integer representing the number of Trotter steps to perform. @@ -86,7 +82,7 @@ class TrotterProduct(Operation): check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator. Raises: - ValueError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. + TypeError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. ValueError: One or more of the terms in 'hamiltonian' are not Hermitian. ValueError: The 'order' is not one or a positive even integer. @@ -103,37 +99,67 @@ class TrotterProduct(Operation): def my_circ(): # Prepare some state qml.Hadamard(0) - qml.Hadamard(1) # Evolve according to H - qml.TrotterProduct(H, time=0.5, order=2) + qml.TrotterProduct(H, time=2.4, order=2) # Measure some quantity return qml.state() + >>> my_circ() + [-0.13259524+0.59790098j 0. +0.j -0.13259524-0.77932754j 0. +0.j ] + .. details:: :title: Usage Details - // + We can also compute the gradient with respect to the coefficients of the hamiltonian and the + evolution time: + + .. code-block:: python3 + + @qml.qnode(dev) + def my_circ(c1, c2, time): + # Prepare H: + H = qml.dot([c1, c2], [qml.PauliX(0), qml.PauliZ(1)]) + + # Prepare some state + qml.Hadamard(0) + + # Evolve according to H + qml.TrotterProduct(H, time, order=2) + + # Measure some quantity + return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) + + >>> args = qnp.array([1.23, 4.5, 0.1]) + >>> qml.grad(my_circ)(*tuple(args)) + (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) """ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): """Initialize the TrotterProduct class""" + if not (order > 0 and (order == 1 or order % 2 == 0)): + raise ValueError( + f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}." + ) + if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() hamiltonian = qml.dot(coeffs, ops) if not isinstance(hamiltonian, Sum): - raise ValueError( + raise TypeError( f"The given operator must be a PennyLane ~.Hamiltonian or ~.Sum got {hamiltonian}" ) if check_hermitian: for op in hamiltonian.operands: if not op.is_hermitian: - raise ValueError(f"One or more of the terms in the Hamiltonian may not be hermitian") + raise ValueError( + "One or more of the terms in the Hamiltonian may not be hermitian" + ) self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} wires = hamiltonian.wires @@ -165,7 +191,7 @@ def compute_decomposition(*args, **kwargs): order = kwargs["order"] ops = kwargs["base"].operands - decomp = _recursive_op(time / n, order, ops) * n + decomp = _recursive_decomposition(time / n, order, ops) * n if qml.QueuingManager.recording(): for op in decomp: diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index a62a0aa9263..812d489933f 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -16,5 +16,320 @@ """ import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane.templates.subroutines.trotter import ( + _recursive_decomposition, + _scalar, +) # pylint: disable=private-access +from pennylane import numpy as qnp +test_hamiltonians = ( + qml.dot([1, 1, 1], [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(1)]), + qml.dot( + [1.23, -0.45], [qml.s_prod(0.1, qml.PauliX(0)), qml.prod(qml.PauliX(0), qml.PauliZ(1))] + ), # op arith + qml.dot( + [1, -0.5, 0.5], [qml.Identity(wires=[0, 1]), qml.PauliZ(0), qml.PauliZ(0)] + ), # H = Identity +) + +p_4 = (4 - 4 ** (1 / 3)) ** -1 + +test_decompositions = { # (hamiltonian_index, order): decomposition assuming t = 4.2 + (0, 1): [ + qml.exp(qml.PauliX(0), 4.2j), + qml.exp(qml.PauliY(0), 4.2j), + qml.exp(qml.PauliZ(1), 4.2j), + ], + (0, 2): [ + qml.exp(qml.PauliX(0), 4.2j / 2), + qml.exp(qml.PauliY(0), 4.2j / 2), + qml.exp(qml.PauliZ(1), 4.2j / 2), + qml.exp(qml.PauliZ(1), 4.2j / 2), + qml.exp(qml.PauliY(0), 4.2j / 2), + qml.exp(qml.PauliX(0), 4.2j / 2), + ], + (0, 4): [ + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), # S_2((1 - 4p) * t) + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 + ], + (1, 1): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j), + ], + (1, 2): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), + ], + (1, 4): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + ], + (2, 1): [ + qml.exp(qml.Identity(wires=[0, 1]), 4.2j), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j), + ], + (2, 2): [ + qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), + ], + (2, 4): [ + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + ], +} + + +class TestInitialization: + """Test the TrotterProduct class initializes correctly.""" + + @pytest.mark.parametrize( + "hamiltonian, raise_error", + ( + (qml.PauliX(0), True), + (qml.prod(qml.PauliX(0), qml.PauliZ(1)), True), + (qml.Hamiltonian([1.23, 3.45], [qml.PauliX(0), qml.PauliZ(1)]), False), + (qml.dot([1.23, 3.45], [qml.PauliX(0), qml.PauliZ(1)]), False), + ), + ) + def test_error_type(self, hamiltonian, raise_error): + """Test an error is raised of an incorrect type is passed""" + if raise_error: + with pytest.raises( + TypeError, match="The given operator must be a PennyLane ~.Hamiltonian or ~.Sum" + ): + qml.TrotterProduct(hamiltonian, time=1.23) + + else: + try: + qml.TrotterProduct(hamiltonian, time=1.23) + except TypeError: + assert False # test should fail if an error was raised when we expect it not to + + @pytest.mark.parametrize( + "hamiltonian", + ( + qml.Hamiltonian([1.23, 4 + 5j], [qml.PauliX(0), qml.PauliZ(1)]), + qml.dot([1.23, 4 + 5j], [qml.PauliX(0), qml.PauliZ(1)]), + qml.dot([1.23, 0.5], [qml.RY(1.23, 0), qml.RZ(3.45, 1)]), + ), + ) + def test_error_hermiticity(self, hamiltonian): + """Test that an error is raised if any terms in + the hamiltonian are not hermitian and check_hermitian is True.""" + + with pytest.raises( + ValueError, match="One or more of the terms in the Hamiltonian may not be hermitian" + ): + qml.TrotterProduct(hamiltonian, time=0.5) + + try: + qml.TrotterProduct(hamiltonian, time=0.5, check_hermitian=False) + except ValueError: + assert False # No error should be raised if the check_hermitian flag is disabled + + @pytest.mark.parametrize("order", (-1, 0, 0.5, 3, 7.0)) + def test_error_order(self, order): + """Test that an error is raised if 'order' is not one or positive even number.""" + time = 0.5 + hamiltonian = qml.dot([1.23, 3.45], [qml.PauliX(0), qml.PauliZ(1)]) + + with pytest.raises( + ValueError, match="The order of a TrotterProduct must be 1 or a positive even integer," + ): + qml.TrotterProduct(hamiltonian, time, order=order) + + def test_init_correctly(self): + """Test that all of the attributes are initalized correctly.""" + pass + + def test_copy(self): + """Test that we can make deep and shallow copies of TrotterProduct correctly.""" + pass + + def test_flatten_and_unflatten(self): + """Test that the flatten and unflatten methods work correctly.""" + pass + + +class TestPrivateFunctions: + """Test the private helper functions.""" + + @pytest.mark.parametrize( + "order, result", + ( + (4, 0.4144907717943757), + (6, 0.3730658277332728), + (8, 0.35958464934999224), + ), + ) # Computed by hand + def test_private_scalar(self, order, result): + """Test the _scalar function correctly computes the parameter scalar.""" + s = _scalar(order) + assert qnp.isclose(s, result) + + expected_expansions = ( # for H = X0 + Y0 + Z1, t = 1.23, computed by hand + [ # S_1(t) + qml.exp(qml.PauliX(0), 1.23j), + qml.exp(qml.PauliY(0), 1.23j), + qml.exp(qml.PauliZ(1), 1.23j), + ], + [ # S_2(t) + qml.exp(qml.PauliX(0), 1.23j / 2), + qml.exp(qml.PauliY(0), 1.23j / 2), + qml.exp(qml.PauliZ(1), 1.23j / 2), + qml.exp(qml.PauliZ(1), 1.23j / 2), + qml.exp(qml.PauliY(0), 1.23j / 2), + qml.exp(qml.PauliX(0), 1.23j / 2), + ], + [ # S_4(t) + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), # S_2(p * t) ^ 2 + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 1.23j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 1.23j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 1.23j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 1.23j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 1.23j / 2), + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 1.23j / 2), # S_2((1 - 4p) * t) + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliZ(1), p_4 * 1.23j / 2), + qml.exp(qml.PauliY(0), p_4 * 1.23j / 2), + qml.exp(qml.PauliX(0), p_4 * 1.23j / 2), # S_2(p * t) ^ 2 + ], + ) + + @pytest.mark.parametrize("order, expected_expansion", zip((1, 2, 4), expected_expansions)) + def test_recursive_decomposition_no_queue(self, order, expected_expansion): + """Test the _recursive_decomposition function correctly generates the decomposition""" + ops = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(1)] + + with qml.tape.QuantumTape() as tape: + decomp = _recursive_decomposition(1.23, order, ops) + + assert tape.operations == [] # No queuing! + assert decomp == expected_expansion # Expected decomposition + + +class TestDecomposition: + """Test the decomposition of the TrotterProduct class.""" + + @pytest.mark.parametrize("order", (1, 2, 4)) + @pytest.mark.parametrize("hamiltonian_index, hamiltonian", enumerate(test_hamiltonians)) + def test_compute_decomposition(self, hamiltonian, hamiltonian_index, order): + """Test the decomposition is correct and queues""" + op = qml.TrotterProduct(hamiltonian, 4.2, order=order) + with qml.tape.QuantumTape() as tape: + decomp = op.compute_decomposition(*op.parameters, **op.hyperparameters) + + assert decomp == tape.operations # queue matches decomp + + decomp = [qml.simplify(op) for op in decomp] + true_decomp = test_decompositions[(hamiltonian_index, order)] + assert decomp == true_decomp # decomp is correct + + +class TestIntegration: + """Test that the TrotterProduct can be executed and differentiated + through all interfaces.""" + + def test_execute_circuit(self): + """Test that the gate executes correctly in a circuit.""" + pass From 7a151d1654dc6c2549d065cccd37d3ca3fb2e7bc Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 10 Oct 2023 14:00:56 -0400 Subject: [PATCH 05/34] fix doc-string --- pennylane/templates/subroutines/trotter.py | 37 +++++++++++++++------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 0cb5f43f9e0..38e9fda3de5 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -25,14 +25,31 @@ def _scalar(order): - """Assumes that order is an even integer > 2""" + """Compute the scalar used in the recursive expression. + + Args: + order (int): order of trotter product (assume order is an even integer > 2). + + Returns: + float: scalar to be used in the recursive expression. + """ root = 1 / (order - 1) - return (4 - 4**root) ** -1 + return (4 - 4 ** root) ** -1 @qml.QueuingManager.stop_recording() def _recursive_decomposition(x, order, ops): - """Generate a list of operators.""" + """Generate a list of operations using the + recursive expression which defines the trotter product. + + Args: + x (complex): the evolution 'time' + order (int): the order of the Trotter Expansion + ops (Iterable(~.Operators)): a list of terms in the Hamiltonian + + Returns: + List: the decomposition as product of exponentials of the hamiltonian terms + """ if order == 1: return [qml.exp(op, x * 1j) for op in ops] @@ -49,7 +66,7 @@ def _recursive_decomposition(x, order, ops): class TrotterProduct(Operation): - """An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential + f"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential of a given hamiltonian. The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of hamiltonian @@ -138,12 +155,10 @@ def my_circ(c1, c2, time): """ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): - """Initialize the TrotterProduct class""" + r"""Initialize the TrotterProduct class""" - if not (order > 0 and (order == 1 or order % 2 == 0)): - raise ValueError( - f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}." - ) + if not (order > 0 and (order == 1 or order % 2 == 0)): + raise ValueError(f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}.") if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() @@ -157,9 +172,7 @@ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=Non if check_hermitian: for op in hamiltonian.operands: if not op.is_hermitian: - raise ValueError( - "One or more of the terms in the Hamiltonian may not be hermitian" - ) + raise ValueError("One or more of the terms in the Hamiltonian may not be hermitian") self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} wires = hamiltonian.wires From ec3c5856a12beda3559fb6eb07f36741e779b407 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 10 Oct 2023 14:53:27 -0400 Subject: [PATCH 06/34] Fix decomp test and doc-string --- pennylane/ops/functions/equal.py | 8 +++++-- pennylane/templates/subroutines/trotter.py | 21 +++++++++++-------- .../test_subroutines/test_trotter.py | 10 ++++++--- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py index b7af0f8f422..f6a513ac235 100644 --- a/pennylane/ops/functions/equal.py +++ b/pennylane/ops/functions/equal.py @@ -264,7 +264,9 @@ def _equal_adjoint(op1: Adjoint, op2: Adjoint, **kwargs): # pylint: disable=unused-argument def _equal_exp(op1: Exp, op2: Exp, **kwargs): """Determine whether two Exp objects are equal""" - if op1.coeff != op2.coeff: + rtol, atol = (kwargs["rtol"], kwargs["atol"]) + + if not qml.math.allclose(op1.coeff, op2.coeff, rtol=rtol, atol=atol): return False return qml.equal(op1.base, op2.base) @@ -273,7 +275,9 @@ def _equal_exp(op1: Exp, op2: Exp, **kwargs): # pylint: disable=unused-argument def _equal_sprod(op1: SProd, op2: SProd, **kwargs): """Determine whether two SProd objects are equal""" - if op1.scalar != op2.scalar: + rtol, atol = (kwargs["rtol"], kwargs["atol"]) + + if not qml.math.allclose(op1.scalar, op2.scalar, rtol=rtol, atol=atol): return False return qml.equal(op1.base, op2.base) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 38e9fda3de5..90bb13f6548 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -25,16 +25,16 @@ def _scalar(order): - """Compute the scalar used in the recursive expression. + """Compute the scalar used in the recursive expression. Args: order (int): order of trotter product (assume order is an even integer > 2). Returns: - float: scalar to be used in the recursive expression. + float: scalar to be used in the recursive expression. """ root = 1 / (order - 1) - return (4 - 4 ** root) ** -1 + return (4 - 4**root) ** -1 @qml.QueuingManager.stop_recording() @@ -48,7 +48,7 @@ def _recursive_decomposition(x, order, ops): ops (Iterable(~.Operators)): a list of terms in the Hamiltonian Returns: - List: the decomposition as product of exponentials of the hamiltonian terms + List: the decomposition as product of exponentials of the hamiltonian terms """ if order == 1: return [qml.exp(op, x * 1j) for op in ops] @@ -66,7 +66,7 @@ def _recursive_decomposition(x, order, ops): class TrotterProduct(Operation): - f"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential + r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential of a given hamiltonian. The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of hamiltonian @@ -151,14 +151,15 @@ def my_circ(c1, c2, time): >>> args = qnp.array([1.23, 4.5, 0.1]) >>> qml.grad(my_circ)(*tuple(args)) (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) - """ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): r"""Initialize the TrotterProduct class""" - if not (order > 0 and (order == 1 or order % 2 == 0)): - raise ValueError(f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}.") + if order <= 0 or order != 1 and order % 2 != 0: + raise ValueError( + f"The order of a TrotterProduct must be 1 or a positive even integer, got {order}." + ) if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() @@ -172,7 +173,9 @@ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=Non if check_hermitian: for op in hamiltonian.operands: if not op.is_hermitian: - raise ValueError("One or more of the terms in the Hamiltonian may not be hermitian") + raise ValueError( + "One or more of the terms in the Hamiltonian may not be hermitian" + ) self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} wires = hamiltonian.wires diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index 812d489933f..51d56580292 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -305,7 +305,9 @@ def test_recursive_decomposition_no_queue(self, order, expected_expansion): decomp = _recursive_decomposition(1.23, order, ops) assert tape.operations == [] # No queuing! - assert decomp == expected_expansion # Expected decomposition + assert all( + qml.equal(op1, op2) for op1, op2 in zip(decomp, expected_expansion) + ) # Expected decomp class TestDecomposition: @@ -322,8 +324,10 @@ def test_compute_decomposition(self, hamiltonian, hamiltonian_index, order): assert decomp == tape.operations # queue matches decomp decomp = [qml.simplify(op) for op in decomp] - true_decomp = test_decompositions[(hamiltonian_index, order)] - assert decomp == true_decomp # decomp is correct + true_decomp = [qml.simplify(op) for op in test_decompositions[(hamiltonian_index, order)]] + assert all( + qml.equal(op1, op2) for op1, op2 in zip(decomp, true_decomp) + ) # decomp is correct class TestIntegration: From a46ae6491f67a6520cabb7f181d5726015cae957 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Tue, 10 Oct 2023 17:45:43 -0400 Subject: [PATCH 07/34] add execution test --- pennylane/interfaces/execution.py | 3 ++ pennylane/templates/subroutines/trotter.py | 12 ++--- .../test_subroutines/test_trotter.py | 46 +++++++++++++++---- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/pennylane/interfaces/execution.py b/pennylane/interfaces/execution.py index a5d07e979ae..fae05de7fc5 100644 --- a/pennylane/interfaces/execution.py +++ b/pennylane/interfaces/execution.py @@ -246,6 +246,9 @@ def inner_execute(tapes: Sequence[QuantumTape], **_) -> ResultBatch: tapes = tuple(expand_fn(t) for t in tapes) if numpy_only: tapes = tuple(qml.transforms.convert_to_numpy_parameters(t) for t in tapes) + + for tape in tapes: + print(f"t1: {tape.operations}, {tape.measurements}") return cached_device_execution(tapes) return inner_execute diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 90bb13f6548..054939d707f 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -38,7 +38,7 @@ def _scalar(order): @qml.QueuingManager.stop_recording() -def _recursive_decomposition(x, order, ops): +def _recursive_expression(x, order, ops): """Generate a list of operations using the recursive expression which defines the trotter product. @@ -48,7 +48,7 @@ def _recursive_decomposition(x, order, ops): ops (Iterable(~.Operators)): a list of terms in the Hamiltonian Returns: - List: the decomposition as product of exponentials of the hamiltonian terms + List: the approximation as product of exponentials of the hamiltonian terms """ if order == 1: return [qml.exp(op, x * 1j) for op in ops] @@ -59,8 +59,8 @@ def _recursive_decomposition(x, order, ops): scalar_1 = _scalar(order) scalar_2 = 1 - 4 * scalar_1 - ops_lst_1 = _recursive_decomposition(scalar_1 * x, order - 2, ops) - ops_lst_2 = _recursive_decomposition(scalar_2 * x, order - 2, ops) + ops_lst_1 = _recursive_expression(scalar_1 * x, order - 2, ops) + ops_lst_2 = _recursive_expression(scalar_2 * x, order - 2, ops) return (2 * ops_lst_1) + ops_lst_2 + (2 * ops_lst_1) @@ -207,10 +207,10 @@ def compute_decomposition(*args, **kwargs): order = kwargs["order"] ops = kwargs["base"].operands - decomp = _recursive_decomposition(time / n, order, ops) * n + decomp = _recursive_expression(time / n, order, ops)[-1::-1] * n if qml.QueuingManager.recording(): - for op in decomp: + for op in decomp: # apply operators in reverse order of expression qml.apply(op) return decomp diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index 51d56580292..0d7170fe389 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -15,9 +15,10 @@ Tests for the TrotterProduct template and helper functions. """ import pytest +from functools import reduce import pennylane as qml from pennylane.templates.subroutines.trotter import ( - _recursive_decomposition, + _recursive_expression, _scalar, ) # pylint: disable=private-access from pennylane import numpy as qnp @@ -297,17 +298,17 @@ def test_private_scalar(self, order, result): ) @pytest.mark.parametrize("order, expected_expansion", zip((1, 2, 4), expected_expansions)) - def test_recursive_decomposition_no_queue(self, order, expected_expansion): - """Test the _recursive_decomposition function correctly generates the decomposition""" + def test_recursive_expression_no_queue(self, order, expected_expansion): + """Test the _recursive_expression function correctly generates the decomposition""" ops = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(1)] with qml.tape.QuantumTape() as tape: - decomp = _recursive_decomposition(1.23, order, ops) + decomp = _recursive_expression(1.23, order, ops) assert tape.operations == [] # No queuing! assert all( qml.equal(op1, op2) for op1, op2 in zip(decomp, expected_expansion) - ) # Expected decomp + ) # Expected expression class TestDecomposition: @@ -321,10 +322,12 @@ def test_compute_decomposition(self, hamiltonian, hamiltonian_index, order): with qml.tape.QuantumTape() as tape: decomp = op.compute_decomposition(*op.parameters, **op.hyperparameters) - assert decomp == tape.operations # queue matches decomp + assert decomp == tape.operations # queue matches decomp with circuit ordering decomp = [qml.simplify(op) for op in decomp] - true_decomp = [qml.simplify(op) for op in test_decompositions[(hamiltonian_index, order)]] + true_decomp = [ + qml.simplify(op) for op in test_decompositions[(hamiltonian_index, order)][-1::-1] + ] assert all( qml.equal(op1, op2) for op1, op2 in zip(decomp, true_decomp) ) # decomp is correct @@ -334,6 +337,31 @@ class TestIntegration: """Test that the TrotterProduct can be executed and differentiated through all interfaces.""" - def test_execute_circuit(self): + @pytest.mark.parametrize("order", (1, 2, 4)) + @pytest.mark.parametrize("hamiltonian_index, hamiltonian", enumerate(test_hamiltonians)) + def test_execute_circuit(self, hamiltonian, hamiltonian_index, order): """Test that the gate executes correctly in a circuit.""" - pass + wires = hamiltonian.wires + dev = qml.device("default.qubit", wires=wires) + + @qml.qnode(dev) + def circ(): + qml.TrotterProduct(hamiltonian, time=4.2, order=order) + return qml.state() + + initial_state = qnp.zeros(2 ** (len(wires))) + initial_state[0] = 1 + + expected_state = ( + reduce( + lambda x, y: x @ y, + [ + qml.matrix(op, wire_order=wires) + for op in test_decompositions[(hamiltonian_index, order)] + ], + ) + @ initial_state + ) + state = circ() + + assert qnp.allclose(expected_state, state) From 80de04f96bc9bb7d7d03dd6d0ed71aee5df6fc1e Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 11 Oct 2023 10:00:49 -0400 Subject: [PATCH 08/34] Apply suggestions from code review --- pennylane/interfaces/execution.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pennylane/interfaces/execution.py b/pennylane/interfaces/execution.py index fae05de7fc5..a5d07e979ae 100644 --- a/pennylane/interfaces/execution.py +++ b/pennylane/interfaces/execution.py @@ -246,9 +246,6 @@ def inner_execute(tapes: Sequence[QuantumTape], **_) -> ResultBatch: tapes = tuple(expand_fn(t) for t in tapes) if numpy_only: tapes = tuple(qml.transforms.convert_to_numpy_parameters(t) for t in tapes) - - for tape in tapes: - print(f"t1: {tape.operations}, {tape.measurements}") return cached_device_execution(tapes) return inner_execute From 805b50a6f9bfeff48950bcd47b7a0f37ba28e801 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 11 Oct 2023 15:26:55 -0400 Subject: [PATCH 09/34] Final tests and polishing --- pennylane/templates/subroutines/trotter.py | 79 +++++++++++- tests/ops/functions/test_equal.py | 16 +++ .../test_subroutines/test_trotter.py | 118 ++++++++++++++++-- 3 files changed, 202 insertions(+), 11 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 054939d707f..57ad7f4e20c 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -177,9 +177,80 @@ def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=Non "One or more of the terms in the Hamiltonian may not be hermitian" ) - self._hyperparameters = {"num_steps": n, "order": order, "base": hamiltonian} - wires = hamiltonian.wires - super().__init__(time, wires=wires, id=id) + self._hyperparameters = { + "n": n, + "order": order, + "base": hamiltonian, + "check_hermitian": check_hermitian, + } + super().__init__(time, wires=hamiltonian.wires, id=id) + + def _flatten(self): + """Serialize the operation into trainable and non-trainable components. + + Returns: + data, metadata: The trainable and non-trainable components. + + See ``Operator._unflatten``. + + The data component can be recursive and include other operations. For example, the trainable component of ``Adjoint(RX(1, wires=0))`` + will be the operator ``RX(1, wires=0)``. + + The metadata **must** be hashable. If the hyperparameters contain a non-hashable component, then this + method and ``Operator._unflatten`` should be overridden to provide a hashable version of the hyperparameters. + + **Example:** + + >>> op = qml.Rot(1.2, 2.3, 3.4, wires=0) + >>> qml.Rot._unflatten(*op._flatten()) + Rot(1.2, 2.3, 3.4, wires=[0]) + >>> op = qml.PauliRot(1.2, "XY", wires=(0,1)) + >>> qml.PauliRot._unflatten(*op._flatten()) + PauliRot(1.2, XY, wires=[0, 1]) + + Operators that have trainable components that differ from their ``Operator.data`` must implement their own + ``_flatten`` methods. + + >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) + >>> op._flatten() + ((U2(3.4, 4.5, wires=['a']),), + (, (True, True), )) + """ + hamiltonian = self.hyperparameters["base"] + time = self.parameters[0] + + hashable_hyperparameters = tuple( + (key, value) for key, value in self.hyperparameters.items() if key != "base" + ) + return (hamiltonian, time), hashable_hyperparameters + + @classmethod + def _unflatten(cls, data, metadata): + """Recreate an operation from its serialized format. + + Args: + data: the trainable component of the operation + metadata: the non-trainable component of the operation. + + The output of ``Operator._flatten`` and the class type must be sufficient to reconstruct the original + operation with ``Operator._unflatten``. + + **Example:** + + >>> op = qml.Rot(1.2, 2.3, 3.4, wires=0) + >>> op._flatten() + ((1.2, 2.3, 3.4), (, ())) + >>> qml.Rot._unflatten(*op._flatten()) + >>> op = qml.PauliRot(1.2, "XY", wires=(0,1)) + >>> op._flatten() + ((1.2,), (, (('pauli_word', 'XY'),))) + >>> op = qml.ctrl(qml.U2(3.4, 4.5, wires="a"), ("b", "c") ) + >>> type(op)._unflatten(*op._flatten()) + Controlled(U2(3.4, 4.5, wires=['a']), control_wires=['b', 'c']) + + """ + hyperparameters_dict = dict(metadata) + return cls(*data, **hyperparameters_dict) @staticmethod def compute_decomposition(*args, **kwargs): @@ -203,7 +274,7 @@ def compute_decomposition(*args, **kwargs): list[Operator]: decomposition of the operator """ time = args[0] - n = kwargs["num_steps"] + n = kwargs["n"] order = kwargs["order"] ops = kwargs["base"].operands diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index c8c5e308002..679bddfa98f 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -1438,6 +1438,14 @@ def test_exp_comparison(self, bases_bases_match, params_params_match): op2 = qml.exp(base2, param2) assert qml.equal(op1, op2) == (bases_match and params_match) + def test_exp_comparison_with_tolerance(self): + """Test that equal compares the parameters within a provided tolerance.""" + op1 = qml.exp(qml.PauliX(0), 0.12345) + op2 = qml.exp(qml.PauliX(0), 0.12356) + + assert qml.equal(op1, op2, atol=1e-3, rtol=1e-2) + assert not qml.equal(op1, op2, atol=1e-5, rtol=1e-4) + @pytest.mark.parametrize("bases_bases_match", BASES) @pytest.mark.parametrize("params_params_match", PARAMS) def test_s_prod_comparison(self, bases_bases_match, params_params_match): @@ -1448,6 +1456,14 @@ def test_s_prod_comparison(self, bases_bases_match, params_params_match): op2 = qml.s_prod(param2, base2) assert qml.equal(op1, op2) == (bases_match and params_match) + def test_s_prod_comparison_with_tolerance(self): + """Test that equal compares the parameters within a provided tolerance.""" + op1 = qml.s_prod(0.12345, qml.PauliX(0)) + op2 = qml.s_prod(0.12356, qml.PauliX(0)) + + assert qml.equal(op1, op2, atol=1e-3, rtol=1e-2) + assert not qml.equal(op1, op2, atol=1e-5, rtol=1e-4) + class TestProdComparisons: """Tests comparisons between Prod operators""" diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index 0d7170fe389..fc2e698cb5b 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -14,8 +14,10 @@ """ Tests for the TrotterProduct template and helper functions. """ +import copy import pytest from functools import reduce + import pennylane as qml from pennylane.templates.subroutines.trotter import ( _recursive_expression, @@ -220,17 +222,51 @@ def test_error_order(self, order): ): qml.TrotterProduct(hamiltonian, time, order=order) - def test_init_correctly(self): + @pytest.mark.parametrize("hamiltonian", test_hamiltonians) + def test_init_correctly(self, hamiltonian): """Test that all of the attributes are initalized correctly.""" - pass - - def test_copy(self): + time, n, order = (4.2, 10, 4) + op = qml.TrotterProduct(hamiltonian, time, n=n, order=order, check_hermitian=False) + + assert op.wires == hamiltonian.wires + assert op.parameters == [time] + assert op.data == (time,) + assert op.hyperparameters == { + "base": hamiltonian, + "n": n, + "order": order, + "check_hermitian": False, + } + + @pytest.mark.parametrize("n", (1, 2, 5, 10)) + @pytest.mark.parametrize("time", (0.5, 1.2)) + @pytest.mark.parametrize("order", (1, 2, 4)) + @pytest.mark.parametrize("hamiltonian", test_hamiltonians) + def test_copy(self, hamiltonian, time, n, order): """Test that we can make deep and shallow copies of TrotterProduct correctly.""" - pass + op = qml.TrotterProduct(hamiltonian, time, n=n, order=order) + new_op = copy.copy(op) + + assert op.wires == new_op.wires + assert op.parameters == new_op.parameters + assert op.data == new_op.data + assert op.hyperparameters == new_op.hyperparameters + assert op is not new_op - def test_flatten_and_unflatten(self): + @pytest.mark.parametrize("hamiltonian", test_hamiltonians) + def test_flatten_and_unflatten(self, hamiltonian): """Test that the flatten and unflatten methods work correctly.""" - pass + time, n, order = (4.2, 10, 4) + op = qml.TrotterProduct(hamiltonian, time, n=n, order=order) + + data, metadata = op._flatten() + assert qml.equal(data[0], hamiltonian) + assert data[1] == time + assert dict(metadata) == {"n": n, "order": order, "check_hermitian": True} + + new_op = type(op)._unflatten(data, metadata) + assert qml.equal(op, new_op) + assert new_op is not op class TestPrivateFunctions: @@ -332,6 +368,32 @@ def test_compute_decomposition(self, hamiltonian, hamiltonian_index, order): qml.equal(op1, op2) for op1, op2 in zip(decomp, true_decomp) ) # decomp is correct + @pytest.mark.parametrize("order", (1, 2)) + @pytest.mark.parametrize("num_steps", (1, 2, 3)) + def test_compute_decomposition_n_steps(self, num_steps, order): + """Test the decomposition is correct when we set the number of trotter steps""" + time = 0.5 + hamiltonian = qml.sum(qml.PauliX(0), qml.PauliZ(0)) + + if order == 1: + base_decomp = [ + qml.exp(qml.PauliZ(0), 0.5j / num_steps), + qml.exp(qml.PauliX(0), 0.5j / num_steps), + ] + if order == 2: + base_decomp = [ + qml.exp(qml.PauliX(0), 0.25j / num_steps), + qml.exp(qml.PauliZ(0), 0.25j / num_steps), + qml.exp(qml.PauliZ(0), 0.25j / num_steps), + qml.exp(qml.PauliX(0), 0.25j / num_steps), + ] + + true_decomp = base_decomp * num_steps + + op = qml.TrotterProduct(hamiltonian, time, n=num_steps, order=order) + decomp = op.compute_decomposition(*op.parameters, **op.hyperparameters) + assert all(qml.equal(op1, op2) for op1, op2 in zip(decomp, true_decomp)) + class TestIntegration: """Test that the TrotterProduct can be executed and differentiated @@ -365,3 +427,45 @@ def circ(): state = circ() assert qnp.allclose(expected_state, state) + + @pytest.mark.parametrize("order", (1, 2)) + @pytest.mark.parametrize("num_steps", (1, 2, 3)) + def test_execute_circuit_n_steps(self, num_steps, order): + """Test that the circuit executes as expected when we set the number of trotter steps""" + time = 0.5 + hamiltonian = qml.sum(qml.PauliX(0), qml.PauliZ(0)) + + if order == 1: + base_decomp = [ + qml.exp(qml.PauliZ(0), 0.5j / num_steps), + qml.exp(qml.PauliX(0), 0.5j / num_steps), + ] + if order == 2: + base_decomp = [ + qml.exp(qml.PauliX(0), 0.25j / num_steps), + qml.exp(qml.PauliZ(0), 0.25j / num_steps), + qml.exp(qml.PauliZ(0), 0.25j / num_steps), + qml.exp(qml.PauliX(0), 0.25j / num_steps), + ] + + true_decomp = base_decomp * num_steps + + wires = hamiltonian.wires + dev = qml.device("default.qubit", wires=wires) + + @qml.qnode(dev) + def circ(): + qml.TrotterProduct(hamiltonian, time, n=num_steps, order=order) + return qml.state() + + initial_state = qnp.zeros(2 ** (len(wires))) + initial_state[0] = 1 + + expected_state = ( + reduce( + lambda x, y: x @ y, [qml.matrix(op, wire_order=wires) for op in true_decomp[-1::-1]] + ) + @ initial_state + ) + state = circ() + assert qnp.allclose(expected_state, state) From d75bfc6120bb13cf2f50a2c25239c6df5112b347 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 11 Oct 2023 16:52:48 -0400 Subject: [PATCH 10/34] codefactor --- pennylane/templates/subroutines/trotter.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 57ad7f4e20c..52851c646ed 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -14,12 +14,7 @@ """ Contains templates for Suzuki-Trotter approximation based subroutines. """ -import copy - import pennylane as qml - -# from pennylane.math ... -from pennylane import numpy as np from pennylane.operation import Operation from pennylane.ops import Sum @@ -153,7 +148,9 @@ def my_circ(c1, c2, time): (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) """ - def __init__(self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None): + def __init__( + self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None + ): # pylin: disable=too-many-arguments r"""Initialize the TrotterProduct class""" if order <= 0 or order != 1 and order % 2 != 0: From 4c60eb49958566b959eb739671a813a0b552150c Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 11 Oct 2023 17:04:18 -0400 Subject: [PATCH 11/34] Apply suggestions from code review Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane/templates/subroutines/trotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 52851c646ed..0c99c7f977b 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -62,7 +62,7 @@ def _recursive_expression(x, order, ops): class TrotterProduct(Operation): r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential - of a given hamiltonian. + of a given Hamiltonian. The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of hamiltonian expressed as a linear combination of terms which in general do not commute. Consider the hamiltonian From 725e34b9eaa2d4fbdd915253409f6e79927f6218 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 12 Oct 2023 10:46:56 -0400 Subject: [PATCH 12/34] lint --- pennylane/templates/subroutines/trotter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 0c99c7f977b..689ed96afc8 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -49,7 +49,7 @@ def _recursive_expression(x, order, ops): return [qml.exp(op, x * 1j) for op in ops] if order == 2: - return [qml.exp(op, x * 0.5j) for op in (ops + ops[::-1])] + return [qml.exp(op, x * 0.5j) for op in ops + ops[::-1]] scalar_1 = _scalar(order) scalar_2 = 1 - 4 * scalar_1 @@ -148,9 +148,9 @@ def my_circ(c1, c2, time): (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) """ - def __init__( + def __init__( # pylin: disable=too-many-arguments self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None - ): # pylin: disable=too-many-arguments + ): r"""Initialize the TrotterProduct class""" if order <= 0 or order != 1 and order % 2 != 0: From 9536b48a8fb4f7ad39dcd1e4fa5b63fd6563ed0c Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 12 Oct 2023 10:47:31 -0400 Subject: [PATCH 13/34] lint --- pennylane/templates/subroutines/trotter.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 689ed96afc8..0fb4bf535e4 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -95,7 +95,7 @@ class TrotterProduct(Operation): Raises: TypeError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. - ValueError: One or more of the terms in 'hamiltonian' are not Hermitian. + ValueError: One or more of the terms in 'hamiltonian' are not Hermitian. ValueError: The 'order' is not one or a positive even integer. **Example** @@ -125,21 +125,21 @@ def my_circ(): :title: Usage Details We can also compute the gradient with respect to the coefficients of the hamiltonian and the - evolution time: + evolution time: .. code-block:: python3 @qml.qnode(dev) - def my_circ(c1, c2, time): - # Prepare H: + def my_circ(c1, c2, time): + # Prepare H: H = qml.dot([c1, c2], [qml.PauliX(0), qml.PauliZ(1)]) - + # Prepare some state qml.Hadamard(0) - + # Evolve according to H qml.TrotterProduct(H, time, order=2) - + # Measure some quantity return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) From 7a5211ab2f946e7ed7bd2f29b743989d23d3181e Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:08:32 -0400 Subject: [PATCH 14/34] add template image --- .../templates/subroutines/trotter_product.png | Bin 0 -> 22515 bytes doc/introduction/templates.rst | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 doc/_static/templates/subroutines/trotter_product.png diff --git a/doc/_static/templates/subroutines/trotter_product.png b/doc/_static/templates/subroutines/trotter_product.png new file mode 100644 index 0000000000000000000000000000000000000000..d617bac2126c89557ac650217cb3c95564e1d28c GIT binary patch literal 22515 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 RX9#$&` :figure: _static/templates/subroutines/approx_time_evolution.png +.. gallery-item:: + :description: :doc:`TrotterProduct <../code/api/pennylane.TrotterProduct>` + :figure: _static/templates/subroutines/trotter_product.png + .. gallery-item:: :description: :doc:`Permute <../code/api/pennylane.Permute>` :figure: _static/templates/subroutines/permute.png From 8bb964efef158381c00a52db5f8fce4f3ec1348d Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:12:43 -0400 Subject: [PATCH 15/34] correct typos --- pennylane/templates/subroutines/trotter.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 0fb4bf535e4..b6cd4970f24 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -23,7 +23,7 @@ def _scalar(order): """Compute the scalar used in the recursive expression. Args: - order (int): order of trotter product (assume order is an even integer > 2). + order (int): order of Trotter product (assume order is an even integer > 2). Returns: float: scalar to be used in the recursive expression. @@ -35,7 +35,7 @@ def _scalar(order): @qml.QueuingManager.stop_recording() def _recursive_expression(x, order, ops): """Generate a list of operations using the - recursive expression which defines the trotter product. + recursive expression which defines the Trotter product. Args: x (complex): the evolution 'time' @@ -43,7 +43,7 @@ def _recursive_expression(x, order, ops): ops (Iterable(~.Operators)): a list of terms in the Hamiltonian Returns: - List: the approximation as product of exponentials of the hamiltonian terms + List: the approximation as product of exponentials of the Hamiltonian terms """ if order == 1: return [qml.exp(op, x * 1j) for op in ops] @@ -64,10 +64,10 @@ class TrotterProduct(Operation): r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential of a given Hamiltonian. - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of hamiltonian - expressed as a linear combination of terms which in general do not commute. Consider the hamiltonian + The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of Hamiltonian + expressed as a linear combination of terms which in general do not commute. Consider the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using symmetrized products of the terms - in the hamiltonian. The symmetrized products of order :math: `m \in [1, 2, 4, ..., 2k] | k \in \mathbb{N}` + in the Hamiltonian. The symmetrized products of order :math: `m \in [1, 2, 4, ..., 2k] | k \in \mathbb{N}` are given by: .. math:: @@ -124,7 +124,7 @@ def my_circ(): .. details:: :title: Usage Details - We can also compute the gradient with respect to the coefficients of the hamiltonian and the + We can also compute the gradient with respect to the coefficients of the Hamiltonian and the evolution time: .. code-block:: python3 @@ -171,7 +171,7 @@ def __init__( # pylin: disable=too-many-arguments for op in hamiltonian.operands: if not op.is_hermitian: raise ValueError( - "One or more of the terms in the Hamiltonian may not be hermitian" + "One or more of the terms in the Hamiltonian may not be Hermitian" ) self._hyperparameters = { From 03fbb87d822e294947c3454dacd74773d00289b5 Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:22:50 -0400 Subject: [PATCH 16/34] add and update args --- pennylane/templates/subroutines/trotter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index b6cd4970f24..50850d72098 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -86,12 +86,12 @@ class TrotterProduct(Operation): .. math:: exp(iHt) \approx (S_{2k}(\frac{t}{n}))^{n} Args: - hamiltonian (Union[~.Hamiltonian, ~.Sum]): - - Keyword Args: - n (int): An integer representing the number of Trotter steps to perform. - order (int): An integer representing the order of the approximation (must be 1 or even). - check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator. + hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written in terms of products of + Pauli gates + time (int or float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` + n (int): An integer representing the number of Trotter steps to perform + order (int): An integer representing the order of the approximation (must be 1 or even) + check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator Raises: TypeError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. From ff7738be6fe70fca44f68cd5ae13cdcbf381254a Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:25:20 -0400 Subject: [PATCH 17/34] fix pylint --- pennylane/templates/subroutines/trotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 50850d72098..3c874ea0384 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -148,7 +148,7 @@ def my_circ(c1, c2, time): (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) """ - def __init__( # pylin: disable=too-many-arguments + def __init__( # pylint: disable=too-many-arguments self, hamiltonian, time, n=1, order=1, check_hermitian=True, id=None ): r"""Initialize the TrotterProduct class""" From 78579ef0b548b70cb390c2db52fa4afeb4685112 Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:42:03 -0400 Subject: [PATCH 18/34] add reference --- pennylane/templates/subroutines/trotter.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 3c874ea0384..fdfb77737b0 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -85,6 +85,8 @@ class TrotterProduct(Operation): .. math:: exp(iHt) \approx (S_{2k}(\frac{t}{n}))^{n} + For more details see `J. Math. Phys. 32, 400 (1991) `_). + Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written in terms of products of Pauli gates From 341e4a17bfab407ccd798b64c1027d5778ea4f14 Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 13:46:21 -0400 Subject: [PATCH 19/34] correct typo --- tests/templates/test_subroutines/test_trotter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index fc2e698cb5b..8ae924e7365 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -199,10 +199,10 @@ def test_error_type(self, hamiltonian, raise_error): ) def test_error_hermiticity(self, hamiltonian): """Test that an error is raised if any terms in - the hamiltonian are not hermitian and check_hermitian is True.""" + the Hamiltonian are not Hermitian and check_hermitian is True.""" with pytest.raises( - ValueError, match="One or more of the terms in the Hamiltonian may not be hermitian" + ValueError, match="One or more of the terms in the Hamiltonian may not be Hermitian" ): qml.TrotterProduct(hamiltonian, time=0.5) From fc9bacddc9038894d4d4480567cd97ee5e60b495 Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 14:01:55 -0400 Subject: [PATCH 20/34] correct docstring math --- pennylane/templates/subroutines/trotter.py | 32 ++++++++++------------ 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index fdfb77737b0..970242208e5 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -61,35 +61,33 @@ def _recursive_expression(x, order, ops): class TrotterProduct(Operation): - r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix exponential - of a given Hamiltonian. + r"""An operation representing the Suzuki-Trotter product approximation for the complex matrix + exponential of a given Hamiltonian. - The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of Hamiltonian - expressed as a linear combination of terms which in general do not commute. Consider the Hamiltonian - :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using symmetrized products of the terms - in the Hamiltonian. The symmetrized products of order :math: `m \in [1, 2, 4, ..., 2k] | k \in \mathbb{N}` - are given by: + The Suzuki-Trotter product formula provides a method to approximate the matrix exponential of + Hamiltonian expressed as a linear combination of terms which in general do not commute. Consider + the Hamiltonian :math:`H = \Sigma^{N}_{j=0} O_{j}`, the product formula is constructed using + symmetrized products of the terms in the Hamiltonian. The symmetrized products of order + :math:`m \in [1, 2, 4, ..., 2k] | k \in \mathbb{N}` are given by: .. math:: \begin{align} - S_{m=1}(t) &= \Pi_{j=0}^{N} \ exp(i t O_{j}) \\ - S_{m=2}(t) &= \Pi_{j=0}^{N} \ exp(i \frac{t}{2} O_{j}) \cdot \Pi_{j=N}^{0} \ exp(i \frac{t}{2} O_{j}) \\ - &\vdots - S_{m=2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2} - \end{align} + S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ + S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ + &\vdots \\ + S_{2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2} + \end{align}, - Where the coefficient is :math:`p_{2k} = \frac{1}{4 - \sqrt[2k - 1]{4}}`. + where the coefficient is :math:`p_{2k} = 1 / (4 - \sqrt[2k - 1]{4})`. The :math:`2k`th order, :math:`n`-step Suzuki-Trotter approximation is then defined as: - The :math:`2k`th order, :math:`n`-step Suzuki-Trotter approximation is then defined as: - - .. math:: exp(iHt) \approx (S_{2k}(\frac{t}{n}))^{n} + .. math:: e^{iHt} \approx \left [S_{2k}(t / n) \right ]^{n}. For more details see `J. Math. Phys. 32, 400 (1991) `_). Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written in terms of products of - Pauli gates + Pauli gates time (int or float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` n (int): An integer representing the number of Trotter steps to perform order (int): An integer representing the order of the approximation (must be 1 or even) From b20d1fba6f6a3159b795b00ca280f79f8081ea9a Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 14:18:49 -0400 Subject: [PATCH 21/34] modify docstring --- pennylane/templates/subroutines/trotter.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 970242208e5..50ec90da4dc 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -76,8 +76,8 @@ class TrotterProduct(Operation): S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ &\vdots \\ - S_{2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2} - \end{align}, + S_{2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2}, + \end{align} where the coefficient is :math:`p_{2k} = 1 / (4 - \sqrt[2k - 1]{4})`. The :math:`2k`th order, :math:`n`-step Suzuki-Trotter approximation is then defined as: @@ -94,9 +94,9 @@ class TrotterProduct(Operation): check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator Raises: - TypeError: The 'hamiltonian' is not of type ~.Hamiltonian, or ~.Sum. - ValueError: One or more of the terms in 'hamiltonian' are not Hermitian. - ValueError: The 'order' is not one or a positive even integer. + TypeError: The ``hamiltonian`` is not of type :class:`~.Hamiltonian`, or :class:`~.Sum` + ValueError: One or more of the terms in ``hamiltonian`` are not Hermitian + ValueError: The ``order`` is not one or a positive even integer **Example** From 42ac504e730425722b44a1701520e992f405df9f Mon Sep 17 00:00:00 2001 From: soranjh Date: Sat, 14 Oct 2023 14:21:51 -0400 Subject: [PATCH 22/34] fix pylint --- tests/templates/test_subroutines/test_trotter.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index 8ae924e7365..1b5d28286fa 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -14,16 +14,15 @@ """ Tests for the TrotterProduct template and helper functions. """ +# pylint: disable=private-access, protected-access import copy -import pytest from functools import reduce +import pytest + import pennylane as qml -from pennylane.templates.subroutines.trotter import ( - _recursive_expression, - _scalar, -) # pylint: disable=private-access from pennylane import numpy as qnp +from pennylane.templates.subroutines.trotter import _recursive_expression, _scalar test_hamiltonians = ( qml.dot([1, 1, 1], [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(1)]), From 946d6ad9513381b8a5f25f392702fd8276ea9060 Mon Sep 17 00:00:00 2001 From: soranjh Date: Mon, 16 Oct 2023 16:47:54 -0400 Subject: [PATCH 23/34] modify doc and image --- .../templates/subroutines/trotter_product.png | Bin 22515 -> 13037 bytes pennylane/templates/subroutines/trotter.py | 7 +- .../test_subroutines/test_trotter.py | 250 +++++++++--------- 3 files changed, 130 insertions(+), 127 deletions(-) diff --git a/doc/_static/templates/subroutines/trotter_product.png b/doc/_static/templates/subroutines/trotter_product.png index d617bac2126c89557ac650217cb3c95564e1d28c..7c8dc33e32edeb2a014ce64475556ddf52202a35 100644 GIT binary patch literal 13037 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 RX9#$&`_). + For more details see `J. Math. Phys. 32, 400 (1991) `_. Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written in terms of products of diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index 1b5d28286fa..b0c34286409 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -36,130 +36,132 @@ p_4 = (4 - 4 ** (1 / 3)) ** -1 -test_decompositions = { # (hamiltonian_index, order): decomposition assuming t = 4.2 - (0, 1): [ - qml.exp(qml.PauliX(0), 4.2j), - qml.exp(qml.PauliY(0), 4.2j), - qml.exp(qml.PauliZ(1), 4.2j), - ], - (0, 2): [ - qml.exp(qml.PauliX(0), 4.2j / 2), - qml.exp(qml.PauliY(0), 4.2j / 2), - qml.exp(qml.PauliZ(1), 4.2j / 2), - qml.exp(qml.PauliZ(1), 4.2j / 2), - qml.exp(qml.PauliY(0), 4.2j / 2), - qml.exp(qml.PauliX(0), 4.2j / 2), - ], - (0, 4): [ - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 - qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), # S_2((1 - 4p) * t) - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), - qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), - qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 - ], - (1, 1): [ - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j), - ], - (1, 2): [ - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), - ], - (1, 4): [ - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), - qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), - ], - (2, 1): [ - qml.exp(qml.Identity(wires=[0, 1]), 4.2j), - qml.exp(qml.PauliZ(0), -0.5 * 4.2j), - qml.exp(qml.PauliZ(0), 0.5 * 4.2j), - ], - (2, 2): [ - qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), - qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), - ], - (2, 4): [ - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), - qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), - qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), - ], -} +test_decompositions = ( + { # (hamiltonian_index, order): decomposition assuming t = 4.2, computed by hand + (0, 1): [ + qml.exp(qml.PauliX(0), 4.2j), + qml.exp(qml.PauliY(0), 4.2j), + qml.exp(qml.PauliZ(1), 4.2j), + ], + (0, 2): [ + qml.exp(qml.PauliX(0), 4.2j / 2), + qml.exp(qml.PauliY(0), 4.2j / 2), + qml.exp(qml.PauliZ(1), 4.2j / 2), + qml.exp(qml.PauliZ(1), 4.2j / 2), + qml.exp(qml.PauliY(0), 4.2j / 2), + qml.exp(qml.PauliX(0), 4.2j / 2), + ], + (0, 4): [ + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(1), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliY(0), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliX(0), (1 - 4 * p_4) * 4.2j / 2), # S_2((1 - 4p) * t) + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(1), p_4 * 4.2j / 2), + qml.exp(qml.PauliY(0), p_4 * 4.2j / 2), + qml.exp(qml.PauliX(0), p_4 * 4.2j / 2), # S_2(p * t) ^ 2 + ], + (1, 1): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j), + ], + (1, 2): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), 1.23 * 4.2j / 2), + ], + (1, 4): [ + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), (1 - 4 * p_4) * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), (1 - 4 * p_4) * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.prod(qml.PauliX(0), qml.PauliZ(1)), p_4 * -0.45 * 4.2j / 2), + qml.exp(qml.s_prod(0.1, qml.PauliX(0)), p_4 * 1.23 * 4.2j / 2), + ], + (2, 1): [ + qml.exp(qml.Identity(wires=[0, 1]), 4.2j), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j), + ], + (2, 2): [ + qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), 4.2j / 2), + ], + (2, 4): [ + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), (1 - 4 * p_4) * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), (1 - 4 * p_4) * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * 0.5 * 4.2j / 2), + qml.exp(qml.PauliZ(0), p_4 * -0.5 * 4.2j / 2), + qml.exp(qml.Identity(wires=[0, 1]), p_4 * 4.2j / 2), + ], + } +) class TestInitialization: From 5c3fddae2e0387a2a8b5e7b2681ac07c49986551 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 18 Oct 2023 14:40:47 -0400 Subject: [PATCH 24/34] Adding Interface and Gradient tests to `TrotterProduct` (#4677) **Context:** This PR adds additional tests to the `TrotterProduct` class to ensure it integrates well with all interfaces and is fully differentiable. **Description of the Change:** - Add tests for interface execution - Add tests for interface gradient computation --------- Co-authored-by: soranjh --- pennylane/templates/subroutines/trotter.py | 7 +- .../test_subroutines/test_trotter.py | 374 +++++++++++++++++- 2 files changed, 378 insertions(+), 3 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index d1f201bbe82..5fb7a0e7542 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -161,6 +161,11 @@ def __init__( # pylint: disable=too-many-arguments if isinstance(hamiltonian, qml.Hamiltonian): coeffs, ops = hamiltonian.terms() + if len(coeffs) < 2: + raise ValueError( + "There should be atleast 2 terms in the Hamiltonian. Otherwise use `qml.exp`" + ) + hamiltonian = qml.dot(coeffs, ops) if not isinstance(hamiltonian, Sum): @@ -276,7 +281,7 @@ def compute_decomposition(*args, **kwargs): order = kwargs["order"] ops = kwargs["base"].operands - decomp = _recursive_expression(time / n, order, ops)[-1::-1] * n + decomp = _recursive_expression(time / n, order, ops)[::-1] * n if qml.QueuingManager.recording(): for op in decomp: # apply operators in reverse order of expression diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index b0c34286409..0cbc7f3e93a 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -22,6 +22,7 @@ import pennylane as qml from pennylane import numpy as qnp +from pennylane.math import allclose, get_interface from pennylane.templates.subroutines.trotter import _recursive_expression, _scalar test_hamiltonians = ( @@ -164,6 +165,43 @@ ) +def _generate_simple_decomp(coeffs, ops, time, order, n): + """Given coeffs, ops and a time argument in a given framework, generate the + Trotter product for order and number of trotter steps.""" + decomp = [] + if order == 1: + decomp.extend(qml.exp(op, coeff * (time / n) * 1j) for coeff, op in zip(coeffs, ops)) + + coeffs_ops = zip(coeffs, ops) + + if get_interface(coeffs) == "torch": + import torch + + coeffs_ops_reversed = zip(torch.flip(coeffs, dims=(0,)), ops[::-1]) + else: + coeffs_ops_reversed = zip(coeffs[::-1], ops[::-1]) + + if order == 2: + decomp.extend(qml.exp(op, coeff * (time / n) * 1j / 2) for coeff, op in coeffs_ops) + decomp.extend(qml.exp(op, coeff * (time / n) * 1j / 2) for coeff, op in coeffs_ops_reversed) + + if order == 4: + s_2 = [] + s_2_p = [] + + for coeff, op in coeffs_ops: + s_2.append(qml.exp(op, (p_4 * coeff) * (time / n) * 1j / 2)) + s_2_p.append(qml.exp(op, ((1 - (4 * p_4)) * coeff) * (time / n) * 1j / 2)) + + for coeff, op in coeffs_ops_reversed: + s_2.append(qml.exp(op, (p_4 * coeff) * (time / n) * 1j / 2)) + s_2_p.append(qml.exp(op, ((1 - (4 * p_4)) * coeff) * (time / n) * 1j / 2)) + + decomp = (s_2 * 2) + s_2_p + (s_2 * 2) + + return decomp * n + + class TestInitialization: """Test the TrotterProduct class initializes correctly.""" @@ -212,6 +250,11 @@ def test_error_hermiticity(self, hamiltonian): except ValueError: assert False # No error should be raised if the check_hermitian flag is disabled + def test_error_hamiltonian(self): + """Test that an error is raised if the input hamultonian has only 1 term.""" + with pytest.raises(ValueError, match="There should be atleast 2 terms in the Hamiltonian."): + qml.TrotterProduct(qml.Hamiltonian([1.0], [qml.PauliX(0)]), 1.23, n=2, order=4) + @pytest.mark.parametrize("order", (-1, 0, 0.5, 3, 7.0)) def test_error_order(self, order): """Test that an error is raised if 'order' is not one or positive even number.""" @@ -363,7 +406,7 @@ def test_compute_decomposition(self, hamiltonian, hamiltonian_index, order): decomp = [qml.simplify(op) for op in decomp] true_decomp = [ - qml.simplify(op) for op in test_decompositions[(hamiltonian_index, order)][-1::-1] + qml.simplify(op) for op in test_decompositions[(hamiltonian_index, order)][::-1] ] assert all( qml.equal(op1, op2) for op1, op2 in zip(decomp, true_decomp) @@ -400,6 +443,7 @@ class TestIntegration: """Test that the TrotterProduct can be executed and differentiated through all interfaces.""" + # Circuit execution tests: @pytest.mark.parametrize("order", (1, 2, 4)) @pytest.mark.parametrize("hamiltonian_index, hamiltonian", enumerate(test_hamiltonians)) def test_execute_circuit(self, hamiltonian, hamiltonian_index, order): @@ -464,9 +508,335 @@ def circ(): expected_state = ( reduce( - lambda x, y: x @ y, [qml.matrix(op, wire_order=wires) for op in true_decomp[-1::-1]] + lambda x, y: x @ y, [qml.matrix(op, wire_order=wires) for op in true_decomp[::-1]] ) @ initial_state ) state = circ() assert qnp.allclose(expected_state, state) + + @pytest.mark.jax + @pytest.mark.parametrize("time", (0.5, 1, 2)) + def test_jax_execute(self, time): + """Test that the gate executes correctly in the jax interface.""" + from jax import numpy as jnp + + time = jnp.array(time) + coeffs = jnp.array([1.23, -0.45]) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=2, order=2) + return qml.state() + + initial_state = jnp.array([1.0, 0.0, 0.0, 0.0]) + + expected_product_sequence = _generate_simple_decomp(coeffs, terms, time, order=2, n=2) + + expected_state = ( + reduce( + lambda x, y: x @ y, + [qml.matrix(op, wire_order=range(2)) for op in expected_product_sequence], + ) + @ initial_state + ) + + state = circ(time, coeffs) + assert allclose(expected_state, state) + + @pytest.mark.jax + @pytest.mark.parametrize("time", (0.5, 1, 2)) + def test_jaxjit_execute(self, time): + """Test that the gate executes correctly in the jax interface with jit.""" + import jax + from jax import numpy as jnp + + time = jnp.array(time) + c1 = jnp.array(1.23) + c2 = jnp.array(-0.45) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=2) + + @jax.jit + @qml.qnode(dev, interface="jax") + def circ(time, c1, c2): + h = qml.sum( + qml.s_prod(c1, terms[0]), + qml.s_prod(c2, terms[1]), + ) + qml.TrotterProduct(h, time, n=2, order=2, check_hermitian=False) + return qml.state() + + initial_state = jnp.array([1.0, 0.0, 0.0, 0.0]) + + expected_product_sequence = _generate_simple_decomp([c1, c2], terms, time, order=2, n=2) + + expected_state = ( + reduce( + lambda x, y: x @ y, + [qml.matrix(op, wire_order=range(2)) for op in expected_product_sequence], + ) + @ initial_state + ) + + state = circ(time, c1, c2) + assert allclose(expected_state, state) + + @pytest.mark.tf + @pytest.mark.parametrize("time", (0.5, 1, 2)) + def test_tf_execute(self, time): + """Test that the gate executes correctly in the tensorflow interface.""" + import tensorflow as tf + + time = tf.Variable(time, dtype=tf.complex128) + coeffs = tf.Variable([1.23, -0.45], dtype=tf.complex128) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.sum( + qml.s_prod(coeffs[0], terms[0]), + qml.s_prod(coeffs[1], terms[1]), + ) + qml.TrotterProduct(h, time, n=2, order=2) + + return qml.state() + + initial_state = tf.Variable([1.0, 0.0, 0.0, 0.0], dtype=tf.complex128) + + expected_product_sequence = _generate_simple_decomp(coeffs, terms, time, order=2, n=2) + + expected_state = tf.linalg.matvec( + reduce( + lambda x, y: x @ y, + [qml.matrix(op, wire_order=range(2)) for op in expected_product_sequence], + ), + initial_state, + ) + + state = circ(time, coeffs) + assert allclose(expected_state, state) + + @pytest.mark.torch + @pytest.mark.parametrize("time", (0.5, 1, 2)) + def test_torch_execute(self, time): + """Test that the gate executes correctly in the torch interface.""" + import torch + + time = torch.tensor(time, dtype=torch.complex64, requires_grad=True) + coeffs = torch.tensor([1.23, -0.45], dtype=torch.complex64, requires_grad=True) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=2, order=2) + return qml.state() + + initial_state = torch.tensor([1.0, 0.0, 0.0, 0.0], dtype=torch.complex64) + + expected_product_sequence = _generate_simple_decomp(coeffs, terms, time, order=2, n=2) + + expected_state = ( + reduce( + lambda x, y: x @ y, + [qml.matrix(op, wire_order=range(2)) for op in expected_product_sequence], + ) + @ initial_state + ) + + state = circ(time, coeffs) + assert allclose(expected_state, state) + + @pytest.mark.autograd + @pytest.mark.parametrize("time", (0.5, 1, 2)) + def test_autograd_execute(self, time): + """Test that the gate executes correctly in the autograd interface.""" + time = qnp.array(time) + coeffs = qnp.array([1.23, -0.45]) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=2, order=2) + return qml.state() + + initial_state = qnp.array([1.0, 0.0, 0.0, 0.0]) + + expected_product_sequence = _generate_simple_decomp(coeffs, terms, time, order=2, n=2) + + expected_state = ( + reduce( + lambda x, y: x @ y, + [qml.matrix(op, wire_order=range(2)) for op in expected_product_sequence], + ) + @ initial_state + ) + + state = circ(time, coeffs) + assert qnp.allclose(expected_state, state) + + @pytest.mark.autograd + @pytest.mark.parametrize("order, n", ((1, 1), (1, 2), (2, 1), (4, 1))) + def test_autograd_gradient(self, order, n): + """Test that the gradient is computed correctly""" + time = qnp.array(1.5) + coeffs = qnp.array([1.23, -0.45]) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=n, order=order) + return qml.expval(qml.Hadamard(0)) + + @qml.qnode(dev) + def reference_circ(time, coeffs): + with qml.QueuingManager.stop_recording(): + decomp = _generate_simple_decomp(coeffs, terms, time, order, n) + + for op in decomp[::-1]: + qml.apply(op) + + return qml.expval(qml.Hadamard(0)) + + measured_time_grad, measured_coeff_grad = qml.grad(circ)(time, coeffs) + reference_time_grad, reference_coeff_grad = qml.grad(reference_circ)(time, coeffs) + assert allclose(measured_time_grad, reference_time_grad) + assert allclose(measured_coeff_grad, reference_coeff_grad) + + @pytest.mark.torch + @pytest.mark.parametrize("order, n", ((1, 1), (1, 2), (2, 1), (4, 1))) + def test_torch_gradient(self, order, n): + """Test that the gradient is computed correctly using torch""" + import torch + + time = torch.tensor(1.5, dtype=torch.complex64, requires_grad=True) + coeffs = torch.tensor([1.23, -0.45], dtype=torch.complex64, requires_grad=True) + time_reference = torch.tensor(1.5, dtype=torch.complex64, requires_grad=True) + coeffs_reference = torch.tensor([1.23, -0.45], dtype=torch.complex64, requires_grad=True) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=n, order=order) + return qml.expval(qml.Hadamard(0)) + + @qml.qnode(dev) + def reference_circ(time, coeffs): + with qml.QueuingManager.stop_recording(): + decomp = _generate_simple_decomp(coeffs, terms, time, order, n) + + for op in decomp[::-1]: + qml.apply(op) + + return qml.expval(qml.Hadamard(0)) + + res_circ = circ(time, coeffs) + res_circ.backward() + measured_time_grad = time.grad + measured_coeff_grad = coeffs.grad + + ref_circ = reference_circ(time_reference, coeffs_reference) + ref_circ.backward() + reference_time_grad = time_reference.grad + reference_coeff_grad = coeffs_reference.grad + + assert allclose(measured_time_grad, reference_time_grad) + assert allclose(measured_coeff_grad, reference_coeff_grad) + + @pytest.mark.tf + @pytest.mark.parametrize("order, n", ((1, 1), (1, 2), (2, 1), (4, 1))) + def test_tf_gradient(self, order, n): + """Test that the gradient is computed correctly using tensorflow""" + import tensorflow as tf + + time = tf.Variable(1.5, dtype=tf.complex128) + coeffs = tf.Variable([1.23, -0.45], dtype=tf.complex128) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.sum( + qml.s_prod(coeffs[0], terms[0]), + qml.s_prod(coeffs[1], terms[1]), + ) + qml.TrotterProduct(h, time, n=n, order=order) + return qml.expval(qml.Hadamard(0)) + + @qml.qnode(dev) + def reference_circ(time, coeffs): + with qml.QueuingManager.stop_recording(): + decomp = _generate_simple_decomp(coeffs, terms, time, order, n) + + for op in decomp[::-1]: + qml.apply(op) + + return qml.expval(qml.Hadamard(0)) + + with tf.GradientTape() as tape: + result = circ(time, coeffs) + + measured_time_grad, measured_coeff_grad = tape.gradient(result, (time, coeffs)) + + with tf.GradientTape() as tape: + result = reference_circ(time, coeffs) + + reference_time_grad, reference_coeff_grad = tape.gradient(result, (time, coeffs)) + assert allclose(measured_time_grad, reference_time_grad) + assert allclose(measured_coeff_grad, reference_coeff_grad) + + @pytest.mark.jax + @pytest.mark.parametrize("order, n", ((1, 1), (1, 2), (2, 1), (4, 1))) + def test_jax_gradient(self, order, n): + """Test that the gradient is computed correctly""" + import jax + from jax import numpy as jnp + + time = jnp.array(1.5) + coeffs = jnp.array([1.23, -0.45]) + terms = [qml.PauliX(0), qml.PauliZ(0)] + + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circ(time, coeffs): + h = qml.dot(coeffs, terms) + qml.TrotterProduct(h, time, n=n, order=order) + return qml.expval(qml.Hadamard(0)) + + @qml.qnode(dev) + def reference_circ(time, coeffs): + with qml.QueuingManager.stop_recording(): + decomp = _generate_simple_decomp(coeffs, terms, time, order, n) + + for op in decomp[::-1]: + qml.apply(op) + + return qml.expval(qml.Hadamard(0)) + + measured_time_grad, measured_coeff_grad = jax.grad(circ, argnums=[0, 1])(time, coeffs) + reference_time_grad, reference_coeff_grad = jax.grad(reference_circ, argnums=[0, 1])( + time, coeffs + ) + assert allclose(measured_time_grad, reference_time_grad) + assert allclose(measured_coeff_grad, reference_coeff_grad) From 6f8ae419e6cd78d43574d8fe0b4c0baeb7119767 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 18 Oct 2023 14:44:10 -0400 Subject: [PATCH 25/34] update changelog --- doc/releases/changelog-dev.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index e0ce840e73f..fc68a659d2d 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,6 +4,31 @@

New features since last release

+* Support Hamiltonian exponentiation with Suzuki-Trotter products. + [(#4661)](https://github.com/PennyLaneAI/pennylane/pull/4661) + [(#4677)](https://github.com/PennyLaneAI/pennylane/pull/4677) + + The new `TrotterProduct` template can be used to approximately exponentiate a Hamiltonian written + as a sum of operations. + + ```python + H = qml.dot([0.25, 0.75], [qml.PauliX(0), qml.PauliZ(1)]) + + dev = qml.device("default.qubit", wires=2) + @qml.qnode(dev) + def circuit(): + qml.TrotterProduct(H, time=2.4, order=2) + return qml.state() + + draw = qml.draw(circuit, expansion_strategy='device')() + ``` + + ```pycon + >>> print(draw) + 0: ──Exp(0.00+1.20j 0.25*X)──Exp(0.00+1.20j 0.25*X)─┤ ╭State + 1: ──Exp(0.00+1.20j 0.75*Z)──Exp(0.00+1.20j 0.75*Z)─┤ ╰State + ``` + * Support drawing QJIT QNode from Catalyst. [(#4609)](https://github.com/PennyLaneAI/pennylane/pull/4609) From f6919863316551cb5cfbc2414a6c075efa524c6c Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 18 Oct 2023 14:54:18 -0400 Subject: [PATCH 26/34] update changelog --- doc/releases/changelog-dev.md | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index fc68a659d2d..b9aad1ae2c3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -4,31 +4,11 @@

New features since last release

-* Support Hamiltonian exponentiation with Suzuki-Trotter products. +* Higher-order Trotter-Suzuki methods are now easily accessible through a new operation called + `TrotterProduct`. [(#4661)](https://github.com/PennyLaneAI/pennylane/pull/4661) [(#4677)](https://github.com/PennyLaneAI/pennylane/pull/4677) - The new `TrotterProduct` template can be used to approximately exponentiate a Hamiltonian written - as a sum of operations. - - ```python - H = qml.dot([0.25, 0.75], [qml.PauliX(0), qml.PauliZ(1)]) - - dev = qml.device("default.qubit", wires=2) - @qml.qnode(dev) - def circuit(): - qml.TrotterProduct(H, time=2.4, order=2) - return qml.state() - - draw = qml.draw(circuit, expansion_strategy='device')() - ``` - - ```pycon - >>> print(draw) - 0: ──Exp(0.00+1.20j 0.25*X)──Exp(0.00+1.20j 0.25*X)─┤ ╭State - 1: ──Exp(0.00+1.20j 0.75*Z)──Exp(0.00+1.20j 0.75*Z)─┤ ╰State - ``` - * Support drawing QJIT QNode from Catalyst. [(#4609)](https://github.com/PennyLaneAI/pennylane/pull/4609) From 2f135278aca45fa006b492365dbc8cd607b4e6ce Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 18 Oct 2023 16:35:40 -0400 Subject: [PATCH 27/34] Apply suggestions from code review --- pennylane/templates/subroutines/trotter.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 5fb7a0e7542..65d02c41708 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -76,22 +76,22 @@ class TrotterProduct(Operation): S_{1}(t) &= \Pi_{j=0}^{N} \ e^{i t O_{j}} \\ S_{2}(t) &= \Pi_{j=0}^{N} \ e^{i \frac{t}{2} O_{j}} \cdot \Pi_{j=N}^{0} \ e^{i \frac{t}{2} O_{j}} \\ &\vdots \\ - S_{2k}(t) &= S_{2k-2}(p_{2k}t)^{2} \cdot S_{2k-2}((1-4p_{2k})t) \cdot S_{2k-2}(p_{2k}t)^{2}, + S_{m}(t) &= S_{m-2}(p_{m}t)^{2} \cdot S_{m-2}((1-4p_{m})t) \cdot S_{m-2}(p_{m}t)^{2}, \end{align} - where the coefficient is :math:`p_{2k} = 1 / (4 - \sqrt[2k - 1]{4})`. The :math:`2k`th order, + where the coefficient is :math:`p_{m} = 1 / (4 - \sqrt[m - 1]{4})`. The :math:`m`th order, :math:`n`-step Suzuki-Trotter approximation is then defined as: - .. math:: e^{iHt} \approx \left [S_{2k}(t / n) \right ]^{n}. + .. math:: e^{iHt} \approx \left [S_{m}(t / n) \right ]^{n}. For more details see `J. Math. Phys. 32, 400 (1991) `_. Args: - hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written in terms of products of - Pauli gates + hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written as a linear combination + of operators with known matrix exponentials. time (int or float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` n (int): An integer representing the number of Trotter steps to perform - order (int): An integer representing the order of the approximation (must be 1 or even) + order (int): An integer (m) representing the order of the approximation (must be 1 or even) check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator Raises: From d69fe6feda31694004a7b815575487857332b6d2 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 18 Oct 2023 16:38:42 -0400 Subject: [PATCH 28/34] Apply suggestions from code review Co-authored-by: soranjh <40344468+soranjh@users.noreply.github.com> --- pennylane/templates/subroutines/trotter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 65d02c41708..7bf0f42959c 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -39,11 +39,11 @@ def _recursive_expression(x, order, ops): Args: x (complex): the evolution 'time' - order (int): the order of the Trotter Expansion + order (int): the order of the Trotter expansion ops (Iterable(~.Operators)): a list of terms in the Hamiltonian Returns: - List: the approximation as product of exponentials of the Hamiltonian terms + list: the approximation as product of exponentials of the Hamiltonian terms """ if order == 1: return [qml.exp(op, x * 1j) for op in ops] From 8b974b83408da62279655b588c60bb7ceb345e08 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Wed, 18 Oct 2023 16:56:01 -0400 Subject: [PATCH 29/34] address review comments --- pennylane/templates/subroutines/trotter.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 7bf0f42959c..e370f8250e2 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -89,15 +89,16 @@ class TrotterProduct(Operation): Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written as a linear combination of operators with known matrix exponentials. - time (int or float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` + time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` n (int): An integer representing the number of Trotter steps to perform order (int): An integer (m) representing the order of the approximation (must be 1 or even) check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator Raises: - TypeError: The ``hamiltonian`` is not of type :class:`~.Hamiltonian`, or :class:`~.Sum` - ValueError: One or more of the terms in ``hamiltonian`` are not Hermitian - ValueError: The ``order`` is not one or a positive even integer + TypeError: The ``hamiltonian`` is not of type :class:`~.Hamiltonian`, or :class:`~.Sum`. + ValueError: The ``hamiltonian`` must have atleast two terms. + ValueError: One or more of the terms in ``hamiltonian`` are not Hermitian. + ValueError: The ``order`` is not one or a positive even integer. **Example** @@ -125,6 +126,7 @@ def my_circ(): .. details:: :title: Usage Details + One can recover the behaviour of :class:`~.ApproxTimeEvolution` by setting :code:`order=1`. We can also compute the gradient with respect to the coefficients of the Hamiltonian and the evolution time: From c0e744b865433ec94376684277c6238b7091eee5 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 18 Oct 2023 19:14:09 -0400 Subject: [PATCH 30/34] update changelog and image --- .../templates/subroutines/trotter_product.png | Bin 13037 -> 12896 bytes doc/releases/changelog-dev.md | 36 ++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/doc/_static/templates/subroutines/trotter_product.png b/doc/_static/templates/subroutines/trotter_product.png index 7c8dc33e32edeb2a014ce64475556ddf52202a35..b5fb79ff6adabbe474beee4744b6b83767985cbf 100644 GIT binary patch literal 12896 zcmc(mcUV*Hmi7ZuLK9E{1w||<3W8Dvq=O(x?~g zp(;HF|W3{_E*r6Ok}hd>}y&*Y@l zAP~a05D38sC<*wy7N&rOK)CNdla|nUGqFBJ7CW+Y*0Hr;rLYySmo{~1FzgG!_qB% zmu?0^$)hCSk4u)s4BTF%SeO|OPwE^?l+2-#xSrue)6(3yF_a&Xo0Fq6)$p3a?S@jy!E##Bn+EfqCq^&8i?k3{`ZYGs&YEG3u0Gc4R#sMna*i6l ziSB{RWstA6khjm|$6s9>vXI z*mErq`&y0QTSo^fO~kVVY@e0D!il9q)$FIA-_UV{TkcZ_Lg9f-7l{kX$_h43M@w`! zJTTMES`99i$iQ9fm=UV$Zu-?9zpa-d{5hMv6kj&I4Y8jyqqmxik{h}}@V8U^$5P}# z+)gUL!o;cYOG8d`iG}#`0&!;%mWDi%4$W3&5=p$isfmzGZFky=d*#4ELUu!(l_-uT zpjDKr z*WW(ll*rA^{Tir2hMO8x4!ehXS2X&F92q-IH$ELqM#*xQs30KY0-=Va3K_wD6H4Ke z%`djcd+RGmYxMyg{S+-z_PHcJ>k=Q_5rO^LVzT{OPK;9b*jQ}6|H{6}m!R>@xi3?g zn7hh-4vqJhrKm+>TgsNfDOur<%Db?bohcPO(HV7X2wtXJnTIe!iY}j7yj&CBsJmrB ze5s7kXLnH6wNG$q{?Kz}tjs7L>h{wlI9FS|{_OOaXR;JJUTLnJEb1*3RnBs2{=S^qJMo_{%!L6o5e`dixoYRz)DH=0^PfdGbL~1EK z9|w2oRa=eMIsRUobndUV8Y$8}++MJqYYQB0cx~;u*-DB4XLdv3&c!>=%cmOM^wreV z;yVP7ma_$~GQ^V8-8U66ZO0sSwbq&?p8zB*jWEunuUm1*c^~rd&#mN>-2@;JwRm*%I zp@c?F#+<)U379@xWZ=*-ww;4Y-Q;`HiHbymis zLb~|?NF0rZq^~^m{{Pv6ADL1DXr=$yEnw&U@dXxT_8WI&NTO&ysgnur2#0|Eq(;OR z8|Tn}n%c{63#*L%C3TyzNLte2G09(TwtxHipZAxx(QXqWQ)HWyv^y@G-kg$B5FObtGX{< zWZ`y)l=(e&EvyH#h{5-lG{x`18mgN2r#-JMnsOSoOoF5&dt18o5Cq7_S3BOJOB?(0 zlu{XYMTLmYUw+79WT?Ee*t`CgTGS!fSVmmAE1K2zU~|sr%zQ9QW^cWArlIp^xrx+% zZHxD@8rhD%fq}u%-2EFjT2BvtN7y7>Y68J-TuWS)c(UF(KaO3Svwx-oU52##GpFUA zt%C>G=vKbc4L*Nu5;Nnq^FgyvjTN`2h0v=HbNc3eyhlq$A-T{U$D>i#n=F9B?a!pI zpLw5bb-2z2G6d~jO%-;lsh#l=?w?TzB&V|hap217Xs$v6C8i7n{u*#}IL?IaCLf>g zAT~pfB6LsrZGM(#oK<@6+ckn|kn%aN`B^IXye>!`+q=zdLTH8I42g64%^uY=$3#cR zy#h1OSvlm!3_TGd`;*QlGX3z=!@J*ai15rnhYQv3CJVdS#UDR8JK5p4pMHX#K_X!e zt;G1`!XPMO)*aimZO|fU`91!D?LdY^jpfiChQtWbBa34&b(8DQo;~BY8fIe>bjqQ+ zCN#|KwJEWG43f`U%khQ+JOv*a=euGG1BhklazXN4iDQdh$o1S?>)P}L0;3X#1jFjZ z%-ntc(U@Jk^f}xPJF`-K)v=v6J}HTD(=m*J%Z`+q*+t9^GcAI{7!t?!C~#ovmza?e zbQE;u1F~(0J4=n$d~#N!C6?&1vi_PIzu8$?zryy{#)tBh)0sz|J?RFr`{q?zpjL7m zhD|>~Qp8IZ7urt+VWFH!__zcT-DJO!&tWdib=+)&+wqsQez~z+r+M7KL{mHtvaBzI>`5p;ePBsE@TIkX1$f~%N--Q-W1r<&qr{_b%PBZrj5L}qQb zwzg=6uNMEZove$})Bedm_cB7{m0}XFqqhZ^iPt!HkU7IshhJa#Y+n&GRMQwWxoxi@%2 z7sheCk8#VUKHbwGqbD8-E$Ikxc`FYNkPP&FZpy&eMdc?^hGZkqflM9uOxY+|Z>P*f z@2JuMHo~}p{m75?3Qsi%I^JJ-`IeOW{Ly2p`XXYA`zC3frG(^kk@z^I;u{y4sy7#w zs;4^p^Y4Tx5G5p7|2nPx+hNZb&~B?v)>dm{!ROMy_bp%fkjsdGE!OW3zW45xL!v+k zWRYT>=cR@`W;_S+dwAx+%m!)I_&4Ee8UPVzA%YOzfY3k*e8r&CUiBvJwUS={`KaUS zOSftF{MYEwUPb}C6!cR4dJSr=0bN>oQR(rGu?Xz2x^n&4c#SRU;}J)i`{EV-PcH%$ zQ-wypgbE2al>rXDZ0&QSkj{uRe#%7%Rk8pv>PjPzZjUCbU zx&lV?ePgN-Hmk1n(C6fh+ZOg1g9O1hh0kiZliBBB&OpovAF9tzakQQ|fhm`m#!|K1 zceul)^hZ1OyQ?GLVMcHqwseI_Zl=Y1>UD;=d&f1mIy7)lN6j#aft@~KOx;^%@R)8O zW)UyGz9SI+xzKqlq6%E;|Mvb$40=gjoNCXYehY}%hOwg0*VyF~IBjn|eP0eTkgk(c z#p~sP*6#QR$#X1wzrwim@|98vMT10ZW0ZutK-h>|->J5r5ncABga$ma+*=#p&@-w5 zxq@pJ=dr(G3sRa#X&U+)lYLd|ThgU*Pr<|a7#NF&F?96#uI!ofh^DsLK{E3#9b`)W zpN1*~nGV!43gjW1<$=sSpeX8IA?#^lDLCss!>lvf)4_^0pnG&%#BRxkyG-36BO~*A z1YR)AKPFF|l8L_u_5_%#N>5Dln8)7QiVrnE8&WpI1*)ag+u$qJcp+9&&MIHGPZvHs ztRdj!xj*IBIQtt7l4I?4pMzH1B;L%8xvUoEA9t`ZWOMCI71 zB=q#&BW8j{M@JhtbW@q}w-#9D)AqKvx9=jok5=+IjoEa9SGT`&QKMg5@@kE%W%xX5 zc7ruhB7py^d9(FGc)e<9ZBX`l^*Pe>0a!2cA0VGQ`w8qgyRQ8ld>X;57)a#z5XtuN zfnHT7XY;NavbLD4SOL7vRKkz|P0Q&Ky7cUes%)JThN%6VlB_L`IxBfFG-or6hu^0yiISu=%y(z{<=n|+Bq!SBcjO?--skS*Wk zD|SHCe|50U{umh8c4Adxe#Ju7p+TnlkrV;GB~hT}Ia+U~NL=`!#9n8?3^V**mIwSS ze66%u)xcL=#~0XbQ;^A>y=^`BH?W@9&0gTg{v$x0=29<##5T0!vzBDi<5eRp|_Z;Y!^ybffz{huw07?pbkfjs_b zz7JBg1(sLCfgWkFzQz%2i#}`?9q5X=t1c+P(!&K-qj$40skR|N{#MtjzQ(}!$=#v* zq?Y3qx!+T~ELS@~BAX*LE4RESd+oJGxWDlSM!w0W24nB2!8WE3q|kuCSnexuFP_dG ziGl1pbGqiKX67l5K}>Yvmk{qkWJ!*@ulbG|N+ctjzR^Lj34O@uiR#0pn zls5Ji50SikA^A4ZY6Q`LjPid!Ab+|H{r&6LkF51T>tyV+m^)i!gv4uIWJ8nnuWsHx zgeMrF2eUig&(F`dhY+__RaJ%9NHm$B^}R~2Db-~R?YM80cb|_Ygs}P8=q0SQneAr8 zUyR}aFUdd5K_(8ur{ljkA>vQ}afbTqvD2*kJ!~C0G?r6;P~=T{zuQEp^$Ls zjJ)wI`{{>`Yt{Mlkz`$=wGJtzy1}A)o>{c6&(|^p5ols<%D{Fg$J+IL6#K@3k$34 zVD{{}uUTZOZRnoc=-uj1ya!^09)K28n3o;l)!>p&f11cW9e)-Y+_VS!0Ym$x*^`yQ z>|opRim$}2AQ$2(5clMCo+6VQg5?c}j40dMsi&D2i2T~J0X+kbv6m=Tb$KK9z`6BGqt?H?`Sy~*~%chV+Y^_Zda7@%c2 z0k#jZmoXrgxUj4j-wEHPd^Kh-tqfqqR7aM%~s7b;)%G8XGq7k#)cHa!hL z^KM+pOI`z6d*5?LCXBuw4e$^O;EQu4UE(-Dknd_2dy`QCWEUrAzu$^@@&Xtk&mb14 zzj0K#%Zgef2;S=;g;i9TeV+hRG?icl%-Y7@9mS-dfDAh0o&f7!QQ0qwYQgQH48-Dv zPN>CB90X);$1g)NXYUqkmz@_%{c62RB9uYN{NyLh$YUi}*N#cZrNA2yHoNZkOW=W( zAmwfcvOwt{tfOjc@yyEyxHC$4I=^ialOrCtO}+X3f!4jW;Wb(hz(eV`Zg@Mi_3P`S z9e0D*BbpmW9|We$L3;WL%+}S-7Ov*qXuMUNU7yZ)hS007m~;hM#vJMk)P;i?8F;xY zS{W>Hp>E>>FqODY#Eg+b=5Dol4LUg;Da=PkZes z3cuMj{(QU~R$wEEv#-w$ZRobSX$#a!snKRS{4}(%UlvB9w?*687DzGmy4G&$WV+Ok zu6}RATFOIIu|dn|Hq>7xDEtV-a&nJ!02@(YcnRnwwT_nBSavRpcOir}B`))x-d+Gw zy5eDAmFgpwGJmU8_|{j36(oCk0oQID z1sCNJe4;5s=hF*<-3GU|amV{^Z!a>=X{qdtLcTX*TF{1X zld2NW%>saj&?r<}kx1nQliH{w#&|^bSWXuZ5hg%r=Z7y#<;x#z{ zCjNYbM_aMx(-#S?l#Jh!P)*LPj+J4aF5m`FTEuzPHhR}7JHoFC|1odOX6D^RK+c;aD6(xX z?}Ox9A9j>-^U=pGU~lWXM*Un?etac1ow>(E{zR|dVKLD>=;Vo2gLSA*!D4^9z4Kzv zmS=Kao(1{43O3>=u-HfJ14SvOU*FN0#C-t_2pyll!uQIsBbgz4kp+5F8t-8mJA<$L zSh!8SPWXed0JkA~8|nkj4uRWa-9}f$_YLlTT~82Rr=EOuxa!>VsFaICBdHESrBF#> z(7?uK{Y4FtJu+|hfQKU3Fo9Vr(Qt8T*r0_~A-+!ZDGib8XW%TJjCpCg=AE)(wNpJe z>gzq8Bc7_#FGM6|pN=+J-BLwZQw1OGw<1TsKk$y__;5O z&ga+%#QF}H!3@8O`}WKe=IP$AK!L$g%$l(JsSKY-;h@~gK;~PDJ4sn{xZZ4^a-cv1 z@&4!~FU5;X~d|TFws>LC*BmCs(hH`iXmI`&l?h-`e%tqau=^q0LH_jHR-& zEkbq;X8F-5mW(WycTo5mstY-bePkfwG55Xe*UNjd&ZIco^_+VT{79fKaK1RH zOiG^NT*Laf^z1L%29ANkB6%eGRgBVAeua1PtfLZ|k(P%~d&y`Eb_2=i{`4*DDamu* zZS;rJBIF|)MPFuN*Afby@%8%`?_+sZ*DPB38Hl0&A&e6j4As?nxi~NI-bbug0$oZ@ zMJ)aSvgB&2AN>WPJ9=3-nx2!xj&_3_fpz5;lb83K971GR}O>wmv)OxsS1Glcj_}+BA!#6EW`? z*c0WsbwbvKH~Yzu_Q#HoMy^000mXWOLZJ)4*n9u{F$=hZ90>uqFE#%A&t*NsT+sB`U4C>+P*CvE zYV@B*O5J3A99cP@hp>)+iVD_gd9IoK#GQM_2Ld@AX*}aw)u;f6O%>Zvq*)N7!`brV zeb(lchtI)gQ3;5`ul9fT?kr;f%9!9W6mTrMUS3Uc_td|=W8g~j@zm2xTAykZz;Qb- zK1YLM%F%kU7NP=W*Vs$XWteh$1~7d0=2hURDnWgs14z`DxFc*OsE(^|Lb_NWENb+e z1_iiY8(>T}_Cqh;)dPY@>SzYI8x*h*LvZ9$n&(<(Ftd}`b?@E58sk<%Yf!NGd}jZ% zJhL&ve4@(Ib-jA5A)Q^T#KInlt4Rf=Coj1_Ze3PoH#NcRx%LQEWf_wSM1Aoip0Oc(lJsQrgGx4S?rI|G)= zt=S(WdM9KpD1`Ni9@~)9<9{(9h-o?QdoXbT80pkx3!VdhWfELKvFm^-yox3F0s3rX z30TYlb|_dCs5B{Ne%{o2v?MXrp^c1qPe`lOz!Ft!UkjvY2z4$^qJVu`pP)USb?VzU zkKho1EZYr!12yatuzyU%^61k{H|QwMOEWx(flD~tHEa#zYWWFj#wn3!Mmv`Xzu#V^c8S@jmM|TA0M{f2W2N#unTC=!`A$-dM=NB$((;C zrMZ+2Z$V4!c$D)>c=smpjUwUjgk3yUzt$6bSt*hG>$N9(H8v|3$}F}33PqKhe8D$x zjDj|Z0(Tf_xF|x8s8Yx5C3yntfpFhh)M#hRX*dOx6SX#8IdoCNeLhOo;9NG4cY^Xn zBb^>V`A+s**-!zKYRh%82(Z@LH(wPBz| z$ieK}+x}pSyqE%_clkVJy7yipic68qC?k#MnuW`j$90!-i^;lA!q8JdAnm4O`CC7b zoE`SVOU(PzYFyX#j+e6|NX)jb7Oy#gjH8-TVIh=$d5Domf&K})kg@a@!|r5VveFljRVf@XgP0|!5JS5GNzTx6s%tidId5Tt?-O(Ax_ zMNzQU-Qhy3Y{%f`*FhfxIr)ig_=V?iX28V0#YobpFu;V@G{y*QYmI`kZc0PU_t-A>hzQYG-+6C_lO z8nYNh(sB9+dUf?0K){6_Ne?)GrPHpPeopnt+AfcudvxGNR+th&KBf;}vDnVN2Nl$P zR$?f@dYbi32b2Zcj^Nm3PiG2t&7w=6=-NHtpekE-?XkmW%o9ocJkZ)VZ3QV1$$ZhT zCp(hYae3GTsy9aUVyj}2r7%kKIkK#6(9lnsXGJiNWzqFk?#eco=*gf5t@LaIvoqCLxdn4`mXkqGO7Sm`H(7Jn#kQ60jEDe{>a73celO;ti8qZ>e(rJYq$ z{uRCp&Ub%oVA;B?c`;gpRjk2cPyzHzC>TqQ|7hp&I&i=j<~>`0=X|1!4vhAQ`wg0f z=A$37POL`lCe&FjYlc0pB97X?aE*_2o4}p`&f=P2>=m=(fM-u}uYFX0lZpq#w8HgK z|H@^5^%yF-ZpdyFkV+%qtYSjN{^yfgn(0=z23UC>Tpn!Y)%m4D1L&EoTk23|4+s;D zIoe&H&bP?No($5!KyrskMrwo8th0M)ao`P#@$Nqqvil9lGWU!$xD&2n=`EF|F+EHZ z!AfBUyg3N>S6S2dQW>;AP+?sGQ0v3Nq>_%jT#)uHhu?6DQrQ4Dc2}A>VBfH>>@F^@ z>}U`c40RA^X}0P?b206o{mwa$?2yfm#$3#+M>ojweyB-^KS3&J0POm&yuUz!@@=)$j-b$9cA9Jdp zRI}i!qFO}o1z-QXvHW^FH87O&GdL(La&`er=TalJ&=Apg6`;BN`4vw!`>YEZGl!{B zS(){)$&>lNWW@nG6AIG&P`4Db~k7-nB1{|YW5#) zexVMoI?Jt5SEa7>a7^S1_8$fE%#K`Ee)U~}2W;g2@G}kuBg?0%2b7oZJdMf~v}=+2 z#*9@f?0Ehq9&}Gs^^2WN_S&jR>VQ2<2%lQ*9q#T)Ng+{oE_LY&50V@Co@@G4B**<3 zS$;IfgS8M|sz8q}=i3%hp^M4x?86k%tJxk0O>s4Pb4(nx!;KQfZ&=5I9%Z zvjF+evS>*`g)J|TeNL)i>ss8kwrcV&$kYL>_8D$_aqv~|Dy=3aLF=bgnt|a?kj@U; z=JsK8(m`=qi_^{}zF4Nh_9GCR9lq=v-ed!vKW+?zkyVVsLi)0xMPw@SQ_XcXr;<4n zU|pnpeze1}zbrqA+XV}}-W)!C$@o;OA|vkOX#uHffANF+E<%s5os3j2V;`@6M$F5{ zjJ~d+VZA0VK3tg{dOFeHi}Dp5o_}T@u&GFN^H~_hF7vKl^)^qLrD{Y#1G!*6xfS0h zexiM?i?a8x;IVJ==L-7`T1SdP)kTlXNa&3R6+W9*Bbo_qGGyf7$l!n zeN(gQ&F#BVgLB){!O>++G-1V|-WT<-5UXT5{L4n=e{iaPG0f!JLxmX7un(h8Tox~8 z&h6nnlXsq~V*8R!my;C13P`jwg@)vc<_|B<_3IyD+yyz(Qi(|?WdRR=R9Q$nU*NmS zT)2ZIsRV*pAL-M4ELcXrd}-I}7hXjQUFaHVXWY|-4ftn$IWNr7A+?rtnt>ywIC?Jx ze4C%aP?PAjDjBJ=W)G|r>eW1NVbemqz5#VqX&m$*yWUg&!s`~zL0q-SI}0f@5qP<@ z%05- zn3kZDlD&CFgKM-s^dd|RF)!nJwm^!_u{_Ndt!$zxE)LbK)r41xOAk@owH&?@=t-NR zsR=?>KQ02Oa-)Qc`pG7w^&Hp_FW!0(zxLH|8mwA@3viBDB0#^`Pbrxf%#kU>Eo#oHzU{^!58m;QI zow3~Mw5&9UJ0k;Cdt$@>S6Cz6KS6K(;FT^-Q%zOS4&%VX2Jy@qH;kbGmujdEuu&Dw z$VD4Vj8JN}Ua=sbk&1JDtVU2pe+JhBA{9&$eX&KQEdo0FE6yU9S)%!y+l;qBmigfy zUY7W2n4k+;2O#1boKYFXtVvw>CfT~NOdJ)Dy#s8ZN?b-HP0t|P`*K`6As%W-M|wDrxNhFMb_%T>t<8 literal 13037 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 features since last release -* Higher-order Trotter-Suzuki methods are now easily accessible through a new operation called - `TrotterProduct`. +

Exponentiate Hamiltonians with flexible Trotter products 🐖

+ +* Higher-order Trotter-Suzuki methods are now easily accessible through a new operation + called `TrotterProduct`. [(#4661)](https://github.com/PennyLaneAI/pennylane/pull/4661) - [(#4677)](https://github.com/PennyLaneAI/pennylane/pull/4677) + + Trotterization techniques are an affective route towards accurate and efficient + Hamiltonian simulation. The Suzuki-Trotter product formula allows for the ability + to express higher-order approximations to the matrix exponential of a Hamiltonian, + and it is now available to use in PennyLane via the `TrotterProduct` operation. + Simply specify the `order` of the approximation and the evolution `time`. + + ```python + coeffs = [0.25, 0.75] + ops = [qml.PauliX(0), qml.PauliZ(0)] + H = qml.dot(coeffs, ops) + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(): + qml.Hadamard(0) + qml.TrotterProduct(H, time=2.4, order=2) + return qml.state() + ``` + + ```pycon + >>> circuit() + [-0.13259524+0.59790098j 0. +0.j -0.13259524-0.77932754j 0. +0.j ] + ``` + + The already-available `ApproxTimeEvolution` operation represents the special case of `order=1`. + It is recommended to switch over to use of `TrotterProduct` because `ApproxTimeEvolution` will be + deprecated and removed in upcoming releases. * Support drawing QJIT QNode from Catalyst. [(#4609)](https://github.com/PennyLaneAI/pennylane/pull/4609) From 2ddf0ea34ed3ce0cfd683fca84937ffa401c3882 Mon Sep 17 00:00:00 2001 From: soranjh Date: Wed, 18 Oct 2023 19:20:01 -0400 Subject: [PATCH 31/34] apply code review comments --- pennylane/templates/subroutines/trotter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index e370f8250e2..6f8c1981e1f 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -1,4 +1,4 @@ -# Copyright 2018-2021 Xanadu Quantum Technologies Inc. +# Copyright 2018-2023 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. @@ -89,7 +89,7 @@ class TrotterProduct(Operation): Args: hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written as a linear combination of operators with known matrix exponentials. - time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{-iHt}` + time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` n (int): An integer representing the number of Trotter steps to perform order (int): An integer (m) representing the order of the approximation (must be 1 or even) check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator @@ -126,7 +126,7 @@ def my_circ(): .. details:: :title: Usage Details - One can recover the behaviour of :class:`~.ApproxTimeEvolution` by setting :code:`order=1`. + One can recover the behaviour of :class:`~.ApproxTimeEvolution` by setting :code:`order=1`. We can also compute the gradient with respect to the coefficients of the Hamiltonian and the evolution time: From 285f420cca0d12c3cc8154e70e67b17b722dd02c Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 19 Oct 2023 09:13:33 -0400 Subject: [PATCH 32/34] lint --- pennylane/templates/subroutines/trotter.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 6f8c1981e1f..81a8dfe329b 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -87,7 +87,7 @@ class TrotterProduct(Operation): For more details see `J. Math. Phys. 32, 400 (1991) `_. Args: - hamiltonian (Union[~.Hamiltonian, ~.Sum]): The Hamiltonian written as a linear combination + hamiltonian (Union[~Hamiltonian, ~Sum]): The Hamiltonian written as a linear combination of operators with known matrix exponentials. time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` n (int): An integer representing the number of Trotter steps to perform @@ -135,7 +135,7 @@ def my_circ(): @qml.qnode(dev) def my_circ(c1, c2, time): # Prepare H: - H = qml.dot([c1, c2], [qml.PauliX(0), qml.PauliZ(1)]) + H = qml.dot([c1, c2], [qml.PauliX(0), qml.PauliZ(0)]) # Prepare some state qml.Hadamard(0) @@ -146,7 +146,7 @@ def my_circ(c1, c2, time): # Measure some quantity return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) - >>> args = qnp.array([1.23, 4.5, 0.1]) + >>> args = np.array([1.23, 4.5, 0.1]) >>> qml.grad(my_circ)(*tuple(args)) (tensor(0.00961064, requires_grad=True), tensor(-0.12338274, requires_grad=True), tensor(-5.43401259, requires_grad=True)) """ From 198ecf620e65207d9b87ee3880b9f63aedb328aa Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 19 Oct 2023 09:53:42 -0400 Subject: [PATCH 33/34] lint --- pennylane/templates/subroutines/trotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 81a8dfe329b..3cd54d9788a 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -87,7 +87,7 @@ class TrotterProduct(Operation): For more details see `J. Math. Phys. 32, 400 (1991) `_. Args: - hamiltonian (Union[~Hamiltonian, ~Sum]): The Hamiltonian written as a linear combination + hamiltonian (Union[.Hamiltonian, .Sum]): The Hamiltonian written as a linear combination of operators with known matrix exponentials. time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` n (int): An integer representing the number of Trotter steps to perform From c08127eccb2579e2a3dec49f7e26cb13a479a755 Mon Sep 17 00:00:00 2001 From: Jay Soni Date: Thu, 19 Oct 2023 09:57:11 -0400 Subject: [PATCH 34/34] Update pennylane/templates/subroutines/trotter.py Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com> --- pennylane/templates/subroutines/trotter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 3cd54d9788a..f843994f5d6 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -91,7 +91,7 @@ class TrotterProduct(Operation): of operators with known matrix exponentials. time (float): The time of evolution, namely the parameter :math:`t` in :math:`e^{iHt}` n (int): An integer representing the number of Trotter steps to perform - order (int): An integer (m) representing the order of the approximation (must be 1 or even) + order (int): An integer (:math:`m`) representing the order of the approximation (must be 1 or even) check_hermitian (bool): A flag to enable the validation check to ensure this is a valid unitary operator Raises: