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."""