diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index aeeeedf42e3..01be3436631 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -298,6 +298,9 @@ * `qml.transforms.split_non_commuting` will now work with single-term operator arithmetic. [(#5314)](https://github.com/PennyLaneAI/pennylane/pull/5314) +* `LinearCombination` and `Sum` now accept `_grouping_indices` on initialization. + [(#5524)](https://github.com/PennyLaneAI/pennylane/pull/5524) +

Mid-circuit measurements and dynamic circuits

* The `QubitDevice` class and children classes support the `dynamic_one_shot` transform provided that they support `MidMeasureMP` operations natively. @@ -465,6 +468,10 @@

Bug fixes 🐛

+* `ApproxTimeEvolution`, `CommutingEvolution`, `QDrift`, and `TrotterProduct` + now de-queue their input observable. + [(#5524)](https://github.com/PennyLaneAI/pennylane/pull/5524) + * (In)equality of `qml.HilbertSchmidt` instances is now reported correctly by `qml.equal`. [(#5538)](https://github.com/PennyLaneAI/pennylane/pull/5538) diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py index 60c604b44ac..e4d943f424a 100644 --- a/pennylane/ops/op_math/linear_combination.py +++ b/pennylane/ops/op_math/linear_combination.py @@ -100,14 +100,11 @@ class LinearCombination(Sum): def _flatten(self): # note that we are unable to restore grouping type or method without creating new properties - return (self._coeffs, self._ops, self.data), (self.grouping_indices,) + return self.terms(), (self.grouping_indices,) @classmethod def _unflatten(cls, data, metadata): - new_op = cls(data[0], data[1]) - new_op._grouping_indices = metadata[0] # pylint: disable=protected-access - new_op.data = data[2] - return new_op + return cls(data[0], data[1], _grouping_indices=metadata[0]) def __init__( self, @@ -116,6 +113,7 @@ def __init__( simplify=False, grouping_type=None, method="rlf", + _grouping_indices=None, _pauli_rep=None, id=None, ): @@ -147,7 +145,12 @@ def __init__( operands = tuple(qml.s_prod(c, op) for c, op in zip(coeffs, observables)) super().__init__( - *operands, grouping_type=grouping_type, method=method, id=id, _pauli_rep=_pauli_rep + *operands, + grouping_type=grouping_type, + method=method, + id=id, + _grouping_indices=_grouping_indices, + _pauli_rep=_pauli_rep, ) @staticmethod diff --git a/pennylane/ops/op_math/sum.py b/pennylane/ops/op_math/sum.py index cb9a580f935..725540bf6db 100644 --- a/pennylane/ops/op_math/sum.py +++ b/pennylane/ops/op_math/sum.py @@ -216,17 +216,24 @@ def _flatten(self): @classmethod def _unflatten(cls, data, metadata): - # pylint: disable=protected-access - new_op = cls(*data) - new_op._grouping_indices = metadata[0] - return new_op + return cls(*data, _grouping_indices=metadata[0]) def __init__( - self, *operands: Operator, grouping_type=None, method="rlf", id=None, _pauli_rep=None + self, + *operands: Operator, + grouping_type=None, + method="rlf", + id=None, + _grouping_indices=None, + _pauli_rep=None, ): super().__init__(*operands, id=id, _pauli_rep=_pauli_rep) - self._grouping_indices = None + self._grouping_indices = _grouping_indices + if _grouping_indices is not None and grouping_type is not None: + raise ValueError( + "_grouping_indices and grouping_type cannot be specified at the same time." + ) if grouping_type is not None: self.compute_grouping(grouping_type=grouping_type, method=method) diff --git a/pennylane/templates/subroutines/approx_time_evolution.py b/pennylane/templates/subroutines/approx_time_evolution.py index 694821865c3..c6d0a06c709 100644 --- a/pennylane/templates/subroutines/approx_time_evolution.py +++ b/pennylane/templates/subroutines/approx_time_evolution.py @@ -139,6 +139,11 @@ def __init__(self, hamiltonian, time, n, id=None): # trainable parameters are passed to the base init method super().__init__(*hamiltonian.data, time, wires=wires, id=id) + def queue(self, context=qml.QueuingManager): + context.remove(self.hyperparameters["hamiltonian"]) + context.append(self) + return self + @staticmethod def compute_decomposition( *coeffs_and_time, wires, hamiltonian, n diff --git a/pennylane/templates/subroutines/commuting_evolution.py b/pennylane/templates/subroutines/commuting_evolution.py index e10252e912d..547dc21584b 100644 --- a/pennylane/templates/subroutines/commuting_evolution.py +++ b/pennylane/templates/subroutines/commuting_evolution.py @@ -141,6 +141,11 @@ def __init__(self, hamiltonian, time, frequencies=None, shifts=None, id=None): super().__init__(time, *hamiltonian.parameters, wires=hamiltonian.wires, id=id) + def queue(self, context=qml.QueuingManager): + context.remove(self.hyperparameters["hamiltonian"]) + context.append(self) + return self + @staticmethod def compute_decomposition( time, *_, wires, hamiltonian, **__ diff --git a/pennylane/templates/subroutines/qdrift.py b/pennylane/templates/subroutines/qdrift.py index 35299e3a7af..989e7a110ae 100644 --- a/pennylane/templates/subroutines/qdrift.py +++ b/pennylane/templates/subroutines/qdrift.py @@ -190,6 +190,11 @@ def __init__( # pylint: disable=too-many-arguments } super().__init__(time, wires=hamiltonian.wires, id=id) + def queue(self, context=qml.QueuingManager): + context.remove(self.hyperparameters["base"]) + context.append(self) + return self + @classmethod def _unflatten(cls, data, metadata): """Recreate an operation from its serialized format. diff --git a/pennylane/templates/subroutines/trotter.py b/pennylane/templates/subroutines/trotter.py index 52a56a0c05d..68946909e0b 100644 --- a/pennylane/templates/subroutines/trotter.py +++ b/pennylane/templates/subroutines/trotter.py @@ -218,6 +218,11 @@ def __init__( # pylint: disable=too-many-arguments } super().__init__(time, wires=hamiltonian.wires, id=id) + def queue(self, context=qml.QueuingManager): + context.remove(self.hyperparameters["base"]) + context.append(self) + return self + def error( self, method: str = "commutator", fast: bool = True ): # pylint: disable=arguments-differ diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py index 52970279b70..4436a7121a9 100644 --- a/tests/ops/op_math/test_linear_combination.py +++ b/tests/ops/op_math/test_linear_combination.py @@ -618,12 +618,11 @@ def test_flatten_unflatten(self, coeffs, ops, grouping_type): data, metadata = H._flatten() assert metadata[0] == H.grouping_indices assert hash(metadata) - assert len(data) == 3 + assert len(data) == 2 assert qml.math.allequal( data[0], H._coeffs ) # Previously checking "is" instead of "==", problem? assert data[1] == H._ops - assert data[2] == H.data new_H = LinearCombination._unflatten(*H._flatten()) assert qml.equal(H, new_H) @@ -1457,6 +1456,12 @@ def test_LinearCombination_matmul(self): class TestGrouping: """Tests for the grouping functionality""" + def test_set_on_initialization(self): + """Test that grouping indices can be set on initialization.""" + + op = qml.ops.LinearCombination([1, 1], [qml.X(0), qml.Y(1)], _grouping_indices=[[0, 1]]) + assert op.grouping_indices == [[0, 1]] + def test_indentities_preserved(self): """Tests that the grouping indices do not drop identity terms when the wire order is nonstandard.""" diff --git a/tests/ops/op_math/test_sum.py b/tests/ops/op_math/test_sum.py index be266eab927..3707dc54475 100644 --- a/tests/ops/op_math/test_sum.py +++ b/tests/ops/op_math/test_sum.py @@ -1326,6 +1326,18 @@ def test_adjoint(self): class TestGrouping: """Test grouping functionality of Sum""" + def test_set_on_initialization(self): + """Test that grouping indices can be set on initialization.""" + + op = qml.ops.Sum(qml.X(0), qml.Y(1), _grouping_indices=[[0, 1]]) + assert op.grouping_indices == [[0, 1]] + op_ac = qml.ops.Sum(qml.X(0), qml.Y(1), grouping_type="anticommuting") + assert op_ac.grouping_indices == ((0,), (1,)) + with pytest.raises(ValueError, match=r"cannot be specified at the same time."): + qml.ops.Sum( + qml.X(0), qml.Y(1), grouping_type="anticommuting", _grouping_indices=[[0, 1]] + ) + def test_non_pauli_error(self): """Test that grouping non-Pauli observables is not supported.""" op = Sum(qml.PauliX(0), Prod(qml.PauliZ(0), qml.PauliX(1)), qml.Hadamard(2)) diff --git a/tests/templates/test_subroutines/test_approx_time_evolution.py b/tests/templates/test_subroutines/test_approx_time_evolution.py index 5924d3e7504..d61b91b4bbf 100644 --- a/tests/templates/test_subroutines/test_approx_time_evolution.py +++ b/tests/templates/test_subroutines/test_approx_time_evolution.py @@ -47,6 +47,17 @@ def test_flatten_unflatten(): assert new_op is not op +def test_queuing(): + """Test that ApproxTimeEvolution de-queues the input hamiltonian.""" + + with qml.queuing.AnnotatedQueue() as q: + H = qml.X(0) + qml.Y(1) + op = qml.ApproxTimeEvolution(H, 0.1, n=20) + + assert len(q.queue) == 1 + assert q.queue[0] is op + + class TestDecomposition: """Tests that the template defines the correct decomposition.""" diff --git a/tests/templates/test_subroutines/test_commuting_evolution.py b/tests/templates/test_subroutines/test_commuting_evolution.py index 60124cf552a..5de57ea4ce1 100644 --- a/tests/templates/test_subroutines/test_commuting_evolution.py +++ b/tests/templates/test_subroutines/test_commuting_evolution.py @@ -85,6 +85,17 @@ def evolution_circuit(time): assert all(np.isclose(state1, state2)) +def test_queuing(): + """Test that CommutingEvolution de-queues the input hamiltonian.""" + + with qml.queuing.AnnotatedQueue() as q: + H = qml.X(0) + qml.Y(1) + op = qml.CommutingEvolution(H, 0.1, (2,)) + + assert len(q.queue) == 1 + assert q.queue[0] is op + + def test_decomposition_expand(): """Test that the decomposition of CommutingEvolution is an ApproxTimeEvolution with one step.""" diff --git a/tests/templates/test_subroutines/test_qdrift.py b/tests/templates/test_subroutines/test_qdrift.py index 4e7b5a80850..ff47bc46fef 100644 --- a/tests/templates/test_subroutines/test_qdrift.py +++ b/tests/templates/test_subroutines/test_qdrift.py @@ -44,6 +44,16 @@ class TestInitialization: """Test that the class is intialized correctly.""" + def test_queuing(self): + """Test that QDrift de-queues the input hamiltonian.""" + + with qml.queuing.AnnotatedQueue() as q: + H = qml.X(0) + qml.Y(1) + op = qml.QDrift(H, 0.1, n=20) + + assert len(q.queue) == 1 + assert q.queue[0] is op + @pytest.mark.parametrize("n", (1, 2, 3)) @pytest.mark.parametrize("time", (0.5, 1, 2)) @pytest.mark.parametrize("seed", (None, 1234, 42)) diff --git a/tests/templates/test_subroutines/test_trotter.py b/tests/templates/test_subroutines/test_trotter.py index a6dea66db4f..88f87801a44 100644 --- a/tests/templates/test_subroutines/test_trotter.py +++ b/tests/templates/test_subroutines/test_trotter.py @@ -399,6 +399,16 @@ def test_convention_approx_time_evolv(self, time, n): qml.matrix(op2, wire_order=hamiltonian.wires), ) + def test_queuing(self): + """Test that the target operator is removed from the queue.""" + + with qml.queuing.AnnotatedQueue() as q: + H = qml.X(0) + qml.Y(1) + op = qml.TrotterProduct(H, time=2) + + assert len(q.queue) == 1 + assert q.queue[0] is op + class TestPrivateFunctions: """Test the private helper functions."""