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)