Skip to content

Commit

Permalink
Adapting generators to opmath enabled/disabled (#5415)
Browse files Browse the repository at this point in the history
**Context:** After enabling the new operator arithmetic by default, we
want the generators in the source code to return a `LinearCombination`
instance or a `Hamiltonian` instance wherever possible.

**Description of the Change:** The generators touched in this PR are
modified so that they return `qml.Hamiltonian` instead of `Sum`, `Prod`,
or `Sprod` instances. When opmath is enabled, `qml.Hamiltonian` points
to `pennylane.ops.op_math.linear_combination.LinearCombination`, and
when it is disabled, it points to
`pennylane.ops.qubit.hamiltonian.Hamiltonian`. This ensures that the
appropriate instance is used consistently.
Note that the generators unchanged in this PR are modified (wherever
possible) in #5410 , #5411 , #5412 (including the changelog entry).

**Benefits:** A more coherent choice depending on whether opmath is
enabled or disabled.

**Possible Drawbacks:** None that I can think of, except that old opmath
would be deprecated in the future. Therefore, some precautions that have
been taken here (to ensure that tests associated with generators work
even with opmath disabled) might become useless.

**Related GitHub Issues:** None.

[sc-57982]

---------

Co-authored-by: Mudit Pandey <mudit.pandey@xanadu.ai>
Co-authored-by: lillian542 <Lillian.frederiksen@xanadu.ai>
Co-authored-by: qottmann <korbinian.kottmann@gmail.com>
Co-authored-by: Alex Preciado <alex.preciado@xanadu.ai>
  • Loading branch information
5 people authored Mar 21, 2024
1 parent 6966f7f commit c290f28
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 26 deletions.
3 changes: 0 additions & 3 deletions pennylane/gradients/hadamard_gradient.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,6 @@ def _get_generators(trainable_op):
elif isinstance(trainable_op, qml.Rot):
generators = [qml.Z(trainable_op.wires)]
coeffs = [-0.5]
elif isinstance(trainable_op, (qml.RX, qml.RY, qml.RZ)):
generators = [trainable_op.generator().base]
coeffs = [trainable_op.generator().scalar]
else:
generators = trainable_op.generator().ops
coeffs = trainable_op.generator().coeffs
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def has_generator(self):
return self.base.has_generator

def generator(self):
return qml.s_prod(-1.0, self.base.generator())
return -1 * self.base.generator()


class AdjointObs(Adjoint, Observable):
Expand Down
2 changes: 1 addition & 1 deletion pennylane/ops/op_math/pow.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def generator(self):
See also :func:`~.generator`
"""
return qml.s_prod(self.z, self.base.generator(), lazy=False)
return self.z * self.base.generator()

def pow(self, z):
return [Pow(base=self.base, z=self.z * z)]
Expand Down
26 changes: 15 additions & 11 deletions pennylane/ops/qubit/parametric_ops_multi_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def compute_matrix(theta, num_wires): # pylint: disable=arguments-differ
)

def generator(self):
return qml.s_prod(-0.5, functools.reduce(matmul, [PauliZ(w) for w in self.wires]))
return qml.Hamiltonian([-0.5], [functools.reduce(matmul, [PauliZ(w) for w in self.wires])])

@staticmethod
def compute_eigvals(theta, num_wires): # pylint: disable=arguments-differ
Expand Down Expand Up @@ -406,7 +406,10 @@ def compute_matrix(theta, pauli_word): # pylint: disable=arguments-differ
def generator(self):
pauli_word = self.hyperparameters["pauli_word"]
wire_map = {w: i for i, w in enumerate(self.wires)}
return qml.s_prod(-0.5, qml.pauli.string_to_pauli_word(pauli_word, wire_map=wire_map))

return qml.Hamiltonian(
[-0.5], [qml.pauli.string_to_pauli_word(pauli_word, wire_map=wire_map)]
)

@staticmethod
def compute_eigvals(theta, pauli_word): # pylint: disable=arguments-differ
Expand Down Expand Up @@ -774,7 +777,7 @@ class IsingXX(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, qml.prod(PauliX(wires=self.wires[0]), PauliX(wires=self.wires[1])))
return qml.Hamiltonian([-0.5], [PauliX(wires=self.wires[0]) @ PauliX(wires=self.wires[1])])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down Expand Up @@ -910,7 +913,7 @@ class IsingYY(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, qml.prod(PauliY(wires=self.wires[0]), PauliY(wires=self.wires[1])))
return qml.Hamiltonian([-0.5], [PauliY(wires=self.wires[0]) @ PauliY(wires=self.wires[1])])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down Expand Up @@ -1053,7 +1056,7 @@ class IsingZZ(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, qml.prod(PauliZ(wires=self.wires[0]), PauliZ(wires=self.wires[1])))
return qml.Hamiltonian([-0.5], [PauliZ(wires=self.wires[0]) @ PauliZ(wires=self.wires[1])])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down Expand Up @@ -1236,12 +1239,13 @@ class IsingXY(Operation):
parameter_frequencies = [(0.5, 1.0)]

def generator(self):
return qml.s_prod(
0.25,
qml.sum(
qml.prod(PauliX(wires=self.wires[0]), PauliX(wires=self.wires[1])),
qml.prod(PauliY(wires=self.wires[0]), PauliY(wires=self.wires[1])),
),

return qml.Hamiltonian(
[0.25, 0.25],
[
qml.X(wires=self.wires[0]) @ qml.X(wires=self.wires[1]),
qml.Y(wires=self.wires[0]) @ qml.Y(wires=self.wires[1]),
],
)

def __init__(self, phi, wires, id=None):
Expand Down
6 changes: 3 additions & 3 deletions pennylane/ops/qubit/parametric_ops_single_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class RX(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, PauliX(wires=self.wires))
return qml.Hamiltonian([-0.5], [PauliX(wires=self.wires)])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down Expand Up @@ -166,7 +166,7 @@ class RY(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, PauliY(wires=self.wires))
return qml.Hamiltonian([-0.5], [PauliY(wires=self.wires)])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down Expand Up @@ -261,7 +261,7 @@ class RZ(Operation):
parameter_frequencies = [(1,)]

def generator(self):
return qml.s_prod(-0.5, PauliZ(wires=self.wires))
return qml.Hamiltonian([-0.5], [PauliZ(wires=self.wires)])

def __init__(self, phi, wires, id=None):
super().__init__(phi, wires=wires, id=id)
Expand Down
3 changes: 2 additions & 1 deletion tests/ops/op_math/test_adjoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,13 @@ def test_has_generator_false(self):

assert op.has_generator is False

@pytest.mark.usefixtures("use_legacy_and_new_opmath")
def test_generator(self):
"""Assert that the generator of an Adjoint is -1.0 times the base generator."""
base = qml.RX(1.23, wires=0)
op = Adjoint(base)

assert qml.equal(base.generator(), qml.s_prod(-1.0, op.generator()))
assert qml.equal(base.generator(), -1.0 * op.generator())

def test_no_generator(self):
"""Test that an adjointed non-Operation raises a GeneratorUndefinedError."""
Expand Down
13 changes: 7 additions & 6 deletions tests/ops/qubit/test_parametric_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
MultiRZ as old_loc_MultiRZ,
)

from pennylane.ops.op_math.sprod import SProd

from pennylane.wires import Wires

PARAMETRIZED_OPERATIONS = [
Expand Down Expand Up @@ -236,6 +234,7 @@ def test_pcphase_raises_error(self):


class TestParameterFrequencies:
@pytest.mark.usefixtures("use_legacy_and_new_opmath")
@pytest.mark.parametrize("op", PARAMETRIZED_OPERATIONS)
def test_parameter_frequencies_match_generator(self, op, tol):
if not qml.operation.has_gen(op):
Expand Down Expand Up @@ -2974,12 +2973,13 @@ def test_init_incorrect_pauli_word_length_error(self, pauli_word, wires):
("IIIXYZ"),
],
)
@pytest.mark.usefixtures("use_legacy_and_new_opmath")
def test_multirz_generator(self, pauli_word):
"""Test that the generator of the MultiRZ gate is correct."""
op = qml.PauliRot(0.3, pauli_word, wires=range(len(pauli_word)))
gen = op.generator()

assert isinstance(gen, SProd)
assert isinstance(gen, qml.Hamiltonian)

if pauli_word[0] == "I":
# this is the identity
Expand All @@ -2994,7 +2994,7 @@ def test_multirz_generator(self, pauli_word):
else:
expected_gen = expected_gen @ getattr(qml, f"Pauli{pauli}")(wires=i)

assert qml.equal(gen, qml.s_prod(-0.5, expected_gen))
assert qml.equal(gen, qml.Hamiltonian([-0.5], [expected_gen]))

@pytest.mark.torch
@pytest.mark.gpu
Expand Down Expand Up @@ -3189,18 +3189,19 @@ def decomp_circuit(theta):
assert np.allclose(qml.jacobian(circuit)(angle), qml.jacobian(decomp_circuit)(angle))

@pytest.mark.parametrize("qubits", range(3, 6))
@pytest.mark.usefixtures("use_legacy_and_new_opmath")
def test_multirz_generator(self, qubits, mocker):
"""Test that the generator of the MultiRZ gate is correct."""
op = qml.MultiRZ(0.3, wires=range(qubits))
gen = op.generator()

assert isinstance(gen, SProd)
assert isinstance(gen, qml.Hamiltonian)

expected_gen = qml.PauliZ(wires=0)
for i in range(1, qubits):
expected_gen = expected_gen @ qml.PauliZ(wires=i)

assert qml.equal(gen, qml.s_prod(-0.5, expected_gen))
assert qml.equal(gen, qml.Hamiltonian([-0.5], [expected_gen]))

spy = mocker.spy(qml.utils, "pauli_eigs")

Expand Down

0 comments on commit c290f28

Please sign in to comment.