diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index bd94ad01217..3b07fae6cfa 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -38,6 +38,13 @@
Breaking changes
+Bug fixes
+
+* Fixed a bug where `QNGOptimizer` did not work with operators
+ whose generator was a Hamiltonian.
+ [(#2524)](https://github.com/PennyLaneAI/pennylane/pull/2524)
+
+
Deprecations
Documentation
@@ -46,4 +53,5 @@
This release contains contributions from (in alphabetical order):
-Utkarsh Azad, Christian Gogolin, Christina Lee
+Guillermo Alonso-Linaje, Utkarsh Azad, Christian Gogolin, Christina Lee
+
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 07fb7d9a103..a26fc71dfc4 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -2478,3 +2478,19 @@ def defines_diagonalizing_gates(obj):
except DiagGatesUndefinedError:
return False
return True
+
+
+@qml.BooleanFn
+def gen_is_multi_term_hamiltonian(obj):
+ """Returns ``True`` if an operator has a generator defined and it is a Hamiltonian
+ with more than one term."""
+
+ try:
+ o = obj.generator()
+ except (AttributeError, OperatorPropertyUndefined, GeneratorUndefinedError):
+ return False
+
+ if isinstance(o, qml.Hamiltonian):
+ if len(o.coeffs) > 1:
+ return True
+ return False
diff --git a/pennylane/transforms/tape_expand.py b/pennylane/transforms/tape_expand.py
index e3444d3c6f6..a50bd8afa51 100644
--- a/pennylane/transforms/tape_expand.py
+++ b/pennylane/transforms/tape_expand.py
@@ -19,6 +19,7 @@
import pennylane as qml
from pennylane.operation import (
has_gen,
+ gen_is_multi_term_hamiltonian,
has_grad_method,
has_nopar,
has_unitary_gen,
@@ -136,7 +137,7 @@ def expand_fn(tape, depth=depth, **kwargs):
expand_multipar = create_expand_fn(
depth=10,
- stop_at=not_tape | is_measurement | has_nopar | has_gen,
+ stop_at=not_tape | is_measurement | has_nopar | (has_gen & ~gen_is_multi_term_hamiltonian),
docstring=_expand_multipar_doc,
)
@@ -159,7 +160,11 @@ def expand_fn(tape, depth=depth, **kwargs):
expand_trainable_multipar = create_expand_fn(
depth=10,
- stop_at=not_tape | is_measurement | has_nopar | (~is_trainable) | has_gen,
+ stop_at=not_tape
+ | is_measurement
+ | has_nopar
+ | (~is_trainable)
+ | (has_gen & ~gen_is_multi_term_hamiltonian),
docstring=_expand_trainable_multipar_doc,
)
diff --git a/tests/optimize/test_qng.py b/tests/optimize/test_qng.py
index e4cc6c1fed8..8363191e3a7 100644
--- a/tests/optimize/test_qng.py
+++ b/tests/optimize/test_qng.py
@@ -72,6 +72,31 @@ def circuit(params):
assert np.allclose(step1, expected_step)
assert np.allclose(step2, expected_step)
+ def test_step_and_cost_autograd_with_gen_hamiltonian(self, tol):
+ """Test that the correct cost and step is returned via the
+ step_and_cost method for the QNG optimizer when the generator
+ of an operator is a Hamiltonian"""
+
+ dev = qml.device("default.qubit", wires=4)
+
+ @qml.qnode(dev)
+ def circuit(params):
+ qml.DoubleExcitation(params[0], wires=[0, 1, 2, 3])
+ qml.RY(params[1], wires=0)
+ return qml.expval(qml.PauliZ(0))
+
+ var = np.array([0.011, 0.012])
+ opt = qml.QNGOptimizer(stepsize=0.01)
+
+ step1, res = opt.step_and_cost(circuit, var)
+ step2 = opt.step(circuit, var)
+
+ expected = circuit(var)
+ expected_step = var - opt.stepsize * 4 * qml.grad(circuit)(var)
+ assert np.all(res == expected)
+ assert np.allclose(step1, expected_step)
+ assert np.allclose(step2, expected_step)
+
def test_step_and_cost_with_grad_fn_grouped_input(self, tol):
"""Test that the correct cost and update is returned via the step_and_cost
method for the QNG optimizer when providing an explicit grad_fn.
diff --git a/tests/test_operation.py b/tests/test_operation.py
index 77810ffde94..ba35455a1bc 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -1531,6 +1531,7 @@ class DummyOp(qml.operation.CVOperation):
class TestCriteria:
+ doubleExcitation = qml.DoubleExcitation(0.1, wires=[0, 1, 2, 3])
rx = qml.RX(qml.numpy.array(0.3, requires_grad=True), wires=1)
stiff_rx = qml.RX(0.3, wires=1)
cnot = qml.CNOT(wires=[1, 0])
@@ -1555,6 +1556,13 @@ def test_has_grad_method(self):
assert qml.operation.has_grad_method(self.rot)
assert not qml.operation.has_grad_method(self.cnot)
+ def test_gen_is_multi_term_hamiltonian(self):
+ """Test gen_is_multi_term_hamiltonian criterion."""
+ assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation)
+ assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot)
+ assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot)
+ assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp)
+
def test_has_multipar(self):
"""Test has_multipar criterion."""
assert not qml.operation.has_multipar(self.rx)