From 68557090402fc734492342a11990b359c14005ab Mon Sep 17 00:00:00 2001 From: Pietropaolo Frisoni Date: Thu, 16 May 2024 13:33:45 -0400 Subject: [PATCH] Deprecate `simplify` argument in `qml.ops.Hamiltonian` and `qml.LinearCombination` (#5677) **Context:** ***Part of deprecations and removals for pennylane-0.37*** **Description of the Change:** Deprecating the ``simplify`` argument in ``qml.ops.Hamiltonian`` and ``qml.ops.LinearCombination`` in favour of ``qml.simplify()``. [sc-59086] --- doc/development/deprecations.rst | 7 ++ doc/releases/changelog-dev.md | 10 ++- pennylane/operation.py | 4 +- pennylane/ops/op_math/linear_combination.py | 13 +++- pennylane/ops/qubit/hamiltonian.py | 23 ++++-- pennylane/transforms/batch_transform.py | 6 +- tests/ops/op_math/test_linear_combination.py | 75 +++++++++++++++++--- tests/ops/qubit/test_hamiltonian.py | 64 +++++++++++++++-- 8 files changed, 173 insertions(+), 29 deletions(-) diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst index 5a8f0cbac9e..34c6e03b9b1 100644 --- a/doc/development/deprecations.rst +++ b/doc/development/deprecations.rst @@ -9,10 +9,17 @@ deprecations are listed below. Pending deprecations -------------------- +* The ``simplify`` argument in ``qml.Hamiltonian`` and ``qml.ops.LinearCombination`` is deprecated. + Instead, ``qml.simplify()`` can be called on the constructed operator. + + - Deprecated in v0.37 + - Will be removed in v0.39 + * ``qml.transforms.map_batch_transform`` is deprecated, since transforms can be applied directly to a batch of tapes. See :func:`~.pennylane.transform` for more information. - Deprecated in v0.37 + - Will be removed in v0.38 New operator arithmetic deprecations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 01db3008d03..1a65146a574 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -92,10 +92,10 @@ * `qml.is_commuting` no longer accepts the `wire_map` argument, which does not bring any functionality. [(#5660)](https://github.com/PennyLaneAI/pennylane/pull/5660) -* ``qml.from_qasm_file`` has been removed. The user can open files and load their content using `qml.from_qasm`. +* `qml.from_qasm_file` has been removed. The user can open files and load their content using `qml.from_qasm`. [(#5659)](https://github.com/PennyLaneAI/pennylane/pull/5659) -* ``qml.load`` has been removed in favour of more specific functions, such as ``qml.from_qiskit``, etc. +* `qml.load` has been removed in favour of more specific functions, such as `qml.from_qiskit`, etc. [(#5654)](https://github.com/PennyLaneAI/pennylane/pull/5654)

Community contributions 🥳

@@ -105,7 +105,11 @@

Deprecations 👋

-* ``qml.transforms.map_batch_transform`` is deprecated, since a transform can be applied directly to a batch of tapes. +* The `simplify` argument in `qml.Hamiltonian` and `qml.ops.LinearCombination` is deprecated. + Instead, `qml.simplify()` can be called on the constructed operator. + [(#5677)](https://github.com/PennyLaneAI/pennylane/pull/5677) + +* `qml.transforms.map_batch_transform` is deprecated, since a transform can be applied directly to a batch of tapes. [(#5676)](https://github.com/PennyLaneAI/pennylane/pull/5676)

Documentation 📝

diff --git a/pennylane/operation.py b/pennylane/operation.py index 7cb3d460d6b..4508e1745cb 100644 --- a/pennylane/operation.py +++ b/pennylane/operation.py @@ -2033,7 +2033,7 @@ def __add__(self, other): if isinstance(other, (qml.ops.Hamiltonian, qml.ops.LinearCombination)): return other + self if isinstance(other, (Observable, Tensor)): - return qml.Hamiltonian([1, 1], [self, other], simplify=True) + return qml.simplify(qml.Hamiltonian([1, 1], [self, other])) return super().__add__(other=other) @@ -2045,7 +2045,7 @@ def __mul__(self, a): return super().__mul__(other=a) if isinstance(a, (int, float)): - return qml.Hamiltonian([a], [self], simplify=True) + return qml.simplify(qml.Hamiltonian([a], [self])) return super().__mul__(other=a) diff --git a/pennylane/ops/op_math/linear_combination.py b/pennylane/ops/op_math/linear_combination.py index cde0c5cd83e..ab00d96356a 100644 --- a/pennylane/ops/op_math/linear_combination.py +++ b/pennylane/ops/op_math/linear_combination.py @@ -39,7 +39,7 @@ class LinearCombination(Sum): observables (Iterable[Observable]): observables in the ``LinearCombination`` expression, of same length as ``coeffs`` simplify (bool): Specifies whether the ``LinearCombination`` is simplified upon initialization (like-terms are combined). The default value is `False`. Note that ``coeffs`` cannot - be differentiated when using the ``'torch'`` interface and ``simplify=True``. + be differentiated when using the ``'torch'`` interface and ``simplify=True``. Use of this argument is deprecated. grouping_type (str): If not ``None``, compute and store information on how to group commuting observables upon initialization. This information may be accessed when a :class:`~.QNode` containing this ``LinearCombination`` is executed on devices. The string refers to the type of binary relation between Pauli words. @@ -48,6 +48,10 @@ class LinearCombination(Sum): can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First). Ignored if ``grouping_type=None``. id (str): name to be assigned to this ``LinearCombination`` instance + .. warning:: + The ``simplify`` argument is deprecated and will be removed in a future release. + Instead, you can call ``qml.simplify`` on the constructed operator. + **Example:** A ``LinearCombination`` can be created by simply passing the list of coefficients @@ -131,6 +135,13 @@ def __init__( _pauli_rep = self._build_pauli_rep_static(coeffs, observables) if simplify: + + warnings.warn( + "The simplify argument in qml.Hamiltonian and qml.ops.LinearCombination is deprecated. " + "Instead, you can call qml.simplify on the constructed operator.", + qml.PennyLaneDeprecationWarning, + ) + # simplify upon initialization changes ops such that they wouldnt be removed in self.queue() anymore if qml.QueuingManager.recording(): for o in observables: diff --git a/pennylane/ops/qubit/hamiltonian.py b/pennylane/ops/qubit/hamiltonian.py index 28ca22204b7..0d6a1d96880 100644 --- a/pennylane/ops/qubit/hamiltonian.py +++ b/pennylane/ops/qubit/hamiltonian.py @@ -77,7 +77,7 @@ class Hamiltonian(Observable): coeffs (tensor_like): coefficients of the Hamiltonian expression observables (Iterable[Observable]): observables in the Hamiltonian expression, of same length as coeffs simplify (bool): Specifies whether the Hamiltonian is simplified upon initialization - (like-terms are combined). The default value is `False`. + (like-terms are combined). The default value is `False`. Use of this argument is deprecated. grouping_type (str): If not None, compute and store information on how to group commuting observables upon initialization. This information may be accessed when QNodes containing this Hamiltonian are executed on devices. The string refers to the type of binary relation between Pauli words. @@ -86,6 +86,10 @@ class Hamiltonian(Observable): can be ``'lf'`` (Largest First) or ``'rlf'`` (Recursive Largest First). Ignored if ``grouping_type=None``. id (str): name to be assigned to this Hamiltonian instance + .. warning:: + The ``simplify`` argument is deprecated and will be removed in a future release. + Instead, you can call ``qml.simplify`` on the constructed operator. + **Example:** .. note:: @@ -279,6 +283,13 @@ def __init__( self._grouping_indices = None if simplify: + + warn( + "The simplify argument in qml.Hamiltonian and qml.ops.LinearCombination is deprecated. " + "Instead, you can call qml.simplify on the constructed operator.", + qml.PennyLaneDeprecationWarning, + ) + # simplify upon initialization changes ops such that they wouldnt be # removed in self.queue() anymore, removing them here manually. if qml.QueuingManager.recording(): @@ -744,12 +755,12 @@ def __matmul__(self, H): coeffs = qml.math.kron(coeffs1, coeffs2) ops_list = itertools.product(ops1, ops2) terms = [qml.operation.Tensor(t[0], t[1]) for t in ops_list] - return Hamiltonian(coeffs, terms, simplify=True) + return qml.simplify(Hamiltonian(coeffs, terms)) if isinstance(H, (Tensor, Observable)): terms = [op @ copy(H) for op in ops1] - return Hamiltonian(coeffs1, terms, simplify=True) + return qml.simplify(Hamiltonian(coeffs1, terms)) return NotImplemented @@ -766,7 +777,7 @@ def __rmatmul__(self, H): if isinstance(H, (Tensor, Observable)): terms = [copy(H) @ op for op in ops1] - return Hamiltonian(coeffs1, terms, simplify=True) + return qml.simplify(Hamiltonian(coeffs1, terms)) return NotImplemented @@ -781,14 +792,14 @@ def __add__(self, H): if isinstance(H, Hamiltonian): coeffs = qml.math.concatenate([self_coeffs, copy(H.coeffs)], axis=0) ops.extend(H.ops.copy()) - return Hamiltonian(coeffs, ops, simplify=True) + return qml.simplify(Hamiltonian(coeffs, ops)) if isinstance(H, (Tensor, Observable)): coeffs = qml.math.concatenate( [self_coeffs, qml.math.cast_like([1.0], self_coeffs)], axis=0 ) ops.append(H) - return Hamiltonian(coeffs, ops, simplify=True) + return qml.simplify(Hamiltonian(coeffs, ops)) return NotImplemented diff --git a/pennylane/transforms/batch_transform.py b/pennylane/transforms/batch_transform.py index 9d07f1d5ea6..9b141534516 100644 --- a/pennylane/transforms/batch_transform.py +++ b/pennylane/transforms/batch_transform.py @@ -72,9 +72,9 @@ def map_batch_transform( """ warnings.warn( - "qml.transforms.map_batch_transform is deprecated." - "Instead, a transform can be applied directly to a batch of tapes." - "See :func:`~.pennylane.transform` for more details.", + "qml.transforms.map_batch_transform is deprecated. " + "Instead, a transform can be applied directly to a batch of tapes. " + "See qml.transform for more details.", qml.PennyLaneDeprecationWarning, ) diff --git a/tests/ops/op_math/test_linear_combination.py b/tests/ops/op_math/test_linear_combination.py index 74e8ec64ced..a1f59b887e8 100644 --- a/tests/ops/op_math/test_linear_combination.py +++ b/tests/ops/op_math/test_linear_combination.py @@ -564,6 +564,14 @@ def circuit2(param): class TestLinearCombination: """Test the LinearCombination class""" + def test_deprecation_simplify_argument(self): + """Test that a deprecation warning is raised if the simplify argument is True.""" + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + _ = qml.ops.LinearCombination([1.0], [qml.X(0)], simplify=True) + def test_error_if_observables_operator(self): """Test thatt an error is raised if an operator is provided to observables.""" @@ -588,7 +596,14 @@ def test_error_if_observables_operator(self): @pytest.mark.parametrize("coeffs, ops, true_pauli", PAULI_REPS) def test_pauli_rep(self, coeffs, ops, true_pauli, simplify): """Test the pauli rep is correctly constructed""" - H = qml.ops.LinearCombination(coeffs, ops, simplify=simplify) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + H = qml.ops.LinearCombination(coeffs, ops, simplify=simplify) + else: + H = qml.ops.LinearCombination(coeffs, ops, simplify=simplify) pr = H.pauli_rep if simplify: pr.simplify() @@ -1629,7 +1644,11 @@ def circuit(): qml.RY(0.1, wires=0) return qml.expval(qml.ops.LinearCombination([1.0, 2.0], [X(1), X(1)], simplify=True)) - circuit() + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + circuit() pars = circuit.qtape.get_parameters(trainable_only=False) # simplify worked and added 1. and 2. assert pars == [0.1, 3.0] @@ -1639,7 +1658,11 @@ def test_queuing_behaviour(self): """Tests that the base observables are correctly dequeued with simplify=True""" with qml.queuing.AnnotatedQueue() as q: - obs = qml.Hamiltonian([1, 1, 1], [qml.X(0), qml.X(0), qml.Z(0)], simplify=True) + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + obs = qml.Hamiltonian([1, 1, 1], [qml.X(0), qml.X(0), qml.Z(0)], simplify=True) assert len(q) == 1 assert q.queue[0] == obs @@ -1671,7 +1694,14 @@ def circuit(coeffs, param): ) grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1745,7 +1775,14 @@ def circuit(coeffs, param): ) grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1815,7 +1852,15 @@ def circuit(coeffs, param): ) grad_fn = jax.grad(circuit) - grad = grad_fn(coeffs, param) + + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1884,7 +1929,14 @@ def circuit(coeffs, param): ) ) - res = circuit(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + res = circuit(coeffs, param) + else: + res = circuit(coeffs, param) res.backward() # pylint:disable=no-member grad = (coeffs.grad, param.grad) @@ -1972,7 +2024,14 @@ def circuit(coeffs, param): ) with tf.GradientTape() as tape: - res = circuit(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + res = circuit(coeffs, param) + else: + res = circuit(coeffs, param) grad = tape.gradient(res, [coeffs, param]) # differentiating a cost that combines circuits with diff --git a/tests/ops/qubit/test_hamiltonian.py b/tests/ops/qubit/test_hamiltonian.py index 945e8ac2023..f208b1e4a9c 100644 --- a/tests/ops/qubit/test_hamiltonian.py +++ b/tests/ops/qubit/test_hamiltonian.py @@ -703,6 +703,15 @@ def test_deprecation_with_new_opmath(recwarn): assert len(recwarn) == 0 +def test_deprecation_simplify_argument(): + """Test that a deprecation warning is raised if the simplify argument is True.""" + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + _ = qml.ops.Hamiltonian([1.0], [qml.X(0)], simplify=True) + + @pytest.mark.usefixtures("use_legacy_opmath") class TestHamiltonian: """Test the Hamiltonian class""" @@ -1743,7 +1752,11 @@ def circuit(): qml.Hamiltonian([1.0, 2.0], [qml.PauliX(1), qml.PauliX(1)], simplify=True) ) - circuit() + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + circuit() pars = circuit.qtape.get_parameters(trainable_only=False) # simplify worked and added 1. and 2. assert pars == [0.1, 3.0] @@ -1776,7 +1789,15 @@ def circuit(coeffs, param): ) grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) + + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1850,7 +1871,15 @@ def circuit(coeffs, param): ) grad_fn = qml.grad(circuit) - grad = grad_fn(coeffs, param) + + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1920,7 +1949,14 @@ def circuit(coeffs, param): ) grad_fn = jax.grad(circuit) - grad = grad_fn(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + grad = grad_fn(coeffs, param) + else: + grad = grad_fn(coeffs, param) # differentiating a cost that combines circuits with # measurements expval(Pauli) @@ -1988,7 +2024,15 @@ def circuit(coeffs, param): ) ) - res = circuit(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + res = circuit(coeffs, param) + else: + res = circuit(coeffs, param) + res.backward() # pylint:disable=no-member grad = (coeffs.grad, param.grad) @@ -2076,7 +2120,15 @@ def circuit(coeffs, param): ) with tf.GradientTape() as tape: - res = circuit(coeffs, param) + if simplify: + with pytest.warns( + qml.PennyLaneDeprecationWarning, + match="deprecated", + ): + res = circuit(coeffs, param) + else: + res = circuit(coeffs, param) + grad = tape.gradient(res, [coeffs, param]) # differentiating a cost that combines circuits with