From 2d4bacaf7b23abfbc3da3b54199cf76c8737615a Mon Sep 17 00:00:00 2001 From: PietropaoloFrisoni Date: Mon, 6 Jan 2025 09:23:01 -0500 Subject: [PATCH 1/5] Remove rc0 from version number --- pennylane/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/_version.py b/pennylane/_version.py index 6c04c4a5261..c7d631ded99 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.40.0rc0" +__version__ = "0.40.0" From 0c2494c6a0424b3f29ab1667dcc61e72ad59a09f Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Mon, 6 Jan 2025 11:31:01 -0500 Subject: [PATCH 2/5] Add device_vjp validation (#6755) **Context:** We weren't actually validating that the `device_vjp=True` was supported. This allowed strange circumstances where you would have `device_vjp=True`, but not support device derivatives at all, and this could lead to incorrect answers. **Description of the Change:** **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** --- doc/releases/changelog-0.40.0.md | 3 +++ pennylane/workflow/resolution.py | 24 ++++++++--------- tests/test_qnode.py | 22 +++++++++++++++ .../workflow/test_resolve_execution_config.py | 27 +++++++++++++++++++ 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/doc/releases/changelog-0.40.0.md b/doc/releases/changelog-0.40.0.md index b381ea18ed0..e3769326619 100644 --- a/doc/releases/changelog-0.40.0.md +++ b/doc/releases/changelog-0.40.0.md @@ -625,6 +625,9 @@ same information.

Bug fixes 🐛

+* Adds validation so the device vjp is only used when the device actually supports it. + [(#6755)](https://github.com/PennyLaneAI/pennylane/pull/6755/) + * `qml.counts` returns all outcomes when the `all_outcomes` argument is `True` and mid-circuit measurements are present. [(#6732)](https://github.com/PennyLaneAI/pennylane/pull/6732) diff --git a/pennylane/workflow/resolution.py b/pennylane/workflow/resolution.py index 502a9810ed6..77d85aae9b3 100644 --- a/pennylane/workflow/resolution.py +++ b/pennylane/workflow/resolution.py @@ -86,9 +86,7 @@ def _use_tensorflow_autograph(): return not tf.executing_eagerly() -def _resolve_interface( - interface: Union[str, Interface, None], tapes: QuantumScriptBatch -) -> Interface: +def _resolve_interface(interface: Union[str, Interface], tapes: QuantumScriptBatch) -> Interface: """Helper function to resolve an interface based on a set of tapes. Args: @@ -249,22 +247,24 @@ def _resolve_execution_config( ): updated_values["grad_on_execution"] = False - if execution_config.use_device_jacobian_product and isinstance( - device, qml.devices.LegacyDeviceFacade - ): - raise qml.QuantumFunctionError( - "device provided jacobian products are not compatible with the old device interface." - ) - if ( "lightning" in device.name - and (transform_program and qml.metric_tensor in transform_program) + and transform_program + and qml.metric_tensor in transform_program and execution_config.gradient_method == "best" ): execution_config = replace(execution_config, gradient_method=qml.gradients.param_shift) - execution_config = _resolve_diff_method(execution_config, device, tape=tapes[0]) + if execution_config.use_device_jacobian_product and not device.supports_vjp( + execution_config, tapes[0] + ): + raise qml.QuantumFunctionError( + f"device_vjp=True is not supported for device {device}," + f" diff_method {execution_config.gradient_method}," + " and the provided circuit." + ) + if execution_config.gradient_method is qml.gradients.param_shift_cv: updated_values["gradient_keyword_arguments"]["dev"] = device diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 22f16a45ff9..5ac318333a3 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -873,6 +873,7 @@ def circuit(x, y): assert np.allclose(res, expected, atol=tol, rtol=0) + # pylint: disable=too-many-positional-arguments @pytest.mark.parametrize("dev_name", ["default.qubit", "default.mixed"]) @pytest.mark.parametrize("first_par", np.linspace(0.15, np.pi - 0.3, 3)) @pytest.mark.parametrize("sec_par", np.linspace(0.15, np.pi - 0.3, 3)) @@ -1170,6 +1171,27 @@ def decomposition(self) -> list: res = qml.execute([tape], dev) assert qml.math.get_interface(res) == "numpy" + def test_error_device_vjp_unsuppoprted(self): + """Test that an error is raised in the device_vjp is unsupported.""" + + class DummyDev(qml.devices.Device): + + def execute(self, circuits, execution_config=qml.devices.ExecutionConfig()): + return 0 + + def supports_derivatives(self, execution_config=None, circuit=None): + return execution_config and execution_config.gradient_method == "vjp_grad" + + def supports_vjp(self, execution_config=None, circuit=None) -> bool: + return execution_config and execution_config.gradient_method == "vjp_grad" + + @qml.qnode(DummyDev(), diff_method="parameter-shift", device_vjp=True) + def circuit(): + return qml.expval(qml.Z(0)) + + with pytest.raises(qml.QuantumFunctionError, match="device_vjp=True is not supported"): + circuit() + class TestShots: """Unit tests for specifying shots per call.""" diff --git a/tests/workflow/test_resolve_execution_config.py b/tests/workflow/test_resolve_execution_config.py index 87a67c8292e..96e7cb9f595 100644 --- a/tests/workflow/test_resolve_execution_config.py +++ b/tests/workflow/test_resolve_execution_config.py @@ -116,3 +116,30 @@ def test_jax_jit_interface(): expected_mcm_config = MCMConfig(mcm_method="deferred", postselect_mode="fill-shots") assert resolved_config.mcm_config == expected_mcm_config + + +# pylint: disable=unused-argument +def test_no_device_vjp_if_not_supported(): + """Test that an error is raised for device_vjp=True if the device does not support it.""" + + class DummyDev(qml.devices.Device): + + def execute(self, circuits, execution_config=qml.devices.ExecutionConfig()): + return 0 + + def supports_derivatives(self, execution_config=None, circuit=None): + return execution_config and execution_config.gradient_method == "vjp_grad" + + def supports_vjp(self, execution_config=None, circuit=None) -> bool: + return execution_config and execution_config.gradient_method == "vjp_grad" + + config_vjp_grad = ExecutionConfig(use_device_jacobian_product=True, gradient_method="vjp_grad") + tape = qml.tape.QuantumScript() + # no error + _ = _resolve_execution_config(config_vjp_grad, DummyDev(), (tape,)) + + config_parameter_shift = ExecutionConfig( + use_device_jacobian_product=True, gradient_method="parameter-shift" + ) + with pytest.raises(qml.QuantumFunctionError, match="device_vjp=True is not supported"): + _resolve_execution_config(config_parameter_shift, DummyDev(), (tape,)) From 22b172b79a33cbd537fd73359dce00d3a0e320ba Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Tue, 7 Jan 2025 14:21:40 -0500 Subject: [PATCH 3/5] Fix postselection Nan sampling with different environment setups (#6777) **Context:** When running tests locally, I was getting failures like: ``` FAILED tests/devices/default_qubit/test_default_qubit.py::TestPostselection::test_postselection_invalid_finite_shots[shots1-mp2-expected_shape2-False-jax] - ValueError: Probabilities contain NaN ``` We had logic to catch the error `'probabilities contain NaN'`, but something about my versioning gives a slightly different capitalization. To prevent this happening of other people, I updated the error catching to be capitalization independent. **Description of the Change:** Catch both `"Probabilities contain NaN"` and `"probabilities contain NaN"`. **Benefits:** Tests pass locally for my environment. **Possible Drawbacks:** Still a bit error prone. I'm also not sure what about my environment gives different capitalization. **Related GitHub Issues:** --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Pietropaolo Frisoni Co-authored-by: Mudit Pandey Co-authored-by: ringo-but-quantum Co-authored-by: Andrija Paurevic <46359773+andrijapau@users.noreply.github.com> --- pennylane/devices/qubit/sampling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/qubit/sampling.py b/pennylane/devices/qubit/sampling.py index d8036a6fb99..06ae78b5708 100644 --- a/pennylane/devices/qubit/sampling.py +++ b/pennylane/devices/qubit/sampling.py @@ -330,7 +330,7 @@ def _process_single_shot(samples): prng_key=prng_key, ) except ValueError as e: - if str(e) != "probabilities contain NaN": + if "probabilities contain nan" not in str(e).lower(): raise e samples = qml.math.full((shots.total_shots, len(wires)), 0) From 0f956984e48745568517c7a34f88540f8df20169 Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Wed, 8 Jan 2025 15:48:53 -0500 Subject: [PATCH 4/5] Make `BasisRotation` jit compatible (#6779) **Context:** PR #6019 only fixes `BasisRotation` when using backprop on `default.qubit`. It is not jit compatible on any other device. This is because `unitary_matrix` was being considered a hyperparameter, not a piece of data. So we could not detect that the matrix was a tracer and we were in jitting mode, and we could not convert the matrix back into numpy data. **Description of the Change:** Make `unitary_matrix` a piece of data instead of a hyperparameter. This allows us to detect when it is being jitted. As a by-product, I also made it valid pytree. By making `unitary_matrix` a piece of data, we were able to get rid of the custom comparison method in `qml.equal`. **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:** [sc-51603] Fixes #6004 --- doc/releases/changelog-0.40.0.md | 1 + pennylane/ops/functions/equal.py | 33 ------------------- .../templates/subroutines/basis_rotation.py | 14 ++++---- tests/ops/functions/test_equal.py | 4 +-- .../test_subroutines/test_basis_rotation.py | 6 ++-- 5 files changed, 13 insertions(+), 45 deletions(-) diff --git a/doc/releases/changelog-0.40.0.md b/doc/releases/changelog-0.40.0.md index e3769326619..c1099ecf358 100644 --- a/doc/releases/changelog-0.40.0.md +++ b/doc/releases/changelog-0.40.0.md @@ -389,6 +389,7 @@ * `qml.BasisRotation` template is now JIT compatible. [(#6019)](https://github.com/PennyLaneAI/pennylane/pull/6019) + [(#6779)](https://github.com/PennyLaneAI/pennylane/pull/6779) * The Jaxpr primitives for `for_loop`, `while_loop` and `cond` now store slices instead of numbers of args. diff --git a/pennylane/ops/functions/equal.py b/pennylane/ops/functions/equal.py index c7afc235b10..f40769a7dc3 100644 --- a/pennylane/ops/functions/equal.py +++ b/pennylane/ops/functions/equal.py @@ -754,39 +754,6 @@ def _equal_counts(op1: CountsMP, op2: CountsMP, **kwargs): return _equal_measurements(op1, op2, **kwargs) and op1.all_outcomes == op2.all_outcomes -@_equal_dispatch.register -# pylint: disable=unused-argument -def _equal_basis_rotation( - op1: qml.BasisRotation, - op2: qml.BasisRotation, - check_interface=True, - check_trainability=True, - rtol=1e-5, - atol=1e-9, -): - if not qml.math.allclose( - op1.hyperparameters["unitary_matrix"], - op2.hyperparameters["unitary_matrix"], - atol=atol, - rtol=rtol, - ): - return ( - "The hyperparameter unitary_matrix is not equal for op1 and op2.\n" - f"Got {op1.hyperparameters['unitary_matrix']}\n and {op2.hyperparameters['unitary_matrix']}." - ) - if op1.wires != op2.wires: - return f"op1 and op2 have different wires. Got {op1.wires} and {op2.wires}." - if check_interface: - interface1 = qml.math.get_interface(op1.hyperparameters["unitary_matrix"]) - interface2 = qml.math.get_interface(op2.hyperparameters["unitary_matrix"]) - if interface1 != interface2: - return ( - "The hyperparameter unitary_matrix has different interfaces for op1 and op2." - f" Got {interface1} and {interface2}." - ) - return True - - @_equal_dispatch.register def _equal_hilbert_schmidt( op1: qml.HilbertSchmidt, diff --git a/pennylane/templates/subroutines/basis_rotation.py b/pennylane/templates/subroutines/basis_rotation.py index 832a0684739..f522dcb8b09 100644 --- a/pennylane/templates/subroutines/basis_rotation.py +++ b/pennylane/templates/subroutines/basis_rotation.py @@ -107,6 +107,10 @@ def _primitive_bind_call(cls, wires, unitary_matrix, check=False, id=None): return cls._primitive.bind(*wires, unitary_matrix, check=check, id=id) + @classmethod + def _unflatten(cls, data, metadata): + return cls(wires=metadata[0], unitary_matrix=data[0]) + def __init__(self, wires, unitary_matrix, check=False, id=None): M, N = qml.math.shape(unitary_matrix) @@ -124,19 +128,15 @@ def __init__(self, wires, unitary_matrix, check=False, id=None): if len(wires) < 2: raise ValueError(f"This template requires at least two wires, got {len(wires)}") - self._hyperparameters = { - "unitary_matrix": unitary_matrix, - } - - super().__init__(wires=wires, id=id) + super().__init__(unitary_matrix, wires=wires, id=id) @property def num_params(self): - return 0 + return 1 @staticmethod def compute_decomposition( - wires, unitary_matrix, check=False + unitary_matrix, wires, check=False ): # pylint: disable=arguments-differ r"""Representation of the operator as a product of other operators. diff --git a/tests/ops/functions/test_equal.py b/tests/ops/functions/test_equal.py index 2a6f5270616..3ef0e9be2da 100644 --- a/tests/ops/functions/test_equal.py +++ b/tests/ops/functions/test_equal.py @@ -2598,7 +2598,7 @@ def test_different_tolerances_comparison(self, op, other_op): assert_equal(op, other_op, atol=1e-5) assert qml.equal(op, other_op, rtol=0, atol=1e-9) is False - with pytest.raises(AssertionError, match="The hyperparameter unitary_matrix is not equal"): + with pytest.raises(AssertionError, match="op1 and op2 have different data"): assert_equal(op, other_op, rtol=0, atol=1e-9) @pytest.mark.parametrize("op, other_op", [(op1, op2)]) @@ -2629,7 +2629,7 @@ def test_non_equal_interfaces(self, op): assert_equal(op, other_op, check_interface=False) assert qml.equal(op, other_op) is False - with pytest.raises(AssertionError, match=r"has different interfaces for op1 and op2"): + with pytest.raises(AssertionError, match=r"have different interfaces"): assert_equal(op, other_op) diff --git a/tests/templates/test_subroutines/test_basis_rotation.py b/tests/templates/test_subroutines/test_basis_rotation.py index a4328ca5264..0f9f6412a2b 100644 --- a/tests/templates/test_subroutines/test_basis_rotation.py +++ b/tests/templates/test_subroutines/test_basis_rotation.py @@ -21,7 +21,6 @@ import pennylane as qml -@pytest.mark.xfail # to be fixed by [sc-51603] def test_standard_validity(): """Run standard tests of operation validity.""" weights = np.array( @@ -402,8 +401,9 @@ def test_autograd(self, tol): assert np.allclose(grads, np.zeros_like(unitary_matrix, dtype=complex), atol=tol, rtol=0) + @pytest.mark.parametrize("device_name", ("default.qubit", "reference.qubit")) @pytest.mark.jax - def test_jax_jit(self, tol): + def test_jax_jit(self, device_name, tol): """Test the jax interface.""" import jax @@ -417,7 +417,7 @@ def test_jax_jit(self, tol): ] ) - dev = qml.device("default.qubit", wires=3) + dev = qml.device(device_name, wires=3) circuit = jax.jit(qml.QNode(circuit_template, dev), static_argnames="check") circuit2 = qml.QNode(circuit_template, dev) From a6485b5af00332db45d089504db9dc314f90356e Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot <> Date: Thu, 9 Jan 2025 03:13:18 +0000 Subject: [PATCH 5/5] exclude files from pr --- doc/development/release_notes.md | 2 ++ pennylane/_version.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/development/release_notes.md b/doc/development/release_notes.md index 2c189078a48..213fa02b448 100644 --- a/doc/development/release_notes.md +++ b/doc/development/release_notes.md @@ -3,6 +3,8 @@ Release notes This page contains the release notes for PennyLane. +.. mdinclude:: ../releases/changelog-dev.md + .. mdinclude:: ../releases/changelog-0.40.0.md .. mdinclude:: ../releases/changelog-0.39.0.md diff --git a/pennylane/_version.py b/pennylane/_version.py index c7d631ded99..a5d50fca8dd 100644 --- a/pennylane/_version.py +++ b/pennylane/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.40.0" +__version__ = "0.41.0-dev2"