Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
dwierichs committed May 13, 2024
1 parent 64de143 commit 3b6e170
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 54 deletions.
12 changes: 0 additions & 12 deletions pennylane/ops/functions/bind_new_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,6 @@ def bind_new_parameters_approx_time_evolution(
return qml.ApproxTimeEvolution(new_hamiltonian, time, n)


@bind_new_parameters.register
def bind_new_parameters_commuting_evolution(
op: qml.CommutingEvolution, params: Sequence[TensorLike]
):
new_hamiltonian = bind_new_parameters(op.hyperparameters["hamiltonian"], params[1:])
freq = op.hyperparameters["frequencies"]
shifts = op.hyperparameters["shifts"]
time = params[0]

return qml.CommutingEvolution(new_hamiltonian, time, frequencies=freq, shifts=shifts)


@bind_new_parameters.register
def bind_new_parameters_fermionic_double_excitation(
op: qml.FermionicDoubleExcitation, params: Sequence[TensorLike]
Expand Down
24 changes: 18 additions & 6 deletions pennylane/ops/functions/equal.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
LinearCombination,
Pow,
SProd,
ScalarSymbolicOp,
)
from pennylane.pulse.parametrized_evolution import ParametrizedEvolution
from pennylane.tape import QuantumTape
Expand Down Expand Up @@ -323,9 +324,11 @@ def _equal_operators(
atol=1e-9,
):
"""Default function to determine whether two Operator objects are equal."""
print("here")
if not isinstance(
op2, type(op1)
): # clarifies cases involving PauliX/Y/Z (Observable/Operation)
print("oh no")
return False

if isinstance(op1, qml.Identity):
Expand All @@ -335,21 +338,26 @@ def _equal_operators(
return True

if op1.arithmetic_depth != op2.arithmetic_depth:
print("oh no2")
return False

if op1.arithmetic_depth > 0:
# Other dispatches cover cases of operations with arithmetic depth > 0.
# If any new operations are added with arithmetic depth > 0, a new dispatch
# should be created for them.
print("oh no3")
return False
if not all(
qml.math.allclose(d1, d2, rtol=rtol, atol=atol) for d1, d2 in zip(op1.data, op2.data)
):
print("oh no4")
return False
if op1.wires != op2.wires:
print("oh no5")
return False

if op1.hyperparameters != op2.hyperparameters:
print("oh no6")
return False

if check_trainability:
Expand Down Expand Up @@ -432,14 +440,18 @@ def _equal_adjoint(op1: Adjoint, op2: Adjoint, **kwargs):

@_equal.register
# pylint: disable=unused-argument
def _equal_exp(op1: Exp, op2: Exp, **kwargs):
def _equal_scalar_symbolic_op(op1: ScalarSymbolicOp, op2: ScalarSymbolicOp, **kwargs):
"""Determine whether two Exp objects are equal"""
rtol, atol = (kwargs["rtol"], kwargs["atol"])

if not qml.math.allclose(op1.coeff, op2.coeff, rtol=rtol, atol=atol):

if not qml.math.allclose(op1.scalar, op2.scalar, rtol=kwargs["rtol"], atol=kwargs["atol"]):
return False
return qml.equal(op1.base, op2.base)

if kwargs["check_trainability"]:
if qml.math.requires_grad(op1.scalar) != qml.math.requires_grad(op2.scalar):
return False
if kwargs["check_interface"]:
if qml.math.get_interface(op1.scalar) != qml.math.get_interface(op2.scalar):
return False
return qml.equal(op1.base, op2.base, **kwargs)

@_equal.register
# pylint: disable=unused-argument
Expand Down
97 changes: 61 additions & 36 deletions pennylane/templates/subroutines/commuting_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
"""
# pylint: disable-msg=too-many-arguments,import-outside-toplevel
import pennylane as qml
from pennylane.operation import AnyWires, Operation
from pennylane.operation import AnyWires, ParameterFrequenciesUndefinedError
from pennylane.ops.op_math import ScalarSymbolicOp


class CommutingEvolution(Operation):
class CommutingEvolution(ScalarSymbolicOp):
r"""Applies the time-evolution operator for a Hamiltonian expressed as a linear combination
of mutually commuting Pauli words.
Expand Down Expand Up @@ -105,10 +106,10 @@ def circuit(time):

num_wires = AnyWires
grad_method = None
_name = "CommutingEvolution"

def _flatten(self):
h = self.hyperparameters["hamiltonian"]
data = (self.data[0], h)
data = (self.data[0], self.base)
return data, (self.hyperparameters["frequencies"], self.hyperparameters["shifts"])

@classmethod
Expand All @@ -121,7 +122,7 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None):

if getattr(hamiltonian, "pauli_rep", None) is None:
raise TypeError(
f"hamiltonian must be a linear combination of pauli words. Got {hamiltonian}"
f"hamiltonian must be a linear combination of Pauli words. Got {hamiltonian}"
)

trainable_hamiltonian = qml.math.requires_grad(hamiltonian.data)
Expand All @@ -131,49 +132,73 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None):
self.grad_recipe = (recipe,) + (None,) * len(hamiltonian.data)
self.grad_method = "A"

self._hyperparameters = {
"hamiltonian": hamiltonian,
"frequencies": frequencies,
"shifts": shifts,
}
super().__init__(hamiltonian, scalar=time, id=id)
self.hyperparameters["frequencies"] = frequencies
self.hyperparameters["shifts"] = shifts

super().__init__(time, *hamiltonian.parameters, wires=hamiltonian.wires, id=id)
@property
def parameter_frequencies(self):
# TODO: Fix the following by exploiting the structure of CommutingEvolution
# Note that because of the coefficients of the Hamiltonian, we do not have
# parameter_frequencies even if "frequencies" are provided at initialization!
raise ParameterFrequenciesUndefinedError(
"CommutingEvolution only has no parameter frequencies defined."
)

def queue(self, context=qml.QueuingManager):
context.remove(self.hyperparameters["hamiltonian"])
context.append(self)
return self
@property
def _queue_category(self):
return "_ops"

@staticmethod
def compute_decomposition(
time, *_, wires, hamiltonian, **__
): # pylint: disable=arguments-differ,unused-argument
r"""Representation of the operator as a product of other operators.
def _matrix(scalar, mat):
return qml.math.expm(-1j * scalar * mat)

.. math:: O = O_1 O_2 \dots O_n.
# pylint: disable=invalid-overridden-method, arguments-renamed
@property
def has_decomposition(self):
return True

Args:
*time_and_coeffs (list[tensor_like or float]): list of coefficients of the Hamiltonian, prepended by the time
variable
wires (Any or Iterable[Any]): wires that the operator acts on
hamiltonian (.Hamiltonian): The commuting Hamiltonian defining the time-evolution operator.
frequencies (tuple[int or float]): The unique positive differences between eigenvalues in
the spectrum of the Hamiltonian.
shifts (tuple[int or float]): The parameter shifts to use in obtaining the
generalized parameter shift rules. If unspecified, equidistant shifts are used.
.. seealso:: :meth:`~.CommutingEvolution.decomposition`.
def decomposition(self):
r"""Representation of the operator as an :class:`~.ApproxTimeEvolution`.
Returns:
list[.Operator]: decomposition of the operator
list[ApproxTimeEvolution]: decomposition of the operator
"""
# uses standard PauliRot decomposition through ApproxTimeEvolution.
return [qml.ApproxTimeEvolution(hamiltonian, time, 1)]
return [qml.ApproxTimeEvolution(self.base, self.scalar, 1)]

def adjoint(self):
hamiltonian = self.hyperparameters["hamiltonian"]
time = self.parameters[0]
frequencies = self.hyperparameters["frequencies"]
shifts = self.hyperparameters["shifts"]

return CommutingEvolution(hamiltonian, -time, frequencies, shifts)
return CommutingEvolution(self.base, -self.scalar, frequencies, shifts)

# pylint: disable=arguments-renamed,invalid-overridden-method
@property
def has_diagonalizing_gates(self):
return self.base.has_diagonalizing_gates

def diagonalizing_gates(self):
return self.base.diagonalizing_gates()

# pylint: disable=arguments-renamed, invalid-overridden-method
@property
def has_generator(self):
return self.base.is_hermitian and not np.real(self.coeff)

def generator(self):
r"""Generator of an operator that is in single-parameter-form.
For example, for operator
.. math::
U(\phi) = e^{i\phi (0.5 Y + Z\otimes X)}
we get the generator
>>> U.generator()
0.5 * Y(0) + Z(0) @ X(1)
"""
return -self.base
3 changes: 3 additions & 0 deletions tests/templates/test_subroutines/test_commuting_evolution.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import pennylane as qml
from pennylane import numpy as np
import copy


def test_standard_validity():
Expand Down Expand Up @@ -234,6 +235,8 @@ def circuit(time, coeffs):
return qml.expval(qml.PauliZ(0))

x_vals = [np.array(x, requires_grad=True) for x in np.linspace(-np.pi, np.pi, num=10)]
circuit(x_vals[0], diff_coeffs)
print(circuit.tape[1].grad_recipe)

grads_finite_diff = [
np.hstack(qml.gradients.finite_diff(circuit)(x, diff_coeffs)) for x in x_vals
Expand Down

0 comments on commit 3b6e170

Please sign in to comment.