From 15247a97d72dca64a1329c6cda02691efe9a7559 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 3 Sep 2024 14:33:01 -0400 Subject: [PATCH 01/59] Implement ReferenceQubit --- pennylane/devices/reference_qubit.py | 151 +++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 152 insertions(+) create mode 100644 pennylane/devices/reference_qubit.py diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py new file mode 100644 index 00000000000..9932e4bd336 --- /dev/null +++ b/pennylane/devices/reference_qubit.py @@ -0,0 +1,151 @@ +# Copyright 2018-2024 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Contains the ReferenceQubit device, a minimal device that can be used for testing +and plugin development purposes. +""" + +import numpy as np + +import pennylane as qml + +from .device_api import Device +from .execution_config import DefaultExecutionConfig +from .preprocess import ( + decompose, + validate_measurements, + validate_device_wires, +) +from .modifiers import single_tape_support, simulator_tracking + + +def sample_state(state: np.ndarray, shots: int, seed=None): + """Generate samples from the provided state and number of shots.""" + + probs = np.imag(state) ** 2 + np.real(state) ** 2 + basis_states = np.arange(len(probs)) + + num_wires = int(np.log2(len(probs))) + + rng = np.random.default_rng(seed) + basis_samples = rng.choice(basis_states, shots, p=probs) + + # convert basis state integers to array of booleans + bin_strings = (format(s, f"0{num_wires}b") for s in basis_samples) + return np.array([[int(val) for val in s] for s in bin_strings]) + + +def simulate(tape: qml.tape.QuantumTape, seed=None) -> qml.typing.Result: + """Simulate a tape and turn it into results. + + Args: + tape (.QuantumTape): a representation of a circuit + seed (Any): A seed to use to control the generation of samples. + + """ + # 1) create the initial state + state = np.zeros(2 ** len(tape.wires)) + state[0] = 1.0 + + # 2) apply all the operations + for op in tape.operations: + op_mat = op.matrix(wire_order=tape.wires) + state = op_mat @ state + + # 3) perform measurements + # note that shots are pulled from the tape, not from the device + if tape.shots: + samples = sample_state(state, shots=tape.shots.total_shots, seed=seed) + # Shot vector support + results = [] + for lower, upper in tape.shots.bins(): + sub_samples = samples[lower:upper] + results.append( + tuple(mp.process_samples(sub_samples, tape.wires) for mp in tape.measurements) + ) + if not tape.shots.has_partitioned_shots: + results = results[0] + else: + # diagonalize measurement transform handled diagonalizing gates + # otherwise we would need to apply them + results = tuple(mp.process_state(state, tape.wires) for mp in tape.measurements) + return results[0] if len(tape.measurements) == 1 else results + + +operations = frozenset({"PauliX", "PauliY", "PauliZ", "Hadamard", "CNOT", "CZ", "RX", "RY", "RZ"}) + + +def supports_operation(op: qml.operation.Operator) -> bool: + """This function used by preprocessing determines what operations + are natively supported by the device. + + While in theory ``simulate`` can support any operation with a matrix, we limit the target + gate set for improved testing and reference purposes. + + """ + return getattr(op, "name", None) in operations + + +@simulator_tracking # update device.tracker with some relevant information +@single_tape_support # add support for device.execute(tape) in addition to device.execute((tape,)) +class ReferenceQubit(Device): + """A slimmed down numpy-based simulator for reference and testing purposes. + + Args: + wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that + contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings + (``['ancilla', 'q1', 'q2']``). Default ``None`` if not specified. While this device allows + for ``wires`` to be unspecified at construction time, other devices may make this argument + mandatory. Devices can also implement additional restrictions on the possible wires. + shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots + to use in executions involving this device. Note that during execution, shots + are pulled from the circuit, not from the device. + seed (Union[str, None, int, array_like[int], SeedSequence, BitGenerator, Generator, jax.random.PRNGKey]): A + seed-like parameter matching that of ``seed`` for ``numpy.random.default_rng``. This is an optional + keyword argument added to follow recommend NumPy best practices. Other devices do not need + this parameter if it does not make sense for them. + + """ + + name = "reference.qubit" + + def __init__(self, wires=None, shots=None, seed=None): + super().__init__(wires=wires, shots=shots) + + # seed and rng not necessary for a device, but part of recommended + # numpy practices to use a local random number generator + self._rng = np.random.default_rng(seed) + + def preprocess(self, execution_config=DefaultExecutionConfig): + + # Here we convert an arbitrary tape into one natively supported by the device + program = qml.transforms.core.TransformProgram() + program.add_transform(validate_device_wires, wires=self.wires, name="reference.qubit") + program.add_transform(qml.defer_measurements) + program.add_transform(qml.transforms.split_non_commuting, name="reference.qubit") + program.add_transform( + decompose, + stopping_condition=supports_operation, + skip_initial_state_prep=False, + name="mini.qubit", + ) + program.add_transform(qml.transforms.diagonalize_measurements, name="reference.qubit") + program.add_transform(validate_measurements, name="reference.qubit") + program.add_transform(qml.transforms.broadcast_expand) + + # no need to preprocess the config as the device does not support derivatives + return program, execution_config + + def execute(self, circuits, execution_config=DefaultExecutionConfig): + return tuple(simulate(tape, seed=self._rng) for tape in circuits) diff --git a/setup.py b/setup.py index 6c0bcab70d3..104069c5aa8 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ "default.qubit.autograd = pennylane.devices.default_qubit_autograd:DefaultQubitAutograd", "default.qubit.jax = pennylane.devices.default_qubit_jax:DefaultQubitJax", "default.mixed = pennylane.devices.default_mixed:DefaultMixed", + "reference.qubit = pennylane.devices.reference_qubit:ReferenceQubit", "null.qubit = pennylane.devices.null_qubit:NullQubit", "default.qutrit = pennylane.devices.default_qutrit:DefaultQutrit", "default.clifford = pennylane.devices.default_clifford:DefaultClifford", From 6060ad656dac6bac188b3d0e53bdc634f2102054 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 4 Sep 2024 11:21:02 -0400 Subject: [PATCH 02/59] make isort happy --- pennylane/devices/reference_qubit.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index 9932e4bd336..e7e712b659f 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -22,12 +22,8 @@ from .device_api import Device from .execution_config import DefaultExecutionConfig -from .preprocess import ( - decompose, - validate_measurements, - validate_device_wires, -) -from .modifiers import single_tape_support, simulator_tracking +from .modifiers import simulator_tracking, single_tape_support +from .preprocess import decompose, validate_device_wires, validate_measurements def sample_state(state: np.ndarray, shots: int, seed=None): From 5a0bea68dbc002ac0fe8197b2fa7e994e96fdc1d Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 4 Sep 2024 16:24:28 -0400 Subject: [PATCH 03/59] Fix bug in reference.qubit --- pennylane/devices/reference_qubit.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index e7e712b659f..d2631c03ceb 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -70,13 +70,16 @@ def simulate(tape: qml.tape.QuantumTape, seed=None) -> qml.typing.Result: results.append( tuple(mp.process_samples(sub_samples, tape.wires) for mp in tape.measurements) ) + if len(tape.measurements) == 1: + results = [res[0] for res in results] if not tape.shots.has_partitioned_shots: results = results[0] else: - # diagonalize measurement transform handled diagonalizing gates - # otherwise we would need to apply them results = tuple(mp.process_state(state, tape.wires) for mp in tape.measurements) - return results[0] if len(tape.measurements) == 1 else results + if len(tape.measurements) == 1: + results = results[0] + + return results operations = frozenset({"PauliX", "PauliY", "PauliZ", "Hadamard", "CNOT", "CZ", "RX", "RY", "RZ"}) @@ -129,14 +132,14 @@ def preprocess(self, execution_config=DefaultExecutionConfig): program = qml.transforms.core.TransformProgram() program.add_transform(validate_device_wires, wires=self.wires, name="reference.qubit") program.add_transform(qml.defer_measurements) - program.add_transform(qml.transforms.split_non_commuting, name="reference.qubit") + program.add_transform(qml.transforms.split_non_commuting) program.add_transform( decompose, stopping_condition=supports_operation, skip_initial_state_prep=False, name="mini.qubit", ) - program.add_transform(qml.transforms.diagonalize_measurements, name="reference.qubit") + program.add_transform(qml.transforms.diagonalize_measurements) program.add_transform(validate_measurements, name="reference.qubit") program.add_transform(qml.transforms.broadcast_expand) From 2068b195a36bec57b8317b9e8ac699ce05826007 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 4 Sep 2024 16:51:42 -0400 Subject: [PATCH 04/59] Add `reference.qubit` to autograd tests --- pennylane/workflow/interfaces/autograd.py | 17 ++++++++++++++- tests/interfaces/test_autograd.py | 25 ++++++++++++++++++----- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/pennylane/workflow/interfaces/autograd.py b/pennylane/workflow/interfaces/autograd.py index 9452af31854..cb5731ddc8b 100644 --- a/pennylane/workflow/interfaces/autograd.py +++ b/pennylane/workflow/interfaces/autograd.py @@ -147,6 +147,21 @@ def autograd_execute( return _execute(parameters, tuple(tapes), execute_fn, jpc) +def _to_autograd(result: qml.typing.ResultBatch) -> qml.typing.ResultBatch: + """Converts an arbitrary result batch to one with autograd arrays. + Args: + result (ResultBatch): a nested structure of lists, tuples, dicts, and numpy arrays + Returns: + ResultBatch: a nested structure of tuples, dicts, and jax arrays + """ + if isinstance(result, dict): + return result + # pylint: disable=no-member + if isinstance(result, (list, tuple, autograd.builtins.tuple, autograd.builtins.list)): + return tuple(_to_autograd(r) for r in result) + return autograd.numpy.array(result) + + @autograd.extend.primitive def _execute( parameters, @@ -165,7 +180,7 @@ def _execute( for the input tapes. """ - return execute_fn(tapes) + return _to_autograd(execute_fn(tapes)) # pylint: disable=unused-argument diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 2a6ee306508..36dd83231a0 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -125,8 +125,8 @@ def f(x): # add tests for lightning 2 when possible # set rng for device when possible test_matrix = [ - ({"gradient_fn": param_shift}, Shots(100000), DefaultQubit(seed=42)), - ({"gradient_fn": param_shift}, Shots((100000, 100000)), DefaultQubit(seed=42)), + ({"gradient_fn": param_shift}, Shots(50000), DefaultQubit(seed=42)), + ({"gradient_fn": param_shift}, Shots((50000, 50000)), DefaultQubit(seed=42)), ({"gradient_fn": param_shift}, Shots(None), DefaultQubit()), ({"gradient_fn": "backprop"}, Shots(None), DefaultQubit()), ( @@ -146,7 +146,7 @@ def f(x): ({"gradient_fn": "adjoint", "device_vjp": True}, Shots(None), DefaultQubit()), ( {"gradient_fn": "device", "device_vjp": False}, - Shots((100000, 100000)), + Shots((50000, 50000)), ParamShiftDerivativesDevice(seed=904747894), ), ( @@ -154,12 +154,27 @@ def f(x): Shots((100000, 100000)), ParamShiftDerivativesDevice(seed=10490244), ), + ( + {"gradient_fn": param_shift}, + Shots(None), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots(50000), + qml.device("reference.qubit", seed=8743274), + ), + ( + {"gradient_fn": param_shift}, + Shots((50000, 50000)), + qml.device("reference.qubit", seed=8743274), + ), ] def atol_for_shots(shots): """Return higher tolerance if finite shots.""" - return 1e-2 if shots else 1e-6 + return 5e-2 if shots else 1e-6 @pytest.mark.parametrize("execute_kwargs, shots, device", test_matrix) @@ -788,7 +803,7 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot """Test hamiltonian with no trainable parameters.""" if execute_kwargs["gradient_fn"] == "adjoint" and not qml.operation.active_new_opmath(): - pytest.skip("adjoint differentiation does not suppport hamiltonians.") + pytest.skip("adjoint differentiation does not support hamiltonians.") coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=False) coeffs2 = np.array([0.7], requires_grad=False) From 66b3769c02c5bd93885a62abd957cd10a20f9c5c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 10:15:43 -0400 Subject: [PATCH 05/59] add reference.qubit to autograd qnode tests --- tests/interfaces/test_autograd_qnode.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py index 3db765be87d..3640d1d0019 100644 --- a/tests/interfaces/test_autograd_qnode.py +++ b/tests/interfaces/test_autograd_qnode.py @@ -37,6 +37,7 @@ [ParamShiftDerivativesDevice(), "best", False, False], [ParamShiftDerivativesDevice(), "parameter-shift", True, False], [ParamShiftDerivativesDevice(), "parameter-shift", False, True], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_qubit_device_and_diff_method = [ @@ -62,6 +63,7 @@ ["auto", DefaultQubit(), "hadamard", False, False], ["auto", qml.device("lightning.qubit", wires=5), "adjoint", False, False], ["auto", qml.device("lightning.qubit", wires=5), "adjoint", True, False], + ["auto", qml.device("reference.qubit"), "parameter-shift", False, False], ] pytestmark = pytest.mark.autograd @@ -1426,6 +1428,8 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") @qml.qnode( dev, From db63bb6def24012448e111dd8cadca32c1cc3300 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 10:33:47 -0400 Subject: [PATCH 06/59] xfail unsupported test --- tests/interfaces/test_autograd_qnode.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py index 3640d1d0019..ee161b8c949 100644 --- a/tests/interfaces/test_autograd_qnode.py +++ b/tests/interfaces/test_autograd_qnode.py @@ -1371,6 +1371,8 @@ def test_projector( """Test that the variance of a projector is correctly returned""" if diff_method == "adjoint": pytest.skip("adjoint supports either expvals or diagonal measurements.") + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") kwargs = dict( diff_method=diff_method, interface=interface, From 5b7b10b81811628cc5091f9af3b8f6b96d4587f2 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 12:10:07 -0400 Subject: [PATCH 07/59] Add reference.qubit to test_jax --- tests/interfaces/test_jax.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/interfaces/test_jax.py b/tests/interfaces/test_jax.py index 519c0daa028..19accca6bca 100644 --- a/tests/interfaces/test_jax.py +++ b/tests/interfaces/test_jax.py @@ -122,16 +122,20 @@ def cost(x, cache): # add tests for lightning 2 when possible # set rng for device when possible no_shots = Shots(None) +shots_10k = Shots(10000) shots_2_10k = Shots((10000, 10000)) dev_def = DefaultQubit() dev_ps = ParamShiftDerivativesDevice(seed=54353453) +dev_ref = qml.device("reference.qubit") test_matrix = [ - ({"gradient_fn": param_shift}, Shots(100000), DefaultQubit(seed=42)), # 0 + ({"gradient_fn": param_shift}, shots_10k, DefaultQubit(seed=42)), # 0 ({"gradient_fn": param_shift}, no_shots, dev_def), # 1 ({"gradient_fn": "backprop"}, no_shots, dev_def), # 2 ({"gradient_fn": "adjoint"}, no_shots, dev_def), # 3 ({"gradient_fn": "adjoint", "device_vjp": True}, no_shots, dev_def), # 4 ({"gradient_fn": "device"}, shots_2_10k, dev_ps), # 5 + ({"gradient_fn": param_shift}, no_shots, dev_ref), # 6 + ({"gradient_fn": param_shift}, shots_10k, dev_ref), # 7 ] From 74bc82b8c07a756f6a8acd4b99ea84119c4d4b8d Mon Sep 17 00:00:00 2001 From: dwierichs Date: Thu, 5 Sep 2024 19:22:20 +0200 Subject: [PATCH 08/59] fix bug --- pennylane/gradients/jvp.py | 11 +++++++--- pennylane/workflow/jacobian_products.py | 20 +++++++++--------- tests/gradients/core/test_jvp.py | 28 +++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/pennylane/gradients/jvp.py b/pennylane/gradients/jvp.py index e3634cce492..1247bbe37a9 100644 --- a/pennylane/gradients/jvp.py +++ b/pennylane/gradients/jvp.py @@ -295,11 +295,16 @@ def jvp(tape, tangent, gradient_fn, gradient_kwargs=None): if len(tape.trainable_params) == 0: # The tape has no trainable parameters; the JVP # is simply none. - def zero_vjp(_): - res = tuple(np.zeros(mp.shape(None, tape.shots)) for mp in tape.measurements) + def zero_jvp_for_single_shots(s): + res = tuple(np.zeros(mp.shape(shots=s)) for mp in tape.measurements) return res[0] if len(tape.measurements) == 1 else res - return tuple(), zero_vjp + def zero_jvp(_): + if tape.shots.has_partitioned_shots: + return tuple(zero_jvp_for_single_shots(s) for s in tape.shots) + return zero_jvp_for_single_shots(tape.shots) + + return tuple(), zero_jvp multi_m = len(tape.measurements) > 1 diff --git a/pennylane/workflow/jacobian_products.py b/pennylane/workflow/jacobian_products.py index f8163813366..688e786c1c5 100644 --- a/pennylane/workflow/jacobian_products.py +++ b/pennylane/workflow/jacobian_products.py @@ -46,6 +46,15 @@ def _compute_vjps(jacs, dys, tapes): return tuple(vjps) +def _zero_jvp_single_shots(shots, tape): + jvp = tuple(np.zeros(mp.shape(shots=shots), dtype=mp.numeric_type) for mp in tape.measurements) + return jvp[0] if len(tape.measurements) == 1 else jvp + +def _zero_jvp(tape): + if tape.shots.has_partitioned_shots: + return tuple(_zero_jvp_single_shots(s, tape) for s in tape.shots) + return _zero_jvp_single_shots(tape.shots, tape) + def _compute_jvps(jacs, tangents, tapes): """Compute the jvps of multiple tapes, directly for a Jacobian and tangents.""" f = {True: qml.gradients.compute_jvp_multi, False: qml.gradients.compute_jvp_single} @@ -54,16 +63,7 @@ def _compute_jvps(jacs, tangents, tapes): for jac, dx, t in zip(jacs, tangents, tapes): multi = len(t.measurements) > 1 if len(t.trainable_params) == 0: - empty_shots = qml.measurements.Shots(None) - zeros_jvp = tuple( - np.zeros(mp.shape(None, empty_shots), dtype=mp.numeric_type) - for mp in t.measurements - ) - zeros_jvp = zeros_jvp[0] if len(t.measurements) == 1 else zeros_jvp - if t.shots.has_partitioned_shots: - jvps.append(tuple(zeros_jvp for _ in range(t.shots.num_copies))) - else: - jvps.append(zeros_jvp) + jvps.append(_zero_jvp(t)) elif t.shots.has_partitioned_shots: jvps.append(tuple(f[multi](dx, j) for j in jac)) else: diff --git a/tests/gradients/core/test_jvp.py b/tests/gradients/core/test_jvp.py index 54bdb051572..bf694740b6a 100644 --- a/tests/gradients/core/test_jvp.py +++ b/tests/gradients/core/test_jvp.py @@ -860,6 +860,34 @@ def test_all_tapes_no_trainable_parameters(self): assert qml.math.allclose(fn([])[0], np.array(0.0)) assert qml.math.allclose(fn([])[1], np.array(0.0)) + def test_some_tapes_no_trainable_parameters(self): + """If some tapes have no trainable parameters all outputs will be None""" + + with qml.queuing.AnnotatedQueue() as q1: + qml.RX(0.4, wires=0) + qml.expval(qml.PauliZ(0)) + + tape1 = qml.tape.QuantumScript.from_queue(q1) + with qml.queuing.AnnotatedQueue() as q2: + qml.RX(0.4, wires=0) + qml.RX(0.6, wires=0) + qml.CNOT(wires=[0, 1]) + qml.expval(qml.PauliZ(0)) + + tape2 = qml.tape.QuantumScript.from_queue(q2) + tape1.trainable_params = {0} + tape2.trainable_params = set() + + tapes = [tape1, tape2] + tangents = [np.array([0.7]), np.array([1.0, 0.0])] + + v_tapes, fn = qml.gradients.batch_jvp(tapes, tangents, param_shift) + + assert len(v_tapes) == 2 + ps_res = [np.cos(0.4+np.pi/2), np.cos(0.4-np.pi/2)] + assert qml.math.allclose(fn(ps_res)[0], -np.sin(0.4) * 0.7) + assert qml.math.allclose(fn(ps_res)[1], np.array(0.0)) + def test_zero_tangent(self): """A zero dy vector will return no tapes and a zero matrix""" dev = qml.device("default.qubit", wires=2) From a7a53de4ad0cfb00c6809d6c86448a5bb5825b2b Mon Sep 17 00:00:00 2001 From: dwierichs Date: Thu, 5 Sep 2024 19:25:57 +0200 Subject: [PATCH 09/59] -a --- pennylane/workflow/jacobian_products.py | 2 ++ tests/gradients/core/test_jvp.py | 45 ++++++------------------- 2 files changed, 12 insertions(+), 35 deletions(-) diff --git a/pennylane/workflow/jacobian_products.py b/pennylane/workflow/jacobian_products.py index 688e786c1c5..44188773221 100644 --- a/pennylane/workflow/jacobian_products.py +++ b/pennylane/workflow/jacobian_products.py @@ -50,11 +50,13 @@ def _zero_jvp_single_shots(shots, tape): jvp = tuple(np.zeros(mp.shape(shots=shots), dtype=mp.numeric_type) for mp in tape.measurements) return jvp[0] if len(tape.measurements) == 1 else jvp + def _zero_jvp(tape): if tape.shots.has_partitioned_shots: return tuple(_zero_jvp_single_shots(s, tape) for s in tape.shots) return _zero_jvp_single_shots(tape.shots, tape) + def _compute_jvps(jacs, tangents, tapes): """Compute the jvps of multiple tapes, directly for a Jacobian and tangents.""" f = {True: qml.gradients.compute_jvp_multi, False: qml.gradients.compute_jvp_single} diff --git a/tests/gradients/core/test_jvp.py b/tests/gradients/core/test_jvp.py index bf694740b6a..8ffd1a7f9bd 100644 --- a/tests/gradients/core/test_jvp.py +++ b/tests/gradients/core/test_jvp.py @@ -17,6 +17,7 @@ import pennylane as qml from pennylane import numpy as np from pennylane.gradients import param_shift +from pennylane.measurements.shots import Shots _x = np.arange(12).reshape((2, 3, 2)) @@ -799,7 +800,8 @@ def cost_fn(params, tangent): class TestBatchJVP: """Tests for the batch JVP function""" - def test_one_tape_no_trainable_parameters(self): + @pytest.mark.parametrize("shots", [Shots(None), Shots(10), Shots([20, 10])]) + def test_one_tape_no_trainable_parameters(self, shots): """A tape with no trainable parameters will simply return None""" dev = qml.device("default.qubit", wires=2) @@ -808,14 +810,14 @@ def test_one_tape_no_trainable_parameters(self): qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - tape1 = qml.tape.QuantumScript.from_queue(q1) + tape1 = qml.tape.QuantumScript.from_queue(q1, shots=shots) with qml.queuing.AnnotatedQueue() as q2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - tape2 = qml.tape.QuantumScript.from_queue(q2) + tape2 = qml.tape.QuantumScript.from_queue(q2, shots=shots) tape1.trainable_params = {} tape2.trainable_params = {0, 1} @@ -823,16 +825,17 @@ def test_one_tape_no_trainable_parameters(self): tangents = [np.array([1.0, 1.0]), np.array([1.0, 1.0])] v_tapes, fn = qml.gradients.batch_jvp(tapes, tangents, param_shift) - assert len(v_tapes) == 4 # Even though there are 3 parameters, only two contribute # to the JVP, so only 2*2=4 quantum evals + assert len(v_tapes) == 4 res = fn(dev.execute(v_tapes)) assert qml.math.allclose(res[0], np.array(0.0)) assert res[1] is not None - def test_all_tapes_no_trainable_parameters(self): + @pytest.mark.parametrize("shots", [Shots(None), Shots(10), Shots([20, 10])]) + def test_all_tapes_no_trainable_parameters(self, shots): """If all tapes have no trainable parameters all outputs will be None""" with qml.queuing.AnnotatedQueue() as q1: @@ -840,14 +843,14 @@ def test_all_tapes_no_trainable_parameters(self): qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - tape1 = qml.tape.QuantumScript.from_queue(q1) + tape1 = qml.tape.QuantumScript.from_queue(q1, shots=shots) with qml.queuing.AnnotatedQueue() as q2: qml.RX(0.4, wires=0) qml.RX(0.6, wires=0) qml.CNOT(wires=[0, 1]) qml.expval(qml.PauliZ(0)) - tape2 = qml.tape.QuantumScript.from_queue(q2) + tape2 = qml.tape.QuantumScript.from_queue(q2, shots=shots) tape1.trainable_params = set() tape2.trainable_params = set() @@ -860,34 +863,6 @@ def test_all_tapes_no_trainable_parameters(self): assert qml.math.allclose(fn([])[0], np.array(0.0)) assert qml.math.allclose(fn([])[1], np.array(0.0)) - def test_some_tapes_no_trainable_parameters(self): - """If some tapes have no trainable parameters all outputs will be None""" - - with qml.queuing.AnnotatedQueue() as q1: - qml.RX(0.4, wires=0) - qml.expval(qml.PauliZ(0)) - - tape1 = qml.tape.QuantumScript.from_queue(q1) - with qml.queuing.AnnotatedQueue() as q2: - qml.RX(0.4, wires=0) - qml.RX(0.6, wires=0) - qml.CNOT(wires=[0, 1]) - qml.expval(qml.PauliZ(0)) - - tape2 = qml.tape.QuantumScript.from_queue(q2) - tape1.trainable_params = {0} - tape2.trainable_params = set() - - tapes = [tape1, tape2] - tangents = [np.array([0.7]), np.array([1.0, 0.0])] - - v_tapes, fn = qml.gradients.batch_jvp(tapes, tangents, param_shift) - - assert len(v_tapes) == 2 - ps_res = [np.cos(0.4+np.pi/2), np.cos(0.4-np.pi/2)] - assert qml.math.allclose(fn(ps_res)[0], -np.sin(0.4) * 0.7) - assert qml.math.allclose(fn(ps_res)[1], np.array(0.0)) - def test_zero_tangent(self): """A zero dy vector will return no tapes and a zero matrix""" dev = qml.device("default.qubit", wires=2) From d78e4e152fd507c79e443737eae4b6c08f5ab90f Mon Sep 17 00:00:00 2001 From: dwierichs Date: Thu, 5 Sep 2024 19:28:45 +0200 Subject: [PATCH 10/59] changelog --- doc/releases/changelog-dev.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 813ee6374e4..379d0c3a868 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -28,6 +28,9 @@

Bug fixes 🐛

+* Fix a bug where zero-valued JVPs were calculated wrongly in the presence of shot vectors. + [(#6219)](https://github.com/PennyLaneAI/pennylane/pull/6219) + * Fix Pytree serialization of operators with empty shot vectors: [(#6155)](https://github.com/PennyLaneAI/pennylane/pull/6155) @@ -46,3 +49,4 @@ Utkarsh Azad Jack Brown Christina Lee William Maxwell +David Wierichs From 91c198d1becd32d3bde255ba13f309ec431f3a12 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 13:52:32 -0400 Subject: [PATCH 11/59] more test cases in test_jax --- tests/interfaces/test_jax.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/interfaces/test_jax.py b/tests/interfaces/test_jax.py index 19accca6bca..1c12ba0b524 100644 --- a/tests/interfaces/test_jax.py +++ b/tests/interfaces/test_jax.py @@ -124,18 +124,20 @@ def cost(x, cache): no_shots = Shots(None) shots_10k = Shots(10000) shots_2_10k = Shots((10000, 10000)) -dev_def = DefaultQubit() +dev_def = DefaultQubit(seed=42) dev_ps = ParamShiftDerivativesDevice(seed=54353453) dev_ref = qml.device("reference.qubit") test_matrix = [ - ({"gradient_fn": param_shift}, shots_10k, DefaultQubit(seed=42)), # 0 - ({"gradient_fn": param_shift}, no_shots, dev_def), # 1 - ({"gradient_fn": "backprop"}, no_shots, dev_def), # 2 - ({"gradient_fn": "adjoint"}, no_shots, dev_def), # 3 - ({"gradient_fn": "adjoint", "device_vjp": True}, no_shots, dev_def), # 4 - ({"gradient_fn": "device"}, shots_2_10k, dev_ps), # 5 - ({"gradient_fn": param_shift}, no_shots, dev_ref), # 6 - ({"gradient_fn": param_shift}, shots_10k, dev_ref), # 7 + ({"gradient_fn": param_shift}, shots_10k, dev_def), # 0 + ({"gradient_fn": param_shift}, shots_2_10k, dev_def), # 1 + ({"gradient_fn": param_shift}, no_shots, dev_def), # 2 + ({"gradient_fn": "backprop"}, no_shots, dev_def), # 3 + ({"gradient_fn": "adjoint"}, no_shots, dev_def), # 4 + ({"gradient_fn": "adjoint", "device_vjp": True}, no_shots, dev_def), # 5 + ({"gradient_fn": "device"}, shots_2_10k, dev_ps), # 6 + ({"gradient_fn": param_shift}, no_shots, dev_ref), # 7 + ({"gradient_fn": param_shift}, shots_10k, dev_ref), # 8 + ({"gradient_fn": param_shift}, shots_2_10k, dev_ref), # 9 ] From 0fdcc03b0afdf56595b16aae4c645e642dccbf25 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 14:14:58 -0400 Subject: [PATCH 12/59] update test_jax_qnode --- tests/interfaces/test_jax_qnode.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/interfaces/test_jax_qnode.py b/tests/interfaces/test_jax_qnode.py index 3ea650f96e8..1b8694becc7 100644 --- a/tests/interfaces/test_jax_qnode.py +++ b/tests/interfaces/test_jax_qnode.py @@ -40,6 +40,7 @@ [qml.device("lightning.qubit", wires=5), "adjoint", True, True], [qml.device("lightning.qubit", wires=5), "adjoint", False, False], [qml.device("lightning.qubit", wires=5), "adjoint", True, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_device_and_diff_method = [ @@ -905,6 +906,8 @@ def test_postselection_differentiation(self, dev, diff_method, grad_on_execution if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.xfail("reference.qubit does not support postselection.") @qml.qnode( dev, @@ -1292,6 +1295,8 @@ def test_projector( elif diff_method == "spsa": gradient_kwargs = {"h": H_FOR_SPSA, "sampler_rng": np.random.default_rng(SEED_FOR_SPSA)} tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = jax.numpy.array(state) x, y = 0.765, -0.654 @@ -1367,7 +1372,7 @@ def circuit(x, y): jax.grad(circuit, argnums=[0])(x, y) @pytest.mark.parametrize("max_diff", [1, 2]) - def test_hamiltonian_expansion_analytic( + def test_split_non_commuting_analytic( self, dev, diff_method, grad_on_execution, max_diff, interface, device_vjp, mocker, tol ): """Test that the Hamiltonian is not expanded if there @@ -1385,6 +1390,8 @@ def test_hamiltonian_expansion_analytic( "sampler_rng": np.random.default_rng(SEED_FOR_SPSA), } tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.skip("The reference device does not support Hamiltonians") spy = mocker.spy(qml.transforms, "split_non_commuting") obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)] @@ -1445,6 +1452,13 @@ def test_hamiltonian_finite_shots( """Test that the Hamiltonian is correctly measured (and not expanded) if there are non-commuting groups and the number of shots is finite and the first and second order gradients are correctly evaluated""" + + if dev.name == "reference.qubit": + pytest.skip( + "Cannot added to a transform to the transform program in " + "preprocessing when using mocker.spy on it." + ) + gradient_kwargs = {} tol = 0.3 if diff_method in ("adjoint", "backprop", "finite-diff"): From 4cf6c2840a72736240e5dc83ceab12b9843a33d1 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 14:33:56 -0400 Subject: [PATCH 13/59] add reference.qubit to test_jax_jit_qnode --- tests/interfaces/test_jax_jit_qnode.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py index 4d959de446f..64c35d7b6fc 100644 --- a/tests/interfaces/test_jax_jit_qnode.py +++ b/tests/interfaces/test_jax_jit_qnode.py @@ -41,6 +41,7 @@ [qml.device("lightning.qubit", wires=5), "adjoint", False, False], [qml.device("lightning.qubit", wires=5), "adjoint", True, True], [qml.device("lightning.qubit", wires=5), "parameter-shift", False, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_qubit_device_and_diff_method = [ ["auto"] + inner_list for inner_list in qubit_device_and_diff_method @@ -1034,6 +1035,8 @@ def test_postselection_differentiation( pytest.xfail("gradient transforms have a different vjp shape convention") elif dev.name == "lightning.qubit": pytest.xfail("lightning qubit does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") @qml.qnode( dev, diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution @@ -1425,6 +1428,8 @@ def test_projector( elif diff_method == "spsa": gradient_kwargs = {"h": H_FOR_SPSA, "sampler_rng": np.random.default_rng(SEED_FOR_SPSA)} tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = jax.numpy.array(state) x, y = 0.765, -0.654 @@ -1508,6 +1513,8 @@ def test_hamiltonian_expansion_analytic( are non-commuting groups and the number of shots is None and the first and second order gradients are correctly evaluated""" gradient_kwargs = {} + if dev.name == "reference.qubit": + pytest.xfail("reference.qubit does not support hamiltonians") if dev.name == "param_shift.qubit": pytest.xfail("gradients transforms have a different vjp shape convention.") if diff_method == "adjoint": @@ -1834,6 +1841,9 @@ def test_hermitian( to different reasons, hence the parametrization in the test. """ # pylint: disable=unused-argument + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support Hermitians (sc-72911)") + if diff_method == "backprop": pytest.skip("Backpropagation is unsupported if shots > 0.") From df4c2a1eb6c9ab2573e74f6b7599b4037904955c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 14:54:48 -0400 Subject: [PATCH 14/59] fix test_spsa_gradient after autograd change --- .../finite_diff/test_spsa_gradient.py | 196 +++++++++--------- 1 file changed, 99 insertions(+), 97 deletions(-) diff --git a/tests/gradients/finite_diff/test_spsa_gradient.py b/tests/gradients/finite_diff/test_spsa_gradient.py index 667a9f00acc..ff222d5266c 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient.py +++ b/tests/gradients/finite_diff/test_spsa_gradient.py @@ -18,7 +18,7 @@ import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.gradients.spsa_gradient import _rademacher_sampler @@ -34,7 +34,7 @@ def coordinate_sampler(indices, num_params, idx, rng=None): intended way.""" # pylint: disable=unused-argument idx = idx % len(indices) - direction = np.zeros(num_params) + direction = pnp.zeros(num_params) direction[indices[idx]] = 1.0 return direction @@ -48,20 +48,20 @@ class TestRademacherSampler: def test_output_structure(self, ids, num): """Test that the sampled output has the right entries to be non-zero and attains the right values.""" - ids_mask = np.zeros(num, dtype=bool) + ids_mask = pnp.zeros(num, dtype=bool) ids_mask[ids] = True - rng = np.random.default_rng() + rng = pnp.random.default_rng() for _ in range(5): direction = _rademacher_sampler(ids, num, rng=rng) assert direction.shape == (num,) assert set(direction).issubset({0, -1, 1}) - assert np.allclose(np.abs(direction)[ids_mask], 1) - assert np.allclose(direction[~ids_mask], 0) + assert pnp.allclose(pnp.abs(direction)[ids_mask], 1) + assert pnp.allclose(direction[~ids_mask], 0) def test_call_with_third_arg(self): """Test that a third argument is ignored.""" - rng = np.random.default_rng() + rng = pnp.random.default_rng() _rademacher_sampler([0, 1, 2], 4, "ignored dummy", rng=rng) def test_differing_seeds(self): @@ -69,22 +69,22 @@ def test_differing_seeds(self): ids = [0, 1, 2, 3, 4] num = 5 seeds = [42, 43] - rng = np.random.default_rng(seeds[0]) + rng = pnp.random.default_rng(seeds[0]) first_direction = _rademacher_sampler(ids, num, rng=rng) - rng = np.random.default_rng(seeds[1]) + rng = pnp.random.default_rng(seeds[1]) second_direction = _rademacher_sampler(ids, num, rng=rng) - assert not np.allclose(first_direction, second_direction) + assert not pnp.allclose(first_direction, second_direction) def test_same_seeds(self): """Test that the output is the same for identical RNGs.""" ids = [0, 1, 2, 3, 4] num = 5 - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) first_direction = _rademacher_sampler(ids, num, rng=rng) - np.random.seed(0) # Setting the global seed should have no effect. - rng = np.random.default_rng(42) + pnp.random.seed(0) # Setting the global seed should have no effect. + rng = pnp.random.default_rng(42) second_direction = _rademacher_sampler(ids, num, rng=rng) - assert np.allclose(first_direction, second_direction) + assert pnp.allclose(first_direction, second_direction) @pytest.mark.parametrize( "ids, num", [(list(range(5)), 5), ([0, 2, 4], 5), ([0], 1), ([2, 3], 5)] @@ -93,18 +93,18 @@ def test_same_seeds(self): def test_mean_and_var(self, ids, num, N): """Test that the mean and variance of many produced samples are close to the theoretical values.""" - rng = np.random.default_rng(42) - ids_mask = np.zeros(num, dtype=bool) + rng = pnp.random.default_rng(42) + ids_mask = pnp.zeros(num, dtype=bool) ids_mask[ids] = True outputs = [_rademacher_sampler(ids, num, rng=rng) for _ in range(N)] # Test that the mean of non-zero entries is approximately right - assert np.allclose(np.mean(outputs, axis=0)[ids_mask], 0, atol=4 / np.sqrt(N)) + assert pnp.allclose(pnp.mean(outputs, axis=0)[ids_mask], 0, atol=4 / pnp.sqrt(N)) # Test that the variance of non-zero entries is approximately right - assert np.allclose(np.var(outputs, axis=0)[ids_mask], 1, atol=4 / N) + assert pnp.allclose(pnp.var(outputs, axis=0)[ids_mask], 1, atol=4 / N) # Test that the mean of zero entries is exactly 0, because all entries should be - assert np.allclose(np.mean(outputs, axis=0)[~ids_mask], 0, atol=1e-8) + assert pnp.allclose(pnp.mean(outputs, axis=0)[~ids_mask], 0, atol=1e-8) # Test that the variance of zero entries is exactly 0, because all entries are the same - assert np.allclose(np.var(outputs, axis=0)[~ids_mask], 0, atol=1e-8) + assert pnp.allclose(pnp.var(outputs, axis=0)[~ids_mask], 0, atol=1e-8) class TestSpsaGradient: @@ -116,14 +116,14 @@ def test_sampler_argument(self): def sampler_required_kwarg( indices, num_params, *args, rng ): # pylint:disable=unused-argument - direction = np.zeros(num_params) + direction = pnp.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction def sampler_required_arg_or_kwarg( indices, num_params, idx_rep, rng ): # pylint:disable=unused-argument - direction = np.zeros(num_params) + direction = pnp.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction @@ -131,7 +131,7 @@ def sampler_required_arg( indices, num_params, foo, idx_rep, rng, / ): # pylint:disable=unused-argument """This should fail since spsa_grad passes rng as a kwarg.""" - direction = np.zeros(num_params) + direction = pnp.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction @@ -141,7 +141,7 @@ def sampler_required_arg( results = [] for sampler in [sampler_required_arg_or_kwarg, sampler_required_kwarg]: - sampler_rng = np.random.default_rng(42) + sampler_rng = pnp.random.default_rng(42) tapes, proc_fn = spsa_grad( tape, sampler=sampler, num_directions=100, sampler_rng=sampler_rng ) @@ -149,7 +149,7 @@ def sampler_required_arg( res = qml.execute(tapes, dev) results.append(proc_fn(res)) - assert np.isclose(results[0], results[1], atol=0.1) + assert pnp.isclose(results[0], results[1], atol=0.1) err = "got some positional-only arguments passed as keyword arguments: 'rng'" with pytest.raises(TypeError, match=err): @@ -168,7 +168,7 @@ def circuit(param): expected_message = "The argument sampler_rng is expected to be a NumPy PRNG" with pytest.raises(ValueError, match=expected_message): - qml.grad(circuit)(np.array(1.0)) + qml.grad(circuit)(pnp.array(1.0)) def test_trainable_batched_tape_raises(self): """Test that an error is raised for a broadcasted/batched tape if the broadcasted @@ -197,12 +197,12 @@ def test_nontrainable_batched_tape(self): ] separate_tapes_and_fns = [spsa_grad(t) for t in separate_tapes] separate_grad = [_fn(dev.execute(_tapes)) for _tapes, _fn in separate_tapes_and_fns] - assert np.allclose(batched_grad, separate_grad) + assert pnp.allclose(batched_grad, separate_grad) def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) + psi = pnp.array([1, 0, 1, 0], requires_grad=False) / pnp.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -383,13 +383,13 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) - assert isinstance(result, np.ndarray) + assert isinstance(result, pnp.ndarray) assert result.shape == (4, 3) - assert np.allclose(result, 0) + assert pnp.allclose(result, 0) def test_all_zero_diff_methods_multiple_returns(self): """Test that the transform works correctly when the diff method for every parameter is @@ -402,7 +402,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -410,9 +410,9 @@ def circuit(params): assert len(result) == 2 for r, exp_shape in zip(result, [(3,), (4, 3)]): - assert isinstance(r, np.ndarray) + assert isinstance(r, pnp.ndarray) assert r.shape == exp_shape - assert np.allclose(r, 0) + assert pnp.allclose(r, 0) def test_y0(self): """Test that if first order finite differences is underlying the SPSA, then @@ -449,7 +449,7 @@ def test_y0_provided(self): def test_independent_parameters(self): """Test the case where expectation values are independent of some parameters. For those parameters, the gradient should be evaluated to zero without executing the device.""" - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) dev = qml.device("default.qubit", wires=2) with qml.queuing.AnnotatedQueue() as q1: @@ -481,10 +481,10 @@ def test_independent_parameters(self): # Because there is just a single gate parameter varied for these tapes, the gradient # approximation will actually be as good as finite differences. - exp = -np.sin(1) + exp = -pnp.sin(1) - assert np.allclose(j1, [exp, 0]) - assert np.allclose(j2, [0, exp]) + assert pnp.allclose(j1, [exp, 0]) + assert pnp.allclose(j2, [0, exp]) def test_output_shape_matches_qnode(self): """Test that the transform output shape matches that of the QNode.""" @@ -514,7 +514,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = np.random.rand(3) + x = pnp.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c)(x)) for c in circuits] @@ -576,7 +576,7 @@ class DeviceSupportingSpecialObservable(DefaultQubitLegacy): @staticmethod def _asarray(arr, dtype=None): - return np.array(arr, dtype=dtype) + return pnp.array(arr, dtype=dtype) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -603,9 +603,11 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + par = pnp.array(0.2, requires_grad=True) + assert qml.math.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert qml.math.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -690,8 +692,8 @@ def test_single_expectation_value(self, approx_order, strategy, validate, tol): assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) - assert np.allclose(res, expected, atol=tol, rtol=0) + expected = pnp.array([[-pnp.sin(y) * pnp.sin(x), pnp.cos(y) * pnp.cos(x)]]) + assert pnp.allclose(res, expected, atol=tol, rtol=0) def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -734,8 +736,8 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) - assert np.allclose(res, expected, atol=tol, rtol=0) + expected = pnp.array([[-pnp.sin(y) * pnp.sin(x), pnp.cos(y) * pnp.cos(x)]]) + assert pnp.allclose(res, expected, atol=tol, rtol=0) def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -778,9 +780,9 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = [0, np.cos(y) * np.cos(x)] + expected = [0, pnp.cos(y) * pnp.cos(x)] - assert np.allclose(res, expected, atol=tol, rtol=0) + assert pnp.allclose(res, expected, atol=tol, rtol=0) def test_multiple_expectation_value_with_argnum_one(self, approx_order, strategy, validate): """Tests correct output shape and evaluation for a tape @@ -816,9 +818,9 @@ def test_multiple_expectation_value_with_argnum_one(self, approx_order, strategy assert isinstance(res, tuple) assert isinstance(res[0], tuple) - assert np.allclose(res[0][0], 0) + assert pnp.allclose(res[0][0], 0) assert isinstance(res[1], tuple) - assert np.allclose(res[1][0], 0) + assert pnp.allclose(res[1][0], 0) def test_multiple_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -855,13 +857,13 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate, tol assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) + assert pnp.allclose(res[0], [-pnp.sin(x), 0], atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert np.allclose(res[1], [0, np.cos(y)], atol=tol, rtol=0) + assert pnp.allclose(res[1], [0, pnp.cos(y)], atol=tol, rtol=0) assert isinstance(res[1][0], numpy.ndarray) assert isinstance(res[1][1], numpy.ndarray) @@ -900,13 +902,13 @@ def test_var_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) + assert pnp.allclose(res[0], [-pnp.sin(x), 0], atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert np.allclose(res[1], [0, -2 * np.cos(y) * np.sin(y)], atol=tol, rtol=0) + assert pnp.allclose(res[1], [0, -2 * pnp.cos(y) * pnp.sin(y)], atol=tol, rtol=0) assert isinstance(res[1][0], numpy.ndarray) assert isinstance(res[1][1], numpy.ndarray) @@ -945,32 +947,32 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert np.allclose(res[0][0], -np.sin(x), atol=tol, rtol=0) + assert pnp.allclose(res[0][0], -pnp.sin(x), atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) - assert np.allclose(res[0][1], 0, atol=tol, rtol=0) + assert pnp.allclose(res[0][1], 0, atol=tol, rtol=0) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert np.allclose( + assert pnp.allclose( res[1][0], [ - -(np.cos(y / 2) ** 2 * np.sin(x)) / 2, - -(np.sin(x) * np.sin(y / 2) ** 2) / 2, - (np.sin(x) * np.sin(y / 2) ** 2) / 2, - (np.cos(y / 2) ** 2 * np.sin(x)) / 2, + -(pnp.cos(y / 2) ** 2 * pnp.sin(x)) / 2, + -(pnp.sin(x) * pnp.sin(y / 2) ** 2) / 2, + (pnp.sin(x) * pnp.sin(y / 2) ** 2) / 2, + (pnp.cos(y / 2) ** 2 * pnp.sin(x)) / 2, ], atol=tol, rtol=0, ) assert isinstance(res[1][0], numpy.ndarray) - assert np.allclose( + assert pnp.allclose( res[1][1], [ - -(np.cos(x / 2) ** 2 * np.sin(y)) / 2, - (np.cos(x / 2) ** 2 * np.sin(y)) / 2, - (np.sin(x / 2) ** 2 * np.sin(y)) / 2, - -(np.sin(x / 2) ** 2 * np.sin(y)) / 2, + -(pnp.cos(x / 2) ** 2 * pnp.sin(y)) / 2, + (pnp.cos(x / 2) ** 2 * pnp.sin(y)) / 2, + (pnp.sin(x / 2) ** 2 * pnp.sin(y)) / 2, + -(pnp.sin(x / 2) ** 2 * pnp.sin(y)) / 2, ], atol=tol, rtol=0, @@ -991,8 +993,8 @@ def test_autograd(self, dev_name, sampler, num_directions, atol): can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) - rng = np.random.default_rng(42) + params = pnp.array([0.543, -0.654], requires_grad=True) + rng = pnp.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1006,21 +1008,21 @@ def cost_fn(x): tapes, fn = spsa_grad( tape, n=1, num_directions=num_directions, sampler=sampler, sampler_rng=rng ) - jac = np.array(fn(execute_fn(tapes))) + jac = pnp.array(fn(execute_fn(tapes))) if sampler is coordinate_sampler: jac *= 2 return jac res = qml.jacobian(cost_fn)(params) x, y = params - expected = np.array( + expected = pnp.array( [ - [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], - [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], + [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], + [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], ] ) - assert np.allclose(res, expected, atol=atol, rtol=0) + assert pnp.allclose(res, expected, atol=atol, rtol=0) @pytest.mark.autograd @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.autograd"]) @@ -1029,8 +1031,8 @@ def test_autograd_ragged(self, dev_name, sampler, num_directions, atol): of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) - rng = np.random.default_rng(42) + params = pnp.array([0.543, -0.654], requires_grad=True) + rng = pnp.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1052,8 +1054,8 @@ def cost_fn(x): x, y = params res = qml.jacobian(cost_fn)(params)[0] - expected = np.array([-np.cos(x) * np.cos(y) / 2, np.sin(x) * np.sin(y) / 2]) - assert np.allclose(res, expected, atol=atol, rtol=0) + expected = pnp.array([-pnp.cos(x) * pnp.cos(y) / 2, pnp.sin(x) * pnp.sin(y) / 2]) + assert pnp.allclose(res, expected, atol=atol, rtol=0) @pytest.mark.tf @pytest.mark.slow @@ -1066,7 +1068,7 @@ def test_tf(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = tf.Variable([0.543, -0.654], dtype=tf.float64) - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) with tf.GradientTape(persistent=True) as t: with qml.queuing.AnnotatedQueue() as q: @@ -1090,13 +1092,13 @@ def test_tf(self, dev_name, sampler, num_directions, atol): res_0 = t.jacobian(jac_0, params) res_1 = t.jacobian(jac_1, params) - expected = np.array( + expected = pnp.array( [ - [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], - [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], + [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], + [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], ] ) - assert np.allclose([res_0, res_1], expected, atol=atol, rtol=0) + assert pnp.allclose([res_0, res_1], expected, atol=atol, rtol=0) @pytest.mark.tf @pytest.mark.slow @@ -1109,7 +1111,7 @@ def test_tf_ragged(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = tf.Variable([0.543, -0.654], dtype=tf.float64) - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) with tf.GradientTape(persistent=True) as t: with qml.queuing.AnnotatedQueue() as q: @@ -1133,9 +1135,9 @@ def test_tf_ragged(self, dev_name, sampler, num_directions, atol): res_01 = t.jacobian(jac_01, params) - expected = np.array([-np.cos(x) * np.cos(y) / 2, np.sin(x) * np.sin(y) / 2]) + expected = pnp.array([-pnp.cos(x) * pnp.cos(y) / 2, pnp.sin(x) * pnp.sin(y) / 2]) - assert np.allclose(res_01[0], expected, atol=atol, rtol=0) + assert pnp.allclose(res_01[0], expected, atol=atol, rtol=0) @pytest.mark.torch @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.torch"]) @@ -1147,7 +1149,7 @@ def test_torch(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = torch.tensor([0.543, -0.654], dtype=torch.float64, requires_grad=True) - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) def cost_fn(params): with qml.queuing.AnnotatedQueue() as q: @@ -1169,15 +1171,15 @@ def cost_fn(params): x, y = params.detach().numpy() - expected = np.array( + expected = pnp.array( [ - [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], - [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], + [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], + [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], ] ) - assert np.allclose(hess[0].detach().numpy(), expected[0], atol=atol, rtol=0) - assert np.allclose(hess[1].detach().numpy(), expected[1], atol=atol, rtol=0) + assert pnp.allclose(hess[0].detach().numpy(), expected[0], atol=atol, rtol=0) + assert pnp.allclose(hess[1].detach().numpy(), expected[1], atol=atol, rtol=0) @pytest.mark.jax @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.jax"]) @@ -1190,7 +1192,7 @@ def test_jax(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = jnp.array([0.543, -0.654]) - rng = np.random.default_rng(42) + rng = pnp.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1212,10 +1214,10 @@ def cost_fn(x): res = jax.jacobian(cost_fn)(params) assert isinstance(res, tuple) x, y = params - expected = np.array( + expected = pnp.array( [ - [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], - [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], + [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], + [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], ] ) - assert np.allclose(res, expected, atol=atol, rtol=0) + assert pnp.allclose(res, expected, atol=atol, rtol=0) From 54b46e63623af5a1ac00500bcd164861b3e3a251 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 15:11:40 -0400 Subject: [PATCH 15/59] fix more spsa test --- tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py index e2c57bfd714..03bfc1ddefe 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py +++ b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py @@ -499,8 +499,10 @@ def reference_qnode(x): return qml.expval(qml.PauliZ(wires=0)) par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert np.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) From 7d48f9a9793c221ee283972bc38ea9e1afa60587 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 15:40:46 -0400 Subject: [PATCH 16/59] add reference.qubit to tensorflow tests --- pennylane/devices/reference_qubit.py | 2 +- tests/interfaces/test_tensorflow.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index d2631c03ceb..629634629ee 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -57,7 +57,7 @@ def simulate(tape: qml.tape.QuantumTape, seed=None) -> qml.typing.Result: # 2) apply all the operations for op in tape.operations: op_mat = op.matrix(wire_order=tape.wires) - state = op_mat @ state + state = qml.math.matmul(op_mat, state) # 3) perform measurements # note that shots are pulled from the tape, not from the device diff --git a/tests/interfaces/test_tensorflow.py b/tests/interfaces/test_tensorflow.py index 11a54575cfa..b9c5b1282b5 100644 --- a/tests/interfaces/test_tensorflow.py +++ b/tests/interfaces/test_tensorflow.py @@ -118,6 +118,16 @@ def cost(x, cache): ({"gradient_fn": "backprop", "interface": "tf-autograph"}, None, DefaultQubit()), # 6 ({"gradient_fn": "adjoint", "interface": "tf-autograph"}, None, DefaultQubit()), # 7 ({"gradient_fn": "adjoint", "interface": "tf", "device_vjp": True}, None, DefaultQubit()), # 8 + ( + {"gradient_fn": param_shift, "interface": "tensorflow"}, + None, + qml.device("reference.qubit"), + ), # 9 + ( + {"gradient_fn": param_shift, "interface": "tensorflow"}, + 100000, + qml.device("reference.qubit"), + ), # 10 ] From 0025281db2f86730f7b1957c238ed6daddb1133c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 16:04:10 -0400 Subject: [PATCH 17/59] fix failing test in test_lightning_qubit --- tests/devices/test_lightning_qubit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/devices/test_lightning_qubit.py b/tests/devices/test_lightning_qubit.py index 6fe2cac85b5..5d61d6be202 100644 --- a/tests/devices/test_lightning_qubit.py +++ b/tests/devices/test_lightning_qubit.py @@ -130,7 +130,10 @@ def circuit(x): expected_dtype = c_dtype else: expected_dtype = np.float64 if c_dtype == np.complex128 else np.float32 - if isinstance(res, np.ndarray): + if isinstance(res, np.ndarray) and res.size > 1: assert res.dtype == expected_dtype + elif isinstance(res, np.ndarray): + # default dtype of np.array() of a regular float + assert res.dtype == np.float64 else: assert isinstance(res, float) From c62ce82fa3a61029fef8f91bd7b969ffce40160d Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 16:51:21 -0400 Subject: [PATCH 18/59] Add _to_autograd to autograd execute --- pennylane/workflow/interfaces/autograd.py | 17 ++++- .../finite_diff/test_spsa_gradient.py | 76 ++++++++++--------- .../test_spsa_gradient_shot_vec.py | 70 ++++++++--------- 3 files changed, 91 insertions(+), 72 deletions(-) diff --git a/pennylane/workflow/interfaces/autograd.py b/pennylane/workflow/interfaces/autograd.py index 9452af31854..cb5731ddc8b 100644 --- a/pennylane/workflow/interfaces/autograd.py +++ b/pennylane/workflow/interfaces/autograd.py @@ -147,6 +147,21 @@ def autograd_execute( return _execute(parameters, tuple(tapes), execute_fn, jpc) +def _to_autograd(result: qml.typing.ResultBatch) -> qml.typing.ResultBatch: + """Converts an arbitrary result batch to one with autograd arrays. + Args: + result (ResultBatch): a nested structure of lists, tuples, dicts, and numpy arrays + Returns: + ResultBatch: a nested structure of tuples, dicts, and jax arrays + """ + if isinstance(result, dict): + return result + # pylint: disable=no-member + if isinstance(result, (list, tuple, autograd.builtins.tuple, autograd.builtins.list)): + return tuple(_to_autograd(r) for r in result) + return autograd.numpy.array(result) + + @autograd.extend.primitive def _execute( parameters, @@ -165,7 +180,7 @@ def _execute( for the input tapes. """ - return execute_fn(tapes) + return _to_autograd(execute_fn(tapes)) # pylint: disable=unused-argument diff --git a/tests/gradients/finite_diff/test_spsa_gradient.py b/tests/gradients/finite_diff/test_spsa_gradient.py index 667a9f00acc..bf1dd1f3d0b 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient.py +++ b/tests/gradients/finite_diff/test_spsa_gradient.py @@ -14,11 +14,11 @@ """ Tests for the gradients.spsa_gradient module. """ -import numpy +import numpy as np import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.gradients.spsa_gradient import _rademacher_sampler @@ -168,7 +168,7 @@ def circuit(param): expected_message = "The argument sampler_rng is expected to be a NumPy PRNG" with pytest.raises(ValueError, match=expected_message): - qml.grad(circuit)(np.array(1.0)) + qml.grad(circuit)(pnp.array(1.0)) def test_trainable_batched_tape_raises(self): """Test that an error is raised for a broadcasted/batched tape if the broadcasted @@ -202,7 +202,7 @@ def test_nontrainable_batched_tape(self): def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) + psi = pnp.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -227,10 +227,10 @@ def test_non_differentiable_error(self): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == (4,) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == (4,) @pytest.mark.parametrize("num_directions", [1, 10]) @@ -252,8 +252,8 @@ def test_independent_parameter(self, num_directions, mocker): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[0], np.ndarray) + assert isinstance(res[1], np.ndarray) # 2 tapes per direction because the default strategy for SPSA is "center" assert len(spy.call_args_list) == num_directions @@ -282,7 +282,7 @@ def test_no_trainable_params_tape(self): res = post_processing(qml.execute(g_tapes, dev, None)) assert g_tapes == [] - assert isinstance(res, numpy.ndarray) + assert isinstance(res, np.ndarray) assert res.shape == (0,) def test_no_trainable_params_multiple_return_tape(self): @@ -383,7 +383,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -402,7 +402,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -514,7 +514,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = np.random.rand(3) + x = pnp.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c)(x)) for c in circuits] @@ -576,7 +576,7 @@ class DeviceSupportingSpecialObservable(DefaultQubitLegacy): @staticmethod def _asarray(arr, dtype=None): - return np.array(arr, dtype=dtype) + return pnp.array(arr, dtype=dtype) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -603,9 +603,11 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + par = pnp.array(0.2, requires_grad=True) + assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert np.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -684,10 +686,10 @@ def test_single_expectation_value(self, approx_order, strategy, validate, tol): # 1 / num_params here. res = tuple(qml.math.convert_like(r * 2, r) for r in res) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) @@ -728,10 +730,10 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, # 1 / num_params here. res = tuple(qml.math.convert_like(r * 2, r) for r in res) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) @@ -772,10 +774,10 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () expected = [0, np.cos(y) * np.cos(x)] @@ -856,14 +858,14 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate, tol assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 assert np.allclose(res[1], [0, np.cos(y)], atol=tol, rtol=0) - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) def test_var_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -901,14 +903,14 @@ def test_var_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 assert np.allclose(res[1], [0, -2 * np.cos(y) * np.sin(y)], atol=tol, rtol=0) - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) def test_prob_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -946,9 +948,9 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 assert np.allclose(res[0][0], -np.sin(x), atol=tol, rtol=0) - assert isinstance(res[0][0], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) assert np.allclose(res[0][1], 0, atol=tol, rtol=0) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 @@ -963,7 +965,7 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): atol=tol, rtol=0, ) - assert isinstance(res[1][0], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) assert np.allclose( res[1][1], [ @@ -975,7 +977,7 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): atol=tol, rtol=0, ) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][1], np.ndarray) @pytest.mark.parametrize( @@ -991,7 +993,7 @@ def test_autograd(self, dev_name, sampler, num_directions, atol): can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): @@ -1006,7 +1008,7 @@ def cost_fn(x): tapes, fn = spsa_grad( tape, n=1, num_directions=num_directions, sampler=sampler, sampler_rng=rng ) - jac = np.array(fn(execute_fn(tapes))) + jac = pnp.array(fn(execute_fn(tapes))) if sampler is coordinate_sampler: jac *= 2 return jac @@ -1029,7 +1031,7 @@ def test_autograd_ragged(self, dev_name, sampler, num_directions, atol): of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): diff --git a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py index e2c57bfd714..2187106f443 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py +++ b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py @@ -14,11 +14,11 @@ """ Tests for the gradients.spsa_gradient module using shot vectors. """ -import numpy +import numpy as np import pytest import pennylane as qml -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.measurements import Shots @@ -49,7 +49,7 @@ class TestSpsaGradient: def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) + psi = pnp.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -78,10 +78,10 @@ def test_non_differentiable_error(self): for res in all_res: assert isinstance(res, tuple) - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == (4,) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == (4,) @pytest.mark.parametrize("num_directions", [1, 6]) @@ -107,8 +107,8 @@ def test_independent_parameter(self, num_directions, mocker): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[0], np.ndarray) + assert isinstance(res[1], np.ndarray) # 2 tapes per direction because the default strategy for SPSA is "center" assert len(spy.call_args_list) == num_directions @@ -139,7 +139,7 @@ def test_no_trainable_params_tape(self): for res in all_res: assert g_tapes == [] - assert isinstance(res, numpy.ndarray) + assert isinstance(res, np.ndarray) assert res.shape == (0,) def test_no_trainable_params_multiple_return_tape(self): @@ -244,7 +244,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) grad_fn = spsa_grad(circuit, h=h_val, sampler_rng=rng) all_result = grad_fn(params) @@ -269,7 +269,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = np.array([0.5, 0.5, 0.5], requires_grad=True) + params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) grad_fn = spsa_grad(circuit, h=h_val, sampler_rng=rng) all_result = grad_fn(params) @@ -416,7 +416,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = np.random.rand(3) + x = pnp.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c, h=h_val)(x)) for c in circuits] @@ -498,9 +498,11 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par)) - assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) + par = pnp.array(0.2, requires_grad=True) + assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) + assert np.isclose( + qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() + ) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -586,10 +588,10 @@ def test_single_expectation_value(self, approx_order, strategy, validate): assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points, but the tape execution @@ -635,10 +637,10 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points, but the tape execution @@ -689,10 +691,10 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res, tuple) assert len(res) == 2 - assert isinstance(res[0], numpy.ndarray) + assert isinstance(res[0], np.ndarray) assert res[0].shape == () - assert isinstance(res[1], numpy.ndarray) + assert isinstance(res[1], np.ndarray) assert res[1].shape == () # The coordinate_sampler produces the right evaluation points and there is just one @@ -783,13 +785,13 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -837,13 +839,13 @@ def test_var_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -892,13 +894,13 @@ def test_prob_expectation_values(self, approx_order, strategy, validate): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert isinstance(res[0][0], numpy.ndarray) - assert isinstance(res[0][1], numpy.ndarray) + assert isinstance(res[0][0], np.ndarray) + assert isinstance(res[0][1], np.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert isinstance(res[1][0], numpy.ndarray) - assert isinstance(res[1][1], numpy.ndarray) + assert isinstance(res[1][0], np.ndarray) + assert isinstance(res[1][1], np.ndarray) # The coordinate_sampler produces the right evaluation points, but the tape execution # results are averaged instead of added, so that we need to revert the prefactor @@ -945,7 +947,7 @@ def test_autograd(self, dev_name, approx_order, strategy): can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2, shots=many_shots_shot_vector) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): @@ -991,7 +993,7 @@ def test_autograd_ragged(self, dev_name, approx_order, strategy): of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2, shots=many_shots_shot_vector) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) rng = np.random.default_rng(42) def cost_fn(x): From 58e5d522a135efc81f97735698220115c0754740 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 16:52:40 -0400 Subject: [PATCH 19/59] move changes to a different branch --- pennylane/workflow/interfaces/autograd.py | 17 +- .../finite_diff/test_spsa_gradient.py | 196 +++++++++--------- .../test_spsa_gradient_shot_vec.py | 6 +- 3 files changed, 100 insertions(+), 119 deletions(-) diff --git a/pennylane/workflow/interfaces/autograd.py b/pennylane/workflow/interfaces/autograd.py index cb5731ddc8b..9452af31854 100644 --- a/pennylane/workflow/interfaces/autograd.py +++ b/pennylane/workflow/interfaces/autograd.py @@ -147,21 +147,6 @@ def autograd_execute( return _execute(parameters, tuple(tapes), execute_fn, jpc) -def _to_autograd(result: qml.typing.ResultBatch) -> qml.typing.ResultBatch: - """Converts an arbitrary result batch to one with autograd arrays. - Args: - result (ResultBatch): a nested structure of lists, tuples, dicts, and numpy arrays - Returns: - ResultBatch: a nested structure of tuples, dicts, and jax arrays - """ - if isinstance(result, dict): - return result - # pylint: disable=no-member - if isinstance(result, (list, tuple, autograd.builtins.tuple, autograd.builtins.list)): - return tuple(_to_autograd(r) for r in result) - return autograd.numpy.array(result) - - @autograd.extend.primitive def _execute( parameters, @@ -180,7 +165,7 @@ def _execute( for the input tapes. """ - return _to_autograd(execute_fn(tapes)) + return execute_fn(tapes) # pylint: disable=unused-argument diff --git a/tests/gradients/finite_diff/test_spsa_gradient.py b/tests/gradients/finite_diff/test_spsa_gradient.py index ff222d5266c..667a9f00acc 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient.py +++ b/tests/gradients/finite_diff/test_spsa_gradient.py @@ -18,7 +18,7 @@ import pytest import pennylane as qml -from pennylane import numpy as pnp +from pennylane import numpy as np from pennylane.devices import DefaultQubitLegacy from pennylane.gradients import spsa_grad from pennylane.gradients.spsa_gradient import _rademacher_sampler @@ -34,7 +34,7 @@ def coordinate_sampler(indices, num_params, idx, rng=None): intended way.""" # pylint: disable=unused-argument idx = idx % len(indices) - direction = pnp.zeros(num_params) + direction = np.zeros(num_params) direction[indices[idx]] = 1.0 return direction @@ -48,20 +48,20 @@ class TestRademacherSampler: def test_output_structure(self, ids, num): """Test that the sampled output has the right entries to be non-zero and attains the right values.""" - ids_mask = pnp.zeros(num, dtype=bool) + ids_mask = np.zeros(num, dtype=bool) ids_mask[ids] = True - rng = pnp.random.default_rng() + rng = np.random.default_rng() for _ in range(5): direction = _rademacher_sampler(ids, num, rng=rng) assert direction.shape == (num,) assert set(direction).issubset({0, -1, 1}) - assert pnp.allclose(pnp.abs(direction)[ids_mask], 1) - assert pnp.allclose(direction[~ids_mask], 0) + assert np.allclose(np.abs(direction)[ids_mask], 1) + assert np.allclose(direction[~ids_mask], 0) def test_call_with_third_arg(self): """Test that a third argument is ignored.""" - rng = pnp.random.default_rng() + rng = np.random.default_rng() _rademacher_sampler([0, 1, 2], 4, "ignored dummy", rng=rng) def test_differing_seeds(self): @@ -69,22 +69,22 @@ def test_differing_seeds(self): ids = [0, 1, 2, 3, 4] num = 5 seeds = [42, 43] - rng = pnp.random.default_rng(seeds[0]) + rng = np.random.default_rng(seeds[0]) first_direction = _rademacher_sampler(ids, num, rng=rng) - rng = pnp.random.default_rng(seeds[1]) + rng = np.random.default_rng(seeds[1]) second_direction = _rademacher_sampler(ids, num, rng=rng) - assert not pnp.allclose(first_direction, second_direction) + assert not np.allclose(first_direction, second_direction) def test_same_seeds(self): """Test that the output is the same for identical RNGs.""" ids = [0, 1, 2, 3, 4] num = 5 - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) first_direction = _rademacher_sampler(ids, num, rng=rng) - pnp.random.seed(0) # Setting the global seed should have no effect. - rng = pnp.random.default_rng(42) + np.random.seed(0) # Setting the global seed should have no effect. + rng = np.random.default_rng(42) second_direction = _rademacher_sampler(ids, num, rng=rng) - assert pnp.allclose(first_direction, second_direction) + assert np.allclose(first_direction, second_direction) @pytest.mark.parametrize( "ids, num", [(list(range(5)), 5), ([0, 2, 4], 5), ([0], 1), ([2, 3], 5)] @@ -93,18 +93,18 @@ def test_same_seeds(self): def test_mean_and_var(self, ids, num, N): """Test that the mean and variance of many produced samples are close to the theoretical values.""" - rng = pnp.random.default_rng(42) - ids_mask = pnp.zeros(num, dtype=bool) + rng = np.random.default_rng(42) + ids_mask = np.zeros(num, dtype=bool) ids_mask[ids] = True outputs = [_rademacher_sampler(ids, num, rng=rng) for _ in range(N)] # Test that the mean of non-zero entries is approximately right - assert pnp.allclose(pnp.mean(outputs, axis=0)[ids_mask], 0, atol=4 / pnp.sqrt(N)) + assert np.allclose(np.mean(outputs, axis=0)[ids_mask], 0, atol=4 / np.sqrt(N)) # Test that the variance of non-zero entries is approximately right - assert pnp.allclose(pnp.var(outputs, axis=0)[ids_mask], 1, atol=4 / N) + assert np.allclose(np.var(outputs, axis=0)[ids_mask], 1, atol=4 / N) # Test that the mean of zero entries is exactly 0, because all entries should be - assert pnp.allclose(pnp.mean(outputs, axis=0)[~ids_mask], 0, atol=1e-8) + assert np.allclose(np.mean(outputs, axis=0)[~ids_mask], 0, atol=1e-8) # Test that the variance of zero entries is exactly 0, because all entries are the same - assert pnp.allclose(pnp.var(outputs, axis=0)[~ids_mask], 0, atol=1e-8) + assert np.allclose(np.var(outputs, axis=0)[~ids_mask], 0, atol=1e-8) class TestSpsaGradient: @@ -116,14 +116,14 @@ def test_sampler_argument(self): def sampler_required_kwarg( indices, num_params, *args, rng ): # pylint:disable=unused-argument - direction = pnp.zeros(num_params) + direction = np.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction def sampler_required_arg_or_kwarg( indices, num_params, idx_rep, rng ): # pylint:disable=unused-argument - direction = pnp.zeros(num_params) + direction = np.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction @@ -131,7 +131,7 @@ def sampler_required_arg( indices, num_params, foo, idx_rep, rng, / ): # pylint:disable=unused-argument """This should fail since spsa_grad passes rng as a kwarg.""" - direction = pnp.zeros(num_params) + direction = np.zeros(num_params) direction[indices] = rng.choice([-1, 0, 1], size=len(indices)) return direction @@ -141,7 +141,7 @@ def sampler_required_arg( results = [] for sampler in [sampler_required_arg_or_kwarg, sampler_required_kwarg]: - sampler_rng = pnp.random.default_rng(42) + sampler_rng = np.random.default_rng(42) tapes, proc_fn = spsa_grad( tape, sampler=sampler, num_directions=100, sampler_rng=sampler_rng ) @@ -149,7 +149,7 @@ def sampler_required_arg( res = qml.execute(tapes, dev) results.append(proc_fn(res)) - assert pnp.isclose(results[0], results[1], atol=0.1) + assert np.isclose(results[0], results[1], atol=0.1) err = "got some positional-only arguments passed as keyword arguments: 'rng'" with pytest.raises(TypeError, match=err): @@ -168,7 +168,7 @@ def circuit(param): expected_message = "The argument sampler_rng is expected to be a NumPy PRNG" with pytest.raises(ValueError, match=expected_message): - qml.grad(circuit)(pnp.array(1.0)) + qml.grad(circuit)(np.array(1.0)) def test_trainable_batched_tape_raises(self): """Test that an error is raised for a broadcasted/batched tape if the broadcasted @@ -197,12 +197,12 @@ def test_nontrainable_batched_tape(self): ] separate_tapes_and_fns = [spsa_grad(t) for t in separate_tapes] separate_grad = [_fn(dev.execute(_tapes)) for _tapes, _fn in separate_tapes_and_fns] - assert pnp.allclose(batched_grad, separate_grad) + assert np.allclose(batched_grad, separate_grad) def test_non_differentiable_error(self): """Test error raised if attempting to differentiate with respect to a non-differentiable argument""" - psi = pnp.array([1, 0, 1, 0], requires_grad=False) / pnp.sqrt(2) + psi = np.array([1, 0, 1, 0], requires_grad=False) / np.sqrt(2) with qml.queuing.AnnotatedQueue() as q: qml.StatePrep(psi, wires=[0, 1]) @@ -383,13 +383,13 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.probs([2, 3]) - params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) + params = np.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) - assert isinstance(result, pnp.ndarray) + assert isinstance(result, np.ndarray) assert result.shape == (4, 3) - assert pnp.allclose(result, 0) + assert np.allclose(result, 0) def test_all_zero_diff_methods_multiple_returns(self): """Test that the transform works correctly when the diff method for every parameter is @@ -402,7 +402,7 @@ def circuit(params): qml.Rot(*params, wires=0) return qml.expval(qml.PauliZ(wires=2)), qml.probs([2, 3]) - params = pnp.array([0.5, 0.5, 0.5], requires_grad=True) + params = np.array([0.5, 0.5, 0.5], requires_grad=True) result = spsa_grad(circuit)(params) @@ -410,9 +410,9 @@ def circuit(params): assert len(result) == 2 for r, exp_shape in zip(result, [(3,), (4, 3)]): - assert isinstance(r, pnp.ndarray) + assert isinstance(r, np.ndarray) assert r.shape == exp_shape - assert pnp.allclose(r, 0) + assert np.allclose(r, 0) def test_y0(self): """Test that if first order finite differences is underlying the SPSA, then @@ -449,7 +449,7 @@ def test_y0_provided(self): def test_independent_parameters(self): """Test the case where expectation values are independent of some parameters. For those parameters, the gradient should be evaluated to zero without executing the device.""" - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) dev = qml.device("default.qubit", wires=2) with qml.queuing.AnnotatedQueue() as q1: @@ -481,10 +481,10 @@ def test_independent_parameters(self): # Because there is just a single gate parameter varied for these tapes, the gradient # approximation will actually be as good as finite differences. - exp = -pnp.sin(1) + exp = -np.sin(1) - assert pnp.allclose(j1, [exp, 0]) - assert pnp.allclose(j2, [0, exp]) + assert np.allclose(j1, [exp, 0]) + assert np.allclose(j2, [0, exp]) def test_output_shape_matches_qnode(self): """Test that the transform output shape matches that of the QNode.""" @@ -514,7 +514,7 @@ def cost6(x): qml.Rot(*x, wires=0) return qml.probs([0, 1]), qml.probs([2, 3]) - x = pnp.random.rand(3) + x = np.random.rand(3) circuits = [qml.QNode(cost, dev) for cost in (cost1, cost2, cost3, cost4, cost5, cost6)] transform = [qml.math.shape(spsa_grad(c)(x)) for c in circuits] @@ -576,7 +576,7 @@ class DeviceSupportingSpecialObservable(DefaultQubitLegacy): @staticmethod def _asarray(arr, dtype=None): - return pnp.array(arr, dtype=dtype) + return np.array(arr, dtype=dtype) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -603,11 +603,9 @@ def reference_qnode(x): qml.RY(x, wires=0) return qml.expval(qml.PauliZ(wires=0)) - par = pnp.array(0.2, requires_grad=True) - assert qml.math.isclose(qnode(par).item().val, reference_qnode(par).item()) - assert qml.math.isclose( - qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() - ) + par = np.array(0.2, requires_grad=True) + assert np.isclose(qnode(par).item().val, reference_qnode(par)) + assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) @pytest.mark.parametrize("approx_order", [2, 4]) @@ -692,8 +690,8 @@ def test_single_expectation_value(self, approx_order, strategy, validate, tol): assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = pnp.array([[-pnp.sin(y) * pnp.sin(x), pnp.cos(y) * pnp.cos(x)]]) - assert pnp.allclose(res, expected, atol=tol, rtol=0) + expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) + assert np.allclose(res, expected, atol=tol, rtol=0) def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -736,8 +734,8 @@ def test_single_expectation_value_with_argnum_all(self, approx_order, strategy, assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = pnp.array([[-pnp.sin(y) * pnp.sin(x), pnp.cos(y) * pnp.cos(x)]]) - assert pnp.allclose(res, expected, atol=tol, rtol=0) + expected = np.array([[-np.sin(y) * np.sin(x), np.cos(y) * np.cos(x)]]) + assert np.allclose(res, expected, atol=tol, rtol=0) def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -780,9 +778,9 @@ def test_single_expectation_value_with_argnum_one(self, approx_order, strategy, assert isinstance(res[1], numpy.ndarray) assert res[1].shape == () - expected = [0, pnp.cos(y) * pnp.cos(x)] + expected = [0, np.cos(y) * np.cos(x)] - assert pnp.allclose(res, expected, atol=tol, rtol=0) + assert np.allclose(res, expected, atol=tol, rtol=0) def test_multiple_expectation_value_with_argnum_one(self, approx_order, strategy, validate): """Tests correct output shape and evaluation for a tape @@ -818,9 +816,9 @@ def test_multiple_expectation_value_with_argnum_one(self, approx_order, strategy assert isinstance(res, tuple) assert isinstance(res[0], tuple) - assert pnp.allclose(res[0][0], 0) + assert np.allclose(res[0][0], 0) assert isinstance(res[1], tuple) - assert pnp.allclose(res[1][0], 0) + assert np.allclose(res[1][0], 0) def test_multiple_expectation_values(self, approx_order, strategy, validate, tol): """Tests correct output shape and evaluation for a tape @@ -857,13 +855,13 @@ def test_multiple_expectation_values(self, approx_order, strategy, validate, tol assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert pnp.allclose(res[0], [-pnp.sin(x), 0], atol=tol, rtol=0) + assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert pnp.allclose(res[1], [0, pnp.cos(y)], atol=tol, rtol=0) + assert np.allclose(res[1], [0, np.cos(y)], atol=tol, rtol=0) assert isinstance(res[1][0], numpy.ndarray) assert isinstance(res[1][1], numpy.ndarray) @@ -902,13 +900,13 @@ def test_var_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert pnp.allclose(res[0], [-pnp.sin(x), 0], atol=tol, rtol=0) + assert np.allclose(res[0], [-np.sin(x), 0], atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert pnp.allclose(res[1], [0, -2 * pnp.cos(y) * pnp.sin(y)], atol=tol, rtol=0) + assert np.allclose(res[1], [0, -2 * np.cos(y) * np.sin(y)], atol=tol, rtol=0) assert isinstance(res[1][0], numpy.ndarray) assert isinstance(res[1][1], numpy.ndarray) @@ -947,32 +945,32 @@ def test_prob_expectation_values(self, approx_order, strategy, validate, tol): assert isinstance(res[0], tuple) assert len(res[0]) == 2 - assert pnp.allclose(res[0][0], -pnp.sin(x), atol=tol, rtol=0) + assert np.allclose(res[0][0], -np.sin(x), atol=tol, rtol=0) assert isinstance(res[0][0], numpy.ndarray) - assert pnp.allclose(res[0][1], 0, atol=tol, rtol=0) + assert np.allclose(res[0][1], 0, atol=tol, rtol=0) assert isinstance(res[0][1], numpy.ndarray) assert isinstance(res[1], tuple) assert len(res[1]) == 2 - assert pnp.allclose( + assert np.allclose( res[1][0], [ - -(pnp.cos(y / 2) ** 2 * pnp.sin(x)) / 2, - -(pnp.sin(x) * pnp.sin(y / 2) ** 2) / 2, - (pnp.sin(x) * pnp.sin(y / 2) ** 2) / 2, - (pnp.cos(y / 2) ** 2 * pnp.sin(x)) / 2, + -(np.cos(y / 2) ** 2 * np.sin(x)) / 2, + -(np.sin(x) * np.sin(y / 2) ** 2) / 2, + (np.sin(x) * np.sin(y / 2) ** 2) / 2, + (np.cos(y / 2) ** 2 * np.sin(x)) / 2, ], atol=tol, rtol=0, ) assert isinstance(res[1][0], numpy.ndarray) - assert pnp.allclose( + assert np.allclose( res[1][1], [ - -(pnp.cos(x / 2) ** 2 * pnp.sin(y)) / 2, - (pnp.cos(x / 2) ** 2 * pnp.sin(y)) / 2, - (pnp.sin(x / 2) ** 2 * pnp.sin(y)) / 2, - -(pnp.sin(x / 2) ** 2 * pnp.sin(y)) / 2, + -(np.cos(x / 2) ** 2 * np.sin(y)) / 2, + (np.cos(x / 2) ** 2 * np.sin(y)) / 2, + (np.sin(x / 2) ** 2 * np.sin(y)) / 2, + -(np.sin(x / 2) ** 2 * np.sin(y)) / 2, ], atol=tol, rtol=0, @@ -993,8 +991,8 @@ def test_autograd(self, dev_name, sampler, num_directions, atol): can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = pnp.array([0.543, -0.654], requires_grad=True) - rng = pnp.random.default_rng(42) + params = np.array([0.543, -0.654], requires_grad=True) + rng = np.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1008,21 +1006,21 @@ def cost_fn(x): tapes, fn = spsa_grad( tape, n=1, num_directions=num_directions, sampler=sampler, sampler_rng=rng ) - jac = pnp.array(fn(execute_fn(tapes))) + jac = np.array(fn(execute_fn(tapes))) if sampler is coordinate_sampler: jac *= 2 return jac res = qml.jacobian(cost_fn)(params) x, y = params - expected = pnp.array( + expected = np.array( [ - [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], - [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], + [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], + [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], ] ) - assert pnp.allclose(res, expected, atol=atol, rtol=0) + assert np.allclose(res, expected, atol=atol, rtol=0) @pytest.mark.autograd @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.autograd"]) @@ -1031,8 +1029,8 @@ def test_autograd_ragged(self, dev_name, sampler, num_directions, atol): of a ragged tape can be differentiated using autograd, yielding second derivatives.""" dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute - params = pnp.array([0.543, -0.654], requires_grad=True) - rng = pnp.random.default_rng(42) + params = np.array([0.543, -0.654], requires_grad=True) + rng = np.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1054,8 +1052,8 @@ def cost_fn(x): x, y = params res = qml.jacobian(cost_fn)(params)[0] - expected = pnp.array([-pnp.cos(x) * pnp.cos(y) / 2, pnp.sin(x) * pnp.sin(y) / 2]) - assert pnp.allclose(res, expected, atol=atol, rtol=0) + expected = np.array([-np.cos(x) * np.cos(y) / 2, np.sin(x) * np.sin(y) / 2]) + assert np.allclose(res, expected, atol=atol, rtol=0) @pytest.mark.tf @pytest.mark.slow @@ -1068,7 +1066,7 @@ def test_tf(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = tf.Variable([0.543, -0.654], dtype=tf.float64) - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) with tf.GradientTape(persistent=True) as t: with qml.queuing.AnnotatedQueue() as q: @@ -1092,13 +1090,13 @@ def test_tf(self, dev_name, sampler, num_directions, atol): res_0 = t.jacobian(jac_0, params) res_1 = t.jacobian(jac_1, params) - expected = pnp.array( + expected = np.array( [ - [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], - [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], + [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], + [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], ] ) - assert pnp.allclose([res_0, res_1], expected, atol=atol, rtol=0) + assert np.allclose([res_0, res_1], expected, atol=atol, rtol=0) @pytest.mark.tf @pytest.mark.slow @@ -1111,7 +1109,7 @@ def test_tf_ragged(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = tf.Variable([0.543, -0.654], dtype=tf.float64) - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) with tf.GradientTape(persistent=True) as t: with qml.queuing.AnnotatedQueue() as q: @@ -1135,9 +1133,9 @@ def test_tf_ragged(self, dev_name, sampler, num_directions, atol): res_01 = t.jacobian(jac_01, params) - expected = pnp.array([-pnp.cos(x) * pnp.cos(y) / 2, pnp.sin(x) * pnp.sin(y) / 2]) + expected = np.array([-np.cos(x) * np.cos(y) / 2, np.sin(x) * np.sin(y) / 2]) - assert pnp.allclose(res_01[0], expected, atol=atol, rtol=0) + assert np.allclose(res_01[0], expected, atol=atol, rtol=0) @pytest.mark.torch @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.torch"]) @@ -1149,7 +1147,7 @@ def test_torch(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = torch.tensor([0.543, -0.654], dtype=torch.float64, requires_grad=True) - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) def cost_fn(params): with qml.queuing.AnnotatedQueue() as q: @@ -1171,15 +1169,15 @@ def cost_fn(params): x, y = params.detach().numpy() - expected = pnp.array( + expected = np.array( [ - [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], - [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], + [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], + [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], ] ) - assert pnp.allclose(hess[0].detach().numpy(), expected[0], atol=atol, rtol=0) - assert pnp.allclose(hess[1].detach().numpy(), expected[1], atol=atol, rtol=0) + assert np.allclose(hess[0].detach().numpy(), expected[0], atol=atol, rtol=0) + assert np.allclose(hess[1].detach().numpy(), expected[1], atol=atol, rtol=0) @pytest.mark.jax @pytest.mark.parametrize("dev_name", ["default.qubit", "default.qubit.jax"]) @@ -1192,7 +1190,7 @@ def test_jax(self, dev_name, sampler, num_directions, atol): dev = qml.device(dev_name, wires=2) execute_fn = dev.execute if dev_name == "default.qubit" else dev.batch_execute params = jnp.array([0.543, -0.654]) - rng = pnp.random.default_rng(42) + rng = np.random.default_rng(42) def cost_fn(x): with qml.queuing.AnnotatedQueue() as q: @@ -1214,10 +1212,10 @@ def cost_fn(x): res = jax.jacobian(cost_fn)(params) assert isinstance(res, tuple) x, y = params - expected = pnp.array( + expected = np.array( [ - [-pnp.cos(x) * pnp.sin(y), -pnp.cos(y) * pnp.sin(x)], - [-pnp.cos(y) * pnp.sin(x), -pnp.cos(x) * pnp.sin(y)], + [-np.cos(x) * np.sin(y), -np.cos(y) * np.sin(x)], + [-np.cos(y) * np.sin(x), -np.cos(x) * np.sin(y)], ] ) - assert pnp.allclose(res, expected, atol=atol, rtol=0) + assert np.allclose(res, expected, atol=atol, rtol=0) diff --git a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py index 03bfc1ddefe..e2c57bfd714 100644 --- a/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py +++ b/tests/gradients/finite_diff/test_spsa_gradient_shot_vec.py @@ -499,10 +499,8 @@ def reference_qnode(x): return qml.expval(qml.PauliZ(wires=0)) par = np.array(0.2, requires_grad=True) - assert np.isclose(qnode(par).item().val, reference_qnode(par).item()) - assert np.isclose( - qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par).item() - ) + assert np.isclose(qnode(par).item().val, reference_qnode(par)) + assert np.isclose(qml.jacobian(qnode)(par).item().val, qml.jacobian(reference_qnode)(par)) @pytest.mark.parametrize("approx_order", [2, 4]) From 6bb842d884cfa62d7e3e8724d0a8424c5937c0a8 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 16:53:15 -0400 Subject: [PATCH 20/59] revert lightning test change --- tests/devices/test_lightning_qubit.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/devices/test_lightning_qubit.py b/tests/devices/test_lightning_qubit.py index 5d61d6be202..6fe2cac85b5 100644 --- a/tests/devices/test_lightning_qubit.py +++ b/tests/devices/test_lightning_qubit.py @@ -130,10 +130,7 @@ def circuit(x): expected_dtype = c_dtype else: expected_dtype = np.float64 if c_dtype == np.complex128 else np.float32 - if isinstance(res, np.ndarray) and res.size > 1: + if isinstance(res, np.ndarray): assert res.dtype == expected_dtype - elif isinstance(res, np.ndarray): - # default dtype of np.array() of a regular float - assert res.dtype == np.float64 else: assert isinstance(res, float) From 26111be1e544bc01ca3551528eb6528672429562 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 5 Sep 2024 16:54:37 -0400 Subject: [PATCH 21/59] stop treating numpy as autograd internally --- pennylane/workflow/qnode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index d491e1f4580..2b3edb5036a 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -989,8 +989,10 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if qml.capture.enabled() else qml.math.get_interface(*args, *list(kwargs.values())) ) + if interface == "numpy": + # Internally stop treating numpy as autograd + interface = None self._interface = INTERFACE_MAP[interface] - if self._qfunc_uses_shots_arg: override_shots = False else: From 26d16bc43accce16868bc70670c0442a1a424ce9 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 6 Sep 2024 09:48:36 -0400 Subject: [PATCH 22/59] bug fixes --- pennylane/devices/legacy_facade.py | 4 ++-- pennylane/workflow/execution.py | 2 +- pennylane/workflow/qnode.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index a35aa4b25f5..e89d74ab6f7 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -408,13 +408,13 @@ def _validate_backprop_method(self, tape): if backprop_interface is not None: # device supports backpropagation natively - return mapped_interface in [backprop_interface, "Numpy"] + return mapped_interface in [backprop_interface, "numpy"] # determine if the device has any child devices that support backpropagation backprop_devices = self._device.capabilities().get("passthru_devices", None) if backprop_devices is None: return False - return mapped_interface in backprop_devices or mapped_interface == "Numpy" + return mapped_interface in backprop_devices or mapped_interface == "numpy" def _validate_adjoint_method(self, tape): # The conditions below provide a minimal set of requirements that we can likely improve upon in diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index 3bc1c0563a7..0549dec758e 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -79,7 +79,7 @@ ] _mapping_output = ( - "Numpy", + "numpy", "auto", "autograd", "autograd", diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 2b3edb5036a..44730dadccd 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -58,7 +58,7 @@ def _convert_to_interface(res, interface): """ interface = INTERFACE_MAP[interface] - if interface in ["Numpy"]: + if interface == "numpy": return res if isinstance(res, (list, tuple)): @@ -989,9 +989,9 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if qml.capture.enabled() else qml.math.get_interface(*args, *list(kwargs.values())) ) - if interface == "numpy": - # Internally stop treating numpy as autograd - interface = None + # if interface == "numpy": + # # Internally stop treating numpy as autograd + # interface = None self._interface = INTERFACE_MAP[interface] if self._qfunc_uses_shots_arg: override_shots = False From c45b7500c8337f878cc4814aec6d158d01025595 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 6 Sep 2024 09:50:41 -0400 Subject: [PATCH 23/59] uncomment line --- pennylane/workflow/qnode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 44730dadccd..8b0ce013677 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -989,9 +989,9 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if qml.capture.enabled() else qml.math.get_interface(*args, *list(kwargs.values())) ) - # if interface == "numpy": - # # Internally stop treating numpy as autograd - # interface = None + if interface == "numpy": + # Internally stop treating numpy as autograd + interface = None self._interface = INTERFACE_MAP[interface] if self._qfunc_uses_shots_arg: override_shots = False From 48f73c4fcec760cea2720776840eab2f0d4d4bc2 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 6 Sep 2024 10:26:41 -0400 Subject: [PATCH 24/59] fix changelog --- doc/releases/changelog-dev.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index cbb074f3862..bee2852b09d 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -42,12 +42,6 @@

Bug fixes 🐛

-* Fix a bug where zero-valued JVPs were calculated wrongly in the presence of shot vectors. - [(#6219)](https://github.com/PennyLaneAI/pennylane/pull/6219) - -* Fix Pytree serialization of operators with empty shot vectors: - [(#6155)](https://github.com/PennyLaneAI/pennylane/pull/6155) - * Fix `qml.PrepSelPrep` template to work with `torch`: [(#6191)](https://github.com/PennyLaneAI/pennylane/pull/6191) @@ -65,4 +59,3 @@ Guillermo Alonso Utkarsh Azad Christina Lee William Maxwell -David Wierichs From 0633dc03b25e5b418a852ea4c096fa2013f1a72c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 6 Sep 2024 11:18:50 -0400 Subject: [PATCH 25/59] fix tiny bug --- pennylane/devices/qubit/simulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/qubit/simulate.py b/pennylane/devices/qubit/simulate.py index 56e4a8f1a48..b45db2edaa3 100644 --- a/pennylane/devices/qubit/simulate.py +++ b/pennylane/devices/qubit/simulate.py @@ -922,7 +922,7 @@ def _(original_measurement: ExpectationMP, measures): # pylint: disable=unused- for v in measures.values(): if not v[0] or v[1] is tuple(): continue - cum_value += v[0] * v[1] + cum_value += qml.math.multiply(v[0], v[1]) total_counts += v[0] return cum_value / total_counts From e1a1fc53286b060c55d8fbc03555e70d1e81c170 Mon Sep 17 00:00:00 2001 From: David Wierichs Date: Mon, 9 Sep 2024 12:27:48 +0200 Subject: [PATCH 26/59] Apply suggestions from code review Co-authored-by: Christina Lee --- pennylane/gradients/jvp.py | 4 ++-- pennylane/workflow/jacobian_products.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/gradients/jvp.py b/pennylane/gradients/jvp.py index 1247bbe37a9..4f4f97f3d01 100644 --- a/pennylane/gradients/jvp.py +++ b/pennylane/gradients/jvp.py @@ -296,13 +296,13 @@ def jvp(tape, tangent, gradient_fn, gradient_kwargs=None): # The tape has no trainable parameters; the JVP # is simply none. def zero_jvp_for_single_shots(s): - res = tuple(np.zeros(mp.shape(shots=s)) for mp in tape.measurements) + res = tuple(np.zeros(mp.shape(shots=s), dtype=mp.numeric_type) for mp in tape.measurements) return res[0] if len(tape.measurements) == 1 else res def zero_jvp(_): if tape.shots.has_partitioned_shots: return tuple(zero_jvp_for_single_shots(s) for s in tape.shots) - return zero_jvp_for_single_shots(tape.shots) + return zero_jvp_for_single_shots(tape.shots.total_shots) return tuple(), zero_jvp diff --git a/pennylane/workflow/jacobian_products.py b/pennylane/workflow/jacobian_products.py index 44188773221..d5ede1227a5 100644 --- a/pennylane/workflow/jacobian_products.py +++ b/pennylane/workflow/jacobian_products.py @@ -54,7 +54,7 @@ def _zero_jvp_single_shots(shots, tape): def _zero_jvp(tape): if tape.shots.has_partitioned_shots: return tuple(_zero_jvp_single_shots(s, tape) for s in tape.shots) - return _zero_jvp_single_shots(tape.shots, tape) + return _zero_jvp_single_shots(tape.shots.total_shots, tape) def _compute_jvps(jacs, tangents, tapes): From 5bd7176d9d719a280addfe200d8d94ec47d52f86 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 09:18:08 -0400 Subject: [PATCH 27/59] minor updates --- tests/interfaces/test_autograd.py | 83 ++++++++++++++++--------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 36dd83231a0..23bdd8cdad2 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -14,11 +14,12 @@ """Autograd specific tests for execute and default qubit 2.""" import autograd import pytest +import numpy as np from param_shift_dev import ParamShiftDerivativesDevice import pennylane as qml from pennylane import execute -from pennylane import numpy as np +from pennylane import numpy as pnp from pennylane.devices import DefaultQubit from pennylane.gradients import param_shift from pennylane.measurements import Shots @@ -36,7 +37,7 @@ def test_caching_param_shift_hessian(self, num_params): caching reduces the number of evaluations to their optimum when computing Hessians.""" dev = DefaultQubit() - params = np.arange(1, num_params + 1) / 10 + params = pnp.arange(1, num_params + 1) / 10 N = len(params) @@ -194,8 +195,8 @@ def cost(a, b): return execute([tape1, tape2], device, **execute_kwargs) - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=False) with device.tracker: res = cost(a, b) @@ -215,7 +216,7 @@ def cost(a, b): def test_scalar_jacobian(self, execute_kwargs, shots, device): """Test scalar jacobian calculation""" - a = np.array(0.1, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) def cost(a): tape = qml.tape.QuantumScript([qml.RY(a, 0)], [qml.expval(qml.PauliZ(0))], shots=shots) @@ -239,8 +240,8 @@ def cost(a): def test_jacobian(self, execute_kwargs, shots, device): """Test jacobian calculation""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=True) def cost(a, b): ops = [qml.RY(a, wires=0), qml.RX(b, wires=1), qml.CNOT(wires=[0, 1])] @@ -285,7 +286,7 @@ def cost(params): ) tape2 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), wires=0)], + [qml.RY(pnp.array(0.5, requires_grad=False), wires=0)], [qml.expval(qml.PauliZ(0))], shots=shots, ) @@ -297,7 +298,7 @@ def cost(params): ) tape4 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), 0)], + [qml.RY(pnp.array(0.5, requires_grad=False), 0)], [qml.probs(wires=[0, 1])], shots=shots, ) @@ -306,7 +307,7 @@ def cost(params): res = tuple(i for r in res for i in r) return sum(autograd.numpy.hstack(res)) - params = np.array([0.1, 0.2], requires_grad=True) + params = pnp.array([0.1, 0.2], requires_grad=True) x, y = params res = cost(params) @@ -336,7 +337,7 @@ def cost(params): ) tape2 = qml.tape.QuantumScript( - [qml.RY(np.array(0.5, requires_grad=False), 0)], + [qml.RY(pnp.array(0.5, requires_grad=False), 0)], [qml.expval(qml.PauliZ(0))], shots=shots, ) @@ -351,7 +352,7 @@ def cost(params): res = tuple(i for r in res for i in r) return autograd.numpy.hstack(res) - params = np.array([0.1, 0.2], requires_grad=True) + params = pnp.array([0.1, 0.2], requires_grad=True) x, y = params res = cost(params) @@ -407,8 +408,8 @@ def cost(params): def test_reusing_quantum_tape(self, execute_kwargs, shots, device): """Test re-using a quantum tape by passing new parameters""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=True) tape = qml.tape.QuantumScript( [qml.RY(a, 0), qml.RX(b, 1), qml.CNOT((0, 1))], @@ -423,8 +424,8 @@ def cost(a, b): jac_fn = qml.jacobian(cost) jac = jac_fn(a, b) - a = np.array(0.54, requires_grad=True) - b = np.array(0.8, requires_grad=True) + a = pnp.array(0.54, requires_grad=True) + b = pnp.array(0.8, requires_grad=True) # check that the cost function continues to depend on the # values of the parameters for subsequent calls @@ -444,15 +445,15 @@ def cost(a, b): def test_classical_processing(self, execute_kwargs, device, shots): """Test classical processing within the quantum tape""" - a = np.array(0.1, requires_grad=True) - b = np.array(0.2, requires_grad=False) - c = np.array(0.3, requires_grad=True) + a = pnp.array(0.1, requires_grad=True) + b = pnp.array(0.2, requires_grad=False) + c = pnp.array(0.3, requires_grad=True) def cost(a, b, c): ops = [ qml.RY(a * c, wires=0), qml.RZ(b, wires=0), - qml.RX(c + c**2 + np.sin(a), wires=0), + qml.RX(c + c**2 + pnp.sin(a), wires=0), ] tape = qml.tape.QuantumScript(ops, [qml.expval(qml.PauliZ(0))], shots=shots) @@ -472,8 +473,8 @@ def cost(a, b, c): def test_no_trainable_parameters(self, execute_kwargs, shots, device): """Test evaluation and Jacobian if there are no trainable parameters""" - a = np.array(0.1, requires_grad=False) - b = np.array(0.2, requires_grad=False) + a = pnp.array(0.1, requires_grad=False) + b = pnp.array(0.2, requires_grad=False) def cost(a, b): ops = [qml.RY(a, 0), qml.RX(b, 0), qml.CNOT((0, 1))] @@ -499,8 +500,8 @@ def loss(a, b): def test_matrix_parameter(self, execute_kwargs, device, shots): """Test that the autograd interface works correctly with a matrix parameter""" - U = np.array([[0, 1], [1, 0]], requires_grad=False) - a = np.array(0.1, requires_grad=True) + U = pnp.array([[0, 1], [1, 0]], requires_grad=False) + a = pnp.array(0.1, requires_grad=True) def cost(a, U): ops = [qml.QubitUnitary(U, wires=0), qml.RY(a, wires=0)] @@ -550,8 +551,8 @@ def cost_fn(a, p): program, _ = device.preprocess(execution_config=config) return execute([tape], device, **execute_kwargs, transform_program=program)[0] - a = np.array(0.1, requires_grad=False) - p = np.array([0.1, 0.2, 0.3], requires_grad=True) + a = pnp.array(0.1, requires_grad=False) + p = pnp.array([0.1, 0.2, 0.3], requires_grad=True) res = cost_fn(a, p) expected = np.cos(a) * np.cos(p[1]) * np.sin(p[0]) + np.sin(a) * ( @@ -583,8 +584,8 @@ def cost(x, y): tape = qml.tape.QuantumScript(ops, m) return autograd.numpy.hstack(execute([tape], device, **execute_kwargs)[0]) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) + x = pnp.array(0.543, requires_grad=True) + y = pnp.array(-0.654, requires_grad=True) res = cost(x, y) expected = np.array( @@ -636,8 +637,8 @@ def cost(x, y): tape = qml.tape.QuantumScript(ops, m) return autograd.numpy.hstack(execute([tape], device, **execute_kwargs)[0]) - x = np.array(0.543, requires_grad=True) - y = np.array(-0.654, requires_grad=True) + x = pnp.array(0.543, requires_grad=True) + y = pnp.array(-0.654, requires_grad=True) res = cost(x, y) expected = np.array( @@ -665,9 +666,9 @@ class TestHigherOrderDerivatives: @pytest.mark.parametrize( "params", [ - np.array([0.543, -0.654], requires_grad=True), - np.array([0, -0.654], requires_grad=True), - np.array([-2.0, 0], requires_grad=True), + pnp.array([0.543, -0.654], requires_grad=True), + pnp.array([0, -0.654], requires_grad=True), + pnp.array([-2.0, 0], requires_grad=True), ], ) def test_parameter_shift_hessian(self, params, tol): @@ -708,7 +709,7 @@ def test_max_diff(self, tol): """Test that setting the max_diff parameter blocks higher-order derivatives""" dev = DefaultQubit() - params = np.array([0.543, -0.654], requires_grad=True) + params = pnp.array([0.543, -0.654], requires_grad=True) def cost_fn(x): ops = [qml.RX(x[0], 0), qml.RY(x[1], 1), qml.CNOT((0, 1))] @@ -805,9 +806,9 @@ def test_multiple_hamiltonians_not_trainable(self, execute_kwargs, cost_fn, shot if execute_kwargs["gradient_fn"] == "adjoint" and not qml.operation.active_new_opmath(): pytest.skip("adjoint differentiation does not support hamiltonians.") - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=False) - coeffs2 = np.array([0.7], requires_grad=False) - weights = np.array([0.4, 0.5], requires_grad=True) + coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=False) + coeffs2 = pnp.array([0.7], requires_grad=False) + weights = pnp.array([0.4, 0.5], requires_grad=True) res = cost_fn(weights, coeffs1, coeffs2) expected = self.cost_fn_expected(weights, coeffs1, coeffs2) @@ -832,9 +833,9 @@ def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): if qml.operation.active_new_opmath(): pytest.skip("parameter shift derivatives do not yet support sums.") - coeffs1 = np.array([0.1, 0.2, 0.3], requires_grad=True) - coeffs2 = np.array([0.7], requires_grad=True) - weights = np.array([0.4, 0.5], requires_grad=True) + coeffs1 = pnp.array([0.1, 0.2, 0.3], requires_grad=True) + coeffs2 = pnp.array([0.7], requires_grad=True) + weights = pnp.array([0.4, 0.5], requires_grad=True) res = cost_fn(weights, coeffs1, coeffs2) expected = self.cost_fn_expected(weights, coeffs1, coeffs2) @@ -851,4 +852,4 @@ def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): "multiple hamiltonians with shot vectors does not seem to be differentiable." ) else: - assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) + assert qml.math.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) From ffb9d3c2d8d02f256f8b03d4a2e259dc6e9707df Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 09:19:05 -0400 Subject: [PATCH 28/59] more fix --- pennylane/devices/qubit/simulate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/qubit/simulate.py b/pennylane/devices/qubit/simulate.py index b45db2edaa3..89c041b8f3e 100644 --- a/pennylane/devices/qubit/simulate.py +++ b/pennylane/devices/qubit/simulate.py @@ -935,7 +935,7 @@ def _(original_measurement: ProbabilityMP, measures): # pylint: disable=unused- for v in measures.values(): if not v[0] or v[1] is tuple(): continue - cum_value += v[0] * v[1] + cum_value += qml.math.multiply(v[0], v[1]) total_counts += v[0] return cum_value / total_counts From a11ef4855ee0636aa6caba5794020ddba969d27e Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 09:27:37 -0400 Subject: [PATCH 29/59] changelog --- doc/releases/changelog-dev.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index 7c83191fc8a..ecebb9a5d68 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -24,6 +24,8 @@ unique representation of the object. [(#6167)](https://github.com/PennyLaneAI/pennylane/pull/6167) +* A `ReferenceQubit` is introduced for testing purposes and as a reference for future plugin development. + [(#6181)](https://github.com/PennyLaneAI/pennylane/pull/6181)

Breaking changes 💔

@@ -66,7 +68,10 @@ This release contains contributions from (in alphabetical order): -Guillermo Alonso -Utkarsh Azad -Christina Lee -William Maxwell +Guillermo Alonso, +Utkarsh Azad, +Astral Cai, +Lillian M. A. Frederiksen, +Christina Lee, +William Maxwell, +Lee J. O'Riordan, From 8333c198dc20370cb133f9ac2859e9ea44762a31 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 09:43:42 -0400 Subject: [PATCH 30/59] format --- pennylane/gradients/jvp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pennylane/gradients/jvp.py b/pennylane/gradients/jvp.py index 4f4f97f3d01..67428ab5c68 100644 --- a/pennylane/gradients/jvp.py +++ b/pennylane/gradients/jvp.py @@ -296,7 +296,9 @@ def jvp(tape, tangent, gradient_fn, gradient_kwargs=None): # The tape has no trainable parameters; the JVP # is simply none. def zero_jvp_for_single_shots(s): - res = tuple(np.zeros(mp.shape(shots=s), dtype=mp.numeric_type) for mp in tape.measurements) + res = tuple( + np.zeros(mp.shape(shots=s), dtype=mp.numeric_type) for mp in tape.measurements + ) return res[0] if len(tape.measurements) == 1 else res def zero_jvp(_): From 3ad19212e65f939f700ff6c34fb893ac4dafaa5c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 10:00:21 -0400 Subject: [PATCH 31/59] fix isort --- tests/interfaces/test_autograd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 23bdd8cdad2..04c8bab3459 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -13,8 +13,8 @@ # limitations under the License. """Autograd specific tests for execute and default qubit 2.""" import autograd -import pytest import numpy as np +import pytest from param_shift_dev import ParamShiftDerivativesDevice import pennylane as qml From 5d6e8eb75272ed5789292f1da94dc7a8626b3e94 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 10:34:54 -0400 Subject: [PATCH 32/59] fix bug --- pennylane/workflow/execution.py | 2 +- tests/measurements/test_sample.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index 0243d5e0d2c..c78167a0a91 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -479,7 +479,7 @@ def cost_fn(params, x): cache = None # changing this set of conditions causes a bunch of tests to break. - no_interface_boundary_required = interface is None or config.gradient_method in { + no_interface_boundary_required = interface in (None, "numpy") or config.gradient_method in { None, "backprop", } diff --git a/tests/measurements/test_sample.py b/tests/measurements/test_sample.py index c9dae08009a..011d8adc50b 100644 --- a/tests/measurements/test_sample.py +++ b/tests/measurements/test_sample.py @@ -63,8 +63,8 @@ def circuit(): assert len(result) == 3 assert np.array_equal(result[0].shape, (n_sample,)) assert circuit._qfunc_output[0].shape(shots=n_sample, num_device_wires=3) == (n_sample,) - assert isinstance(result[1], np.float64) - assert isinstance(result[2], np.float64) + assert np.shape(result[1]) == () + assert np.shape(result[2]) == () def test_single_wire_sample(self): """Test the return type and shape of sampling a single wire""" From 6776c01186a808a72e81e81b2c7b552cf0f60a82 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 11:19:10 -0400 Subject: [PATCH 33/59] fix tests --- tests/test_qnode.py | 2 +- tests/test_qnode_legacy.py | 2 +- tests/test_vqe.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_qnode.py b/tests/test_qnode.py index 9a4c4e33f5b..b36c2cb557e 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -1844,7 +1844,7 @@ def circuit(x): else: spy = mocker.spy(circuit.device, "execute") - x = np.array(0.5) + x = pnp.array(0.5) circuit(x) tape = spy.call_args[0][0][0] diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 714bb202395..5a6f2e20040 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -1614,7 +1614,7 @@ def circuit(x): else: spy = mocker.spy(circuit.device, "execute") - x = np.array(0.5) + x = pnp.array(0.5) circuit(x) tape = spy.call_args[0][0][0] diff --git a/tests/test_vqe.py b/tests/test_vqe.py index e8edc6c84cf..3af5992a4dc 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -179,10 +179,10 @@ def amp_embed_and_strong_ent_layer(params, wires=None): ##################################################### # Parameters -EMPTY_PARAMS = [] -VAR_PARAMS = [0.5] -EMBED_PARAMS = np.array([1 / np.sqrt(2**3)] * 2**3) -LAYER_PARAMS = np.random.random(qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3)) +EMPTY_PARAMS = pnp.array([]) +VAR_PARAMS = pnp.array([0.5]) +EMBED_PARAMS = pnp.array([1 / np.sqrt(2**3)] * 2**3) +LAYER_PARAMS = pnp.random.random(qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3)) CIRCUITS = [ (lambda params, wires=None: None, EMPTY_PARAMS), From eca0f141b99ffe6afd705ee7a38d088bc6b3eb7f Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 11:28:27 -0400 Subject: [PATCH 34/59] fix black --- tests/test_vqe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_vqe.py b/tests/test_vqe.py index 3af5992a4dc..1fe95eceaa8 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -182,7 +182,9 @@ def amp_embed_and_strong_ent_layer(params, wires=None): EMPTY_PARAMS = pnp.array([]) VAR_PARAMS = pnp.array([0.5]) EMBED_PARAMS = pnp.array([1 / np.sqrt(2**3)] * 2**3) -LAYER_PARAMS = pnp.random.random(qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3)) +LAYER_PARAMS = pnp.random.random( + qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3) +) CIRCUITS = [ (lambda params, wires=None: None, EMPTY_PARAMS), From 2e4f55ce716a043fa4a161df43973d1222799d83 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 11:54:11 -0400 Subject: [PATCH 35/59] revert change --- tests/test_vqe.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_vqe.py b/tests/test_vqe.py index 1fe95eceaa8..e8edc6c84cf 100644 --- a/tests/test_vqe.py +++ b/tests/test_vqe.py @@ -179,12 +179,10 @@ def amp_embed_and_strong_ent_layer(params, wires=None): ##################################################### # Parameters -EMPTY_PARAMS = pnp.array([]) -VAR_PARAMS = pnp.array([0.5]) -EMBED_PARAMS = pnp.array([1 / np.sqrt(2**3)] * 2**3) -LAYER_PARAMS = pnp.random.random( - qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3) -) +EMPTY_PARAMS = [] +VAR_PARAMS = [0.5] +EMBED_PARAMS = np.array([1 / np.sqrt(2**3)] * 2**3) +LAYER_PARAMS = np.random.random(qml.templates.StronglyEntanglingLayers.shape(n_layers=2, n_wires=3)) CIRCUITS = [ (lambda params, wires=None: None, EMPTY_PARAMS), From 8c4a72a93f18fcaab42214fcc27564a62f7c7064 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 13:05:19 -0400 Subject: [PATCH 36/59] add sparse matrix to Hermitian --- pennylane/ops/qubit/observables.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pennylane/ops/qubit/observables.py b/pennylane/ops/qubit/observables.py index 8f992c81bc2..4fc4a98c092 100644 --- a/pennylane/ops/qubit/observables.py +++ b/pennylane/ops/qubit/observables.py @@ -137,6 +137,10 @@ def compute_matrix(A: TensorLike) -> TensorLike: # pylint: disable=arguments-di Hermitian._validate_input(A) return A + @staticmethod + def compute_sparse_matrix(A) -> csr_matrix: # pylint: disable=arguments-differ + return csr_matrix(Hermitian.compute_matrix(A)) + @property def eigendecomposition(self) -> dict[str, TensorLike]: """Return the eigendecomposition of the matrix specified by the Hermitian observable. From 3c8a6910b518254ee2dc0a921db3b99edf90c317 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 13:46:02 -0400 Subject: [PATCH 37/59] bug fix --- pennylane/workflow/execution.py | 2 +- pennylane/workflow/qnode.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index c78167a0a91..0243d5e0d2c 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -479,7 +479,7 @@ def cost_fn(params, x): cache = None # changing this set of conditions causes a bunch of tests to break. - no_interface_boundary_required = interface in (None, "numpy") or config.gradient_method in { + no_interface_boundary_required = interface is None or config.gradient_method in { None, "backprop", } diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 24052854e4a..008d3ddd87c 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -589,7 +589,7 @@ def interface(self, value: SupportedInterfaceUserInput): f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACES}." ) - self._interface = INTERFACE_MAP[value] + self._interface = value self._update_gradient_fn(shots=self.device.shots) @property @@ -934,7 +934,7 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if interface == "numpy": # Internally stop treating numpy as autograd interface = None - self._interface = INTERFACE_MAP[interface] + self._interface = interface if self._qfunc_uses_shots_arg: override_shots = False else: From 71561f9df8972376966d303694d608ca117e985f Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 14:11:16 -0400 Subject: [PATCH 38/59] bug fix --- pennylane/workflow/execution.py | 4 ++-- pennylane/workflow/qnode.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index 0243d5e0d2c..e1652f4bf76 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -479,12 +479,12 @@ def cost_fn(params, x): cache = None # changing this set of conditions causes a bunch of tests to break. - no_interface_boundary_required = interface is None or config.gradient_method in { + no_interface_boundary_required = interface in (None, "numpy") or config.gradient_method in { None, "backprop", } device_supports_interface_data = no_interface_boundary_required and ( - interface is None + interface in (None, "numpy") or config.gradient_method == "backprop" or getattr(device, "short_name", "") == "default.mixed" ) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 008d3ddd87c..5d716ba5c5c 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -56,7 +56,6 @@ def _convert_to_interface(res, interface): """ Recursively convert res to the given interface. """ - interface = INTERFACE_MAP[interface] if interface == "numpy": return res @@ -589,7 +588,7 @@ def interface(self, value: SupportedInterfaceUserInput): f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACES}." ) - self._interface = value + self._interface = INTERFACE_MAP[value] self._update_gradient_fn(shots=self.device.shots) @property @@ -934,7 +933,7 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if interface == "numpy": # Internally stop treating numpy as autograd interface = None - self._interface = interface + self._interface = INTERFACE_MAP[interface] if self._qfunc_uses_shots_arg: override_shots = False else: From 676d85bc3f5afcdb0e7c09a4402fd0021433a166 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 9 Sep 2024 15:34:40 -0400 Subject: [PATCH 39/59] bug fix --- pennylane/workflow/qnode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 5d716ba5c5c..39b12548be3 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -523,7 +523,7 @@ def __init__( # input arguments self.func = func self.device = device - self._interface = interface + self._interface = INTERFACE_MAP[interface] self.diff_method = diff_method mcm_config = qml.devices.MCMConfig(mcm_method=mcm_method, postselect_mode=postselect_mode) cache = (max_diff > 1) if cache == "auto" else cache From b34b48cb30d5ea31b586b18eb802491dca890f88 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 11:25:05 -0400 Subject: [PATCH 40/59] clean up handling of interface --- pennylane/devices/execution_config.py | 6 +-- pennylane/devices/legacy_facade.py | 22 ++++---- pennylane/workflow/__init__.py | 2 +- pennylane/workflow/execution.py | 75 +++++++++++++++------------ pennylane/workflow/qnode.py | 46 +++++++++++----- 5 files changed, 90 insertions(+), 61 deletions(-) diff --git a/pennylane/devices/execution_config.py b/pennylane/devices/execution_config.py index 5b7af096d81..1a6be124003 100644 --- a/pennylane/devices/execution_config.py +++ b/pennylane/devices/execution_config.py @@ -17,7 +17,7 @@ from dataclasses import dataclass, field from typing import Optional, Union -from pennylane.workflow import SUPPORTED_INTERFACES +from pennylane.workflow import SUPPORTED_INTERFACE_INPUTS @dataclass @@ -110,9 +110,9 @@ def __post_init__(self): Note that this hook is automatically called after init via the dataclass integration. """ - if self.interface not in SUPPORTED_INTERFACES: + if self.interface not in SUPPORTED_INTERFACE_INPUTS: raise ValueError( - f"Unknown interface. interface must be in {SUPPORTED_INTERFACES}, got {self.interface} instead." + f"Unknown interface. interface must be in {SUPPORTED_INTERFACE_INPUTS}, got {self.interface} instead." ) if self.grad_on_execution not in {True, False, None}: diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index e89d74ab6f7..c661b431dd7 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -25,6 +25,7 @@ import pennylane as qml from pennylane.measurements import MidMeasureMP, Shots from pennylane.transforms.core.transform_program import TransformProgram +from pennylane.workflow.execution import USER_INPUT_TO_INTERFACE_MAP from .device_api import Device from .execution_config import DefaultExecutionConfig @@ -325,13 +326,11 @@ def _create_temp_device(self, batch): for t in batch: params.extend(t.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) - if interface == "numpy": - return self._device - - mapped_interface = qml.workflow.execution.INTERFACE_MAP.get(interface, interface) + if interface != "numpy": + interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) backprop_interface = self._device.capabilities().get("passthru_interface", None) - if mapped_interface == backprop_interface: + if interface == backprop_interface: return self._device backprop_devices = self._device.capabilities().get("passthru_devices", None) @@ -339,7 +338,7 @@ def _create_temp_device(self, batch): if backprop_devices is None: raise qml.DeviceError(f"Device {self} does not support backpropagation.") - if backprop_devices[mapped_interface] == self._device.short_name: + if backprop_devices[interface] == self._device.short_name: return self._device if self.target_device.short_name != "default.qubit.legacy": @@ -367,7 +366,7 @@ def _create_temp_device(self, batch): ) # we already warned about backprop device switching new_device = qml.device( - backprop_devices[mapped_interface], + backprop_devices[interface], wires=self._device.wires, shots=self._device.shots, ).target_device @@ -396,25 +395,24 @@ def _validate_backprop_method(self, tape): return False params = tape.get_parameters(trainable_only=False) interface = qml.math.get_interface(*params) + if interface != "numpy": + interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) if tape and any(isinstance(m.obs, qml.SparseHamiltonian) for m in tape.measurements): return False - if interface == "numpy": - interface = None - mapped_interface = qml.workflow.execution.INTERFACE_MAP.get(interface, interface) # determine if the device supports backpropagation backprop_interface = self._device.capabilities().get("passthru_interface", None) if backprop_interface is not None: # device supports backpropagation natively - return mapped_interface in [backprop_interface, "numpy"] + return interface in [backprop_interface, "numpy"] # determine if the device has any child devices that support backpropagation backprop_devices = self._device.capabilities().get("passthru_devices", None) if backprop_devices is None: return False - return mapped_interface in backprop_devices or mapped_interface == "numpy" + return interface in backprop_devices or interface == "numpy" def _validate_adjoint_method(self, tape): # The conditions below provide a minimal set of requirements that we can likely improve upon in diff --git a/pennylane/workflow/__init__.py b/pennylane/workflow/__init__.py index 55068804b68..169dec60342 100644 --- a/pennylane/workflow/__init__.py +++ b/pennylane/workflow/__init__.py @@ -56,6 +56,6 @@ """ from .construct_batch import construct_batch, get_transform_program -from .execution import INTERFACE_MAP, SUPPORTED_INTERFACES, execute +from .execution import USER_INPUT_TO_INTERFACE_MAP, SUPPORTED_INTERFACE_INPUTS, execute from .qnode import QNode, qnode from .set_shots import set_shots diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index e1652f4bf76..e179624b39b 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -51,12 +51,9 @@ "autograd", "numpy", "torch", - "pytorch", "jax", - "jax-python", "jax-jit", "tf", - "tensorflow", } SupportedInterfaceUserInput = Literal[ @@ -84,24 +81,23 @@ "autograd", "numpy", "jax", - "jax", + "jax-jit", "jax", "jax", "torch", "torch", "tf", "tf", - "tf", - "tf", + "tf-autograph", + "tf-autograph", ) -INTERFACE_MAP = dict(zip(get_args(SupportedInterfaceUserInput), _mapping_output)) + +USER_INPUT_TO_INTERFACE_MAP = dict(zip(get_args(SupportedInterfaceUserInput), _mapping_output)) """dict[str, str]: maps an allowed interface specification to its canonical name.""" -#: list[str]: allowed interface strings -SUPPORTED_INTERFACES = list(INTERFACE_MAP) +SUPPORTED_INTERFACE_INPUTS = list(USER_INPUT_TO_INTERFACE_MAP) """list[str]: allowed interface strings""" - _CACHED_EXECUTION_WITH_FINITE_SHOTS_WARNINGS = ( "Cached execution with finite shots detected!\n" "Note that samples as well as all noisy quantities computed via sampling " @@ -135,23 +131,21 @@ def _get_ml_boundary_execute( pennylane.QuantumFunctionError if the required package is not installed. """ - mapped_interface = INTERFACE_MAP[interface] try: - if mapped_interface == "autograd": + if interface == "autograd": from .interfaces.autograd import autograd_execute as ml_boundary - elif mapped_interface == "tf": - if "autograph" in interface: - from .interfaces.tensorflow_autograph import execute as ml_boundary + elif interface == "tf-autograph": + from .interfaces.tensorflow_autograph import execute as ml_boundary - ml_boundary = partial(ml_boundary, grad_on_execution=grad_on_execution) + ml_boundary = partial(ml_boundary, grad_on_execution=grad_on_execution) - else: - from .interfaces.tensorflow import tf_execute as full_ml_boundary + elif interface == "tf": + from .interfaces.tensorflow import tf_execute as full_ml_boundary - ml_boundary = partial(full_ml_boundary, differentiable=differentiable) + ml_boundary = partial(full_ml_boundary, differentiable=differentiable) - elif mapped_interface == "torch": + elif interface == "torch": from .interfaces.torch import execute as ml_boundary elif interface == "jax-jit": @@ -159,7 +153,8 @@ def _get_ml_boundary_execute( from .interfaces.jax_jit import jax_jit_vjp_execute as ml_boundary else: from .interfaces.jax_jit import jax_jit_jvp_execute as ml_boundary - else: # interface in {"jax", "jax-python", "JAX"}: + + else: # interface is jax if device_vjp: from .interfaces.jax_jit import jax_jit_vjp_execute as ml_boundary else: @@ -167,9 +162,10 @@ def _get_ml_boundary_execute( except ImportError as e: # pragma: no-cover raise qml.QuantumFunctionError( - f"{mapped_interface} not found. Please install the latest " - f"version of {mapped_interface} to enable the '{mapped_interface}' interface." + f"{interface} not found. Please install the latest " + f"version of {interface} to enable the '{interface}' interface." ) from e + return ml_boundary @@ -263,12 +259,22 @@ def _get_interface_name(tapes, interface): Returns: str: Interface name""" + + if interface not in SUPPORTED_INTERFACE_INPUTS: + raise qml.QuantumFunctionError( + f"Unknown interface {interface}. Interface must be one of {SUPPORTED_INTERFACE_INPUTS}." + ) + + interface = USER_INPUT_TO_INTERFACE_MAP[interface] + if interface == "auto": params = [] for tape in tapes: params.extend(tape.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) - if INTERFACE_MAP.get(interface, "") == "tf" and _use_tensorflow_autograph(): + if interface != "numpy": + interface = USER_INPUT_TO_INTERFACE_MAP[interface] + if interface == "tf" and _use_tensorflow_autograph(): interface = "tf-autograph" if interface == "jax": try: # pragma: no cover @@ -439,6 +445,7 @@ def cost_fn(params, x): ### Specifying and preprocessing variables #### + _interface_user_input = interface interface = _get_interface_name(tapes, interface) # Only need to calculate derivatives with jax when we know it will be executed later. if interface in {"jax", "jax-jit"}: @@ -460,7 +467,11 @@ def cost_fn(params, x): ) # Mid-circuit measurement configuration validation - mcm_interface = interface or _get_interface_name(tapes, "auto") + # If the user specifies `interface=None`, regular execution considers it numpy, but the mcm + # workflow still needs to know if jax-jit is used + mcm_interface = ( + _get_interface_name(tapes, "auto") if _interface_user_input is None else interface + ) finite_shots = any(tape.shots for tape in tapes) _update_mcm_config(config.mcm_config, mcm_interface, finite_shots) @@ -479,12 +490,12 @@ def cost_fn(params, x): cache = None # changing this set of conditions causes a bunch of tests to break. - no_interface_boundary_required = interface in (None, "numpy") or config.gradient_method in { + no_interface_boundary_required = interface == "numpy" or config.gradient_method in { None, "backprop", } device_supports_interface_data = no_interface_boundary_required and ( - interface in (None, "numpy") + interface == "numpy" or config.gradient_method == "backprop" or getattr(device, "short_name", "") == "default.mixed" ) @@ -497,9 +508,9 @@ def cost_fn(params, x): numpy_only=not device_supports_interface_data, ) - # moved to its own explicit step so it will be easier to remove + # moved to its own explicit step so that it will be easier to remove def inner_execute_with_empty_jac(tapes, **_): - return (inner_execute(tapes), []) + return inner_execute(tapes), [] if interface in jpc_interfaces: execute_fn = inner_execute @@ -522,7 +533,7 @@ def inner_execute_with_empty_jac(tapes, **_): and getattr(device, "short_name", "") in ("lightning.gpu", "lightning.kokkos") and interface in jpc_interfaces ): # pragma: no cover - if INTERFACE_MAP[interface] == "jax" and "use_device_state" in gradient_kwargs: + if "jax" in interface and "use_device_state" in gradient_kwargs: gradient_kwargs["use_device_state"] = False jpc = LightningVJPs(device, gradient_kwargs=gradient_kwargs) @@ -563,7 +574,7 @@ def execute_fn(internal_tapes) -> tuple[ResultBatch, tuple]: config: the ExecutionConfig that specifies how to perform the simulations. """ numpy_tapes, _ = qml.transforms.convert_to_numpy_parameters(internal_tapes) - return (device.execute(numpy_tapes, config), tuple()) + return device.execute(numpy_tapes, config), tuple() def gradient_fn(internal_tapes): """A partial function that wraps compute_derivatives method of the device. @@ -612,7 +623,7 @@ def gradient_fn(internal_tapes): # trainable parameters can only be set on the first pass for jax # not higher order passes for higher order derivatives - if interface in {"jax", "jax-python", "jax-jit"}: + if "jax" in interface: for tape in tapes: params = tape.get_parameters(trainable_only=False) tape.trainable_params = qml.math.get_trainable_indices(params) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 39b12548be3..88fb4d422f3 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -32,7 +32,11 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.transforms.core import TransformContainer, TransformDispatcher, TransformProgram -from .execution import INTERFACE_MAP, SUPPORTED_INTERFACES, SupportedInterfaceUserInput +from .execution import ( + USER_INPUT_TO_INTERFACE_MAP, + SUPPORTED_INTERFACE_INPUTS, + SupportedInterfaceUserInput, +) logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) @@ -66,7 +70,18 @@ def _convert_to_interface(res, interface): if isinstance(res, dict): return {k: _convert_to_interface(v, interface) for k, v in res.items()} - return qml.math.asarray(res, like=interface if interface != "tf" else "tensorflow") + interface_conversion_map = { + "autograd": "autograd", + "jax": "jax", + "jax-jit": "jax", + "torch": "torch", + "tf": "tensorflow", + "tf-autograph": "tensorflow", + } + + interface_name = interface_conversion_map[interface] + + return qml.math.asarray(res, like=interface_name) def _make_execution_config( @@ -494,10 +509,10 @@ def __init__( gradient_kwargs, ) - if interface not in SUPPORTED_INTERFACES: + if interface not in SUPPORTED_INTERFACE_INPUTS: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " - f"one of {SUPPORTED_INTERFACES}." + f"one of {SUPPORTED_INTERFACE_INPUTS}." ) if not isinstance(device, (qml.devices.LegacyDevice, qml.devices.Device)): @@ -523,7 +538,7 @@ def __init__( # input arguments self.func = func self.device = device - self._interface = INTERFACE_MAP[interface] + self._interface = USER_INPUT_TO_INTERFACE_MAP[interface] self.diff_method = diff_method mcm_config = qml.devices.MCMConfig(mcm_method=mcm_method, postselect_mode=postselect_mode) cache = (max_diff > 1) if cache == "auto" else cache @@ -582,13 +597,13 @@ def interface(self) -> str: @interface.setter def interface(self, value: SupportedInterfaceUserInput): - if value not in SUPPORTED_INTERFACES: + if value not in SUPPORTED_INTERFACE_INPUTS: raise qml.QuantumFunctionError( - f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACES}." + f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACE_INPUTS}." ) - self._interface = INTERFACE_MAP[value] + self._interface = USER_INPUT_TO_INTERFACE_MAP[value] self._update_gradient_fn(shots=self.device.shots) @property @@ -895,12 +910,18 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result: execute_kwargs["mcm_config"] = mcm_config + # Mapping numpy to None here because `qml.execute` will map None back into + # numpy. If we do not do this, numpy will become autograd in `qml.execute`. + # If the user specified interface="numpy", it would've already been converted to + # "autograd", and it wouldn't be affected. + interface = self.interface if self.interface != "numpy" else None + # pylint: disable=unexpected-keyword-arg res = qml.execute( (self._tape,), device=self.device, gradient_fn=self.gradient_fn, - interface=self.interface, + interface=interface, transform_program=full_transform_program, inner_transform=inner_transform_program, config=config, @@ -930,10 +951,9 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: if qml.capture.enabled() else qml.math.get_interface(*args, *list(kwargs.values())) ) - if interface == "numpy": - # Internally stop treating numpy as autograd - interface = None - self._interface = INTERFACE_MAP[interface] + if interface != "numpy": + interface = USER_INPUT_TO_INTERFACE_MAP[interface] + self._interface = interface if self._qfunc_uses_shots_arg: override_shots = False else: From c21eee370dac105f056ed0cefd4f6e259702bdbe Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 12:09:05 -0400 Subject: [PATCH 41/59] fix isort --- pennylane/devices/legacy_facade.py | 6 ++++-- pennylane/workflow/qnode.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index c661b431dd7..2fbcb88ed04 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -326,8 +326,10 @@ def _create_temp_device(self, batch): for t in batch: params.extend(t.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) - if interface != "numpy": - interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) + if interface == "numpy": + return self._device + + interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) backprop_interface = self._device.capabilities().get("passthru_interface", None) if interface == backprop_interface: diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 88fb4d422f3..e22ada2c1a9 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -33,8 +33,8 @@ from pennylane.transforms.core import TransformContainer, TransformDispatcher, TransformProgram from .execution import ( - USER_INPUT_TO_INTERFACE_MAP, SUPPORTED_INTERFACE_INPUTS, + USER_INPUT_TO_INTERFACE_MAP, SupportedInterfaceUserInput, ) From ffd41a92d8345cdf7349b075031edcf6203abea6 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 12:36:52 -0400 Subject: [PATCH 42/59] update --- pennylane/workflow/qnode.py | 2 +- tests/test_qnode.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index e22ada2c1a9..c66ecba4bea 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -621,7 +621,7 @@ def add_transform(self, transform_container: TransformContainer): def _update_gradient_fn(self, shots=None, tape: Optional["qml.tape.QuantumTape"] = None): if self.diff_method is None: - self._interface = None + self._interface = "numpy" self.gradient_fn = None self.gradient_kwargs = {} return diff --git a/tests/test_qnode.py b/tests/test_qnode.py index b36c2cb557e..a252cb3067c 100644 --- a/tests/test_qnode.py +++ b/tests/test_qnode.py @@ -388,7 +388,7 @@ def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) - assert circuit.interface is None + assert circuit.interface == "numpy" assert circuit.gradient_fn is None assert circuit.device is dev From cee39762c97c375439a1c8d1b490d89bb55fec00 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 13:27:32 -0400 Subject: [PATCH 43/59] fix some tests --- tests/devices/default_qubit/test_default_qubit.py | 2 +- tests/measurements/test_sample.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/devices/default_qubit/test_default_qubit.py b/tests/devices/default_qubit/test_default_qubit.py index 8b3a1e257dd..d3049d90eae 100644 --- a/tests/devices/default_qubit/test_default_qubit.py +++ b/tests/devices/default_qubit/test_default_qubit.py @@ -1960,7 +1960,7 @@ def test_postselection_invalid_analytic( dev = qml.device("default.qubit") @qml.defer_measurements - @qml.qnode(dev, interface=interface) + @qml.qnode(dev, interface=None if interface == "numpy" else interface) def circ(): qml.RX(np.pi, 0) qml.CNOT([0, 1]) diff --git a/tests/measurements/test_sample.py b/tests/measurements/test_sample.py index 0111f4516b5..d31ce97d4a5 100644 --- a/tests/measurements/test_sample.py +++ b/tests/measurements/test_sample.py @@ -63,8 +63,8 @@ def circuit(): assert len(result) == 3 assert np.array_equal(result[0].shape, (n_sample,)) assert circuit._qfunc_output[0].shape(shots=n_sample, num_device_wires=3) == (n_sample,) - assert np.shape(result[1]) == () - assert np.shape(result[2]) == () + assert isinstance(result[1], np.float64) + assert isinstance(result[2], np.float64) def test_single_wire_sample(self): """Test the return type and shape of sampling a single wire""" @@ -121,8 +121,8 @@ def circuit(): # If all the dimensions are equal the result will end up to be a proper rectangular array assert len(result) == 3 - assert isinstance(result[0], np.ndarray) - assert isinstance(result[1], np.ndarray) + assert isinstance(result[0], float) + assert isinstance(result[1], float) assert result[2].dtype == np.dtype("float") assert np.array_equal(result[2].shape, (n_sample,)) From 087dc22a14ab9d11fe44e15ee506648036482f34 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 13:59:33 -0400 Subject: [PATCH 44/59] fix tests --- .../legacy_devices_integration/test_autograd_legacy.py | 2 +- tests/interfaces/test_jax_jit.py | 2 +- tests/qnn/test_keras.py | 6 +++++- tests/qnn/test_qnn_torch.py | 6 +++++- tests/test_qnode_legacy.py | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py b/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py index 03072f2515d..e80875179fa 100644 --- a/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py +++ b/tests/interfaces/legacy_devices_integration/test_autograd_legacy.py @@ -117,7 +117,7 @@ def cost(a, device): tape = qml.tape.QuantumScript.from_queue(q) return qml.execute([tape], device, gradient_fn=param_shift, interface="None")[0] - with pytest.raises(ValueError, match="interface must be in"): + with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): cost(a, device=dev) def test_grad_on_execution(self, mocker): diff --git a/tests/interfaces/test_jax_jit.py b/tests/interfaces/test_jax_jit.py index a9927dad7fb..eea7b6be52a 100644 --- a/tests/interfaces/test_jax_jit.py +++ b/tests/interfaces/test_jax_jit.py @@ -107,7 +107,7 @@ def cost(a, device): interface="None", )[0] - with pytest.raises(ValueError, match="Unknown interface"): + with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): cost(a, device=dev) def test_grad_on_execution(self, mocker): diff --git a/tests/qnn/test_keras.py b/tests/qnn/test_keras.py index f4f9769edc2..b208a8bf42d 100644 --- a/tests/qnn/test_keras.py +++ b/tests/qnn/test_keras.py @@ -588,7 +588,11 @@ def circuit(inputs, w1): return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) qlayer = KerasLayer(circuit, weight_shapes, output_dim=2) - assert qlayer.qnode.interface == circuit.interface == interface + assert ( + qlayer.qnode.interface + == circuit.interface + == qml.workflow.execution.USER_INPUT_TO_INTERFACE_MAP[interface] + ) @pytest.mark.tf diff --git a/tests/qnn/test_qnn_torch.py b/tests/qnn/test_qnn_torch.py index 64aeb9b1a9c..fce721e9cd6 100644 --- a/tests/qnn/test_qnn_torch.py +++ b/tests/qnn/test_qnn_torch.py @@ -632,7 +632,11 @@ def circuit(inputs, w1): return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)) qlayer = TorchLayer(circuit, weight_shapes) - assert qlayer.qnode.interface == circuit.interface == interface + assert ( + qlayer.qnode.interface + == circuit.interface + == qml.workflow.execution.USER_INPUT_TO_INTERFACE_MAP[interface] + ) @pytest.mark.torch diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 5a6f2e20040..323a725dfa0 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -416,7 +416,7 @@ def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) - assert circuit.interface is None + assert circuit.interface is "numpy" assert circuit.gradient_fn is None assert circuit.device is dev From 535e66b38fc2274a1612753a29bb59a4c68d4c81 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 14:07:44 -0400 Subject: [PATCH 45/59] make pylint happy --- tests/test_qnode_legacy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_qnode_legacy.py b/tests/test_qnode_legacy.py index 323a725dfa0..f0a25fd8e8c 100644 --- a/tests/test_qnode_legacy.py +++ b/tests/test_qnode_legacy.py @@ -416,7 +416,7 @@ def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) - assert circuit.interface is "numpy" + assert circuit.interface == "numpy" assert circuit.gradient_fn is None assert circuit.device is dev From 18c5fa671e9f972285723735fa6e169e64e41ae2 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 14:11:11 -0400 Subject: [PATCH 46/59] update name --- pennylane/devices/execution_config.py | 6 +++--- pennylane/devices/legacy_facade.py | 6 +++--- pennylane/workflow/__init__.py | 2 +- pennylane/workflow/execution.py | 12 ++++++------ pennylane/workflow/qnode.py | 18 +++++++++--------- tests/qnn/test_keras.py | 2 +- tests/qnn/test_qnn_torch.py | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pennylane/devices/execution_config.py b/pennylane/devices/execution_config.py index 1a6be124003..7f3866d9e86 100644 --- a/pennylane/devices/execution_config.py +++ b/pennylane/devices/execution_config.py @@ -17,7 +17,7 @@ from dataclasses import dataclass, field from typing import Optional, Union -from pennylane.workflow import SUPPORTED_INTERFACE_INPUTS +from pennylane.workflow import SUPPORTED_INTERFACE_NAMES @dataclass @@ -110,9 +110,9 @@ def __post_init__(self): Note that this hook is automatically called after init via the dataclass integration. """ - if self.interface not in SUPPORTED_INTERFACE_INPUTS: + if self.interface not in SUPPORTED_INTERFACE_NAMES: raise ValueError( - f"Unknown interface. interface must be in {SUPPORTED_INTERFACE_INPUTS}, got {self.interface} instead." + f"Unknown interface. interface must be in {SUPPORTED_INTERFACE_NAMES}, got {self.interface} instead." ) if self.grad_on_execution not in {True, False, None}: diff --git a/pennylane/devices/legacy_facade.py b/pennylane/devices/legacy_facade.py index 2fbcb88ed04..f3b33dd2a0c 100644 --- a/pennylane/devices/legacy_facade.py +++ b/pennylane/devices/legacy_facade.py @@ -25,7 +25,7 @@ import pennylane as qml from pennylane.measurements import MidMeasureMP, Shots from pennylane.transforms.core.transform_program import TransformProgram -from pennylane.workflow.execution import USER_INPUT_TO_INTERFACE_MAP +from pennylane.workflow.execution import INTERFACE_MAP from .device_api import Device from .execution_config import DefaultExecutionConfig @@ -329,7 +329,7 @@ def _create_temp_device(self, batch): if interface == "numpy": return self._device - interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) + interface = INTERFACE_MAP.get(interface, interface) backprop_interface = self._device.capabilities().get("passthru_interface", None) if interface == backprop_interface: @@ -398,7 +398,7 @@ def _validate_backprop_method(self, tape): params = tape.get_parameters(trainable_only=False) interface = qml.math.get_interface(*params) if interface != "numpy": - interface = USER_INPUT_TO_INTERFACE_MAP.get(interface, interface) + interface = INTERFACE_MAP.get(interface, interface) if tape and any(isinstance(m.obs, qml.SparseHamiltonian) for m in tape.measurements): return False diff --git a/pennylane/workflow/__init__.py b/pennylane/workflow/__init__.py index 169dec60342..b41c031e8a4 100644 --- a/pennylane/workflow/__init__.py +++ b/pennylane/workflow/__init__.py @@ -56,6 +56,6 @@ """ from .construct_batch import construct_batch, get_transform_program -from .execution import USER_INPUT_TO_INTERFACE_MAP, SUPPORTED_INTERFACE_INPUTS, execute +from .execution import INTERFACE_MAP, SUPPORTED_INTERFACE_NAMES, execute from .qnode import QNode, qnode from .set_shots import set_shots diff --git a/pennylane/workflow/execution.py b/pennylane/workflow/execution.py index e179624b39b..58d22d564c0 100644 --- a/pennylane/workflow/execution.py +++ b/pennylane/workflow/execution.py @@ -92,10 +92,10 @@ "tf-autograph", ) -USER_INPUT_TO_INTERFACE_MAP = dict(zip(get_args(SupportedInterfaceUserInput), _mapping_output)) +INTERFACE_MAP = dict(zip(get_args(SupportedInterfaceUserInput), _mapping_output)) """dict[str, str]: maps an allowed interface specification to its canonical name.""" -SUPPORTED_INTERFACE_INPUTS = list(USER_INPUT_TO_INTERFACE_MAP) +SUPPORTED_INTERFACE_NAMES = list(INTERFACE_MAP) """list[str]: allowed interface strings""" _CACHED_EXECUTION_WITH_FINITE_SHOTS_WARNINGS = ( @@ -260,12 +260,12 @@ def _get_interface_name(tapes, interface): Returns: str: Interface name""" - if interface not in SUPPORTED_INTERFACE_INPUTS: + if interface not in SUPPORTED_INTERFACE_NAMES: raise qml.QuantumFunctionError( - f"Unknown interface {interface}. Interface must be one of {SUPPORTED_INTERFACE_INPUTS}." + f"Unknown interface {interface}. Interface must be one of {SUPPORTED_INTERFACE_NAMES}." ) - interface = USER_INPUT_TO_INTERFACE_MAP[interface] + interface = INTERFACE_MAP[interface] if interface == "auto": params = [] @@ -273,7 +273,7 @@ def _get_interface_name(tapes, interface): params.extend(tape.get_parameters(trainable_only=False)) interface = qml.math.get_interface(*params) if interface != "numpy": - interface = USER_INPUT_TO_INTERFACE_MAP[interface] + interface = INTERFACE_MAP[interface] if interface == "tf" and _use_tensorflow_autograph(): interface = "tf-autograph" if interface == "jax": diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index c66ecba4bea..815375cd34d 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -33,8 +33,8 @@ from pennylane.transforms.core import TransformContainer, TransformDispatcher, TransformProgram from .execution import ( - SUPPORTED_INTERFACE_INPUTS, - USER_INPUT_TO_INTERFACE_MAP, + SUPPORTED_INTERFACE_NAMES, + INTERFACE_MAP, SupportedInterfaceUserInput, ) @@ -509,10 +509,10 @@ def __init__( gradient_kwargs, ) - if interface not in SUPPORTED_INTERFACE_INPUTS: + if interface not in SUPPORTED_INTERFACE_NAMES: raise qml.QuantumFunctionError( f"Unknown interface {interface}. Interface must be " - f"one of {SUPPORTED_INTERFACE_INPUTS}." + f"one of {SUPPORTED_INTERFACE_NAMES}." ) if not isinstance(device, (qml.devices.LegacyDevice, qml.devices.Device)): @@ -538,7 +538,7 @@ def __init__( # input arguments self.func = func self.device = device - self._interface = USER_INPUT_TO_INTERFACE_MAP[interface] + self._interface = INTERFACE_MAP[interface] self.diff_method = diff_method mcm_config = qml.devices.MCMConfig(mcm_method=mcm_method, postselect_mode=postselect_mode) cache = (max_diff > 1) if cache == "auto" else cache @@ -597,13 +597,13 @@ def interface(self) -> str: @interface.setter def interface(self, value: SupportedInterfaceUserInput): - if value not in SUPPORTED_INTERFACE_INPUTS: + if value not in SUPPORTED_INTERFACE_NAMES: raise qml.QuantumFunctionError( - f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACE_INPUTS}." + f"Unknown interface {value}. Interface must be one of {SUPPORTED_INTERFACE_NAMES}." ) - self._interface = USER_INPUT_TO_INTERFACE_MAP[value] + self._interface = INTERFACE_MAP[value] self._update_gradient_fn(shots=self.device.shots) @property @@ -952,7 +952,7 @@ def _impl_call(self, *args, **kwargs) -> qml.typing.Result: else qml.math.get_interface(*args, *list(kwargs.values())) ) if interface != "numpy": - interface = USER_INPUT_TO_INTERFACE_MAP[interface] + interface = INTERFACE_MAP[interface] self._interface = interface if self._qfunc_uses_shots_arg: override_shots = False diff --git a/tests/qnn/test_keras.py b/tests/qnn/test_keras.py index b208a8bf42d..1115460922d 100644 --- a/tests/qnn/test_keras.py +++ b/tests/qnn/test_keras.py @@ -591,7 +591,7 @@ def circuit(inputs, w1): assert ( qlayer.qnode.interface == circuit.interface - == qml.workflow.execution.USER_INPUT_TO_INTERFACE_MAP[interface] + == qml.workflow.execution.INTERFACE_MAP[interface] ) diff --git a/tests/qnn/test_qnn_torch.py b/tests/qnn/test_qnn_torch.py index fce721e9cd6..e2642df0e4b 100644 --- a/tests/qnn/test_qnn_torch.py +++ b/tests/qnn/test_qnn_torch.py @@ -635,7 +635,7 @@ def circuit(inputs, w1): assert ( qlayer.qnode.interface == circuit.interface - == qml.workflow.execution.USER_INPUT_TO_INTERFACE_MAP[interface] + == qml.workflow.execution.INTERFACE_MAP[interface] ) From 7fde693eb8de05f57030114877255266ff653ec1 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 14:19:38 -0400 Subject: [PATCH 47/59] fix isort --- pennylane/workflow/qnode.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 815375cd34d..6407c6b2690 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -32,11 +32,7 @@ from pennylane.tape import QuantumScript, QuantumTape from pennylane.transforms.core import TransformContainer, TransformDispatcher, TransformProgram -from .execution import ( - SUPPORTED_INTERFACE_NAMES, - INTERFACE_MAP, - SupportedInterfaceUserInput, -) +from .execution import INTERFACE_MAP, SUPPORTED_INTERFACE_NAMES, SupportedInterfaceUserInput logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) From 9e86111f0006d2452ee835136bd213e42ce22669 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Tue, 10 Sep 2024 15:04:15 -0400 Subject: [PATCH 48/59] fix tests --- tests/devices/qubit/test_simulate.py | 2 +- .../legacy_devices_integration/test_jax_jit_legacy.py | 2 +- tests/interfaces/legacy_devices_integration/test_jax_legacy.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/devices/qubit/test_simulate.py b/tests/devices/qubit/test_simulate.py index dbe9573b8df..4dce5afd4c5 100644 --- a/tests/devices/qubit/test_simulate.py +++ b/tests/devices/qubit/test_simulate.py @@ -205,7 +205,7 @@ def test_result_has_correct_interface(self, op): def test_expand_state_keeps_autograd_interface(self): """Test that expand_state doesn't convert autograd to numpy.""" - @qml.qnode(qml.device("default.qubit", wires=2)) + @qml.qnode(qml.device("default.qubit", wires=2), interface="autograd") def circuit(x): qml.RX(x, 0) return qml.probs(wires=[0, 1]) diff --git a/tests/interfaces/legacy_devices_integration/test_jax_jit_legacy.py b/tests/interfaces/legacy_devices_integration/test_jax_jit_legacy.py index eaa0cd95cf8..5ab698f5695 100644 --- a/tests/interfaces/legacy_devices_integration/test_jax_jit_legacy.py +++ b/tests/interfaces/legacy_devices_integration/test_jax_jit_legacy.py @@ -107,7 +107,7 @@ def cost(a, device): interface="None", )[0] - with pytest.raises(ValueError, match="Unknown interface"): + with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): cost(a, device=dev) def test_grad_on_execution(self, mocker): diff --git a/tests/interfaces/legacy_devices_integration/test_jax_legacy.py b/tests/interfaces/legacy_devices_integration/test_jax_legacy.py index ecaa78a4164..3f885a37d52 100644 --- a/tests/interfaces/legacy_devices_integration/test_jax_legacy.py +++ b/tests/interfaces/legacy_devices_integration/test_jax_legacy.py @@ -104,7 +104,7 @@ def cost(a, device): interface="None", )[0] - with pytest.raises(ValueError, match="Unknown interface"): + with pytest.raises(qml.QuantumFunctionError, match="Unknown interface"): cost(a, device=dev) def test_grad_on_execution(self, mocker): From c4415a8a566363a918335e427e54526e3f07131f Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 11 Sep 2024 09:55:05 -0400 Subject: [PATCH 49/59] Update pennylane/workflow/qnode.py Co-authored-by: Christina Lee --- pennylane/workflow/qnode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/workflow/qnode.py b/pennylane/workflow/qnode.py index 6407c6b2690..f419e78b997 100644 --- a/pennylane/workflow/qnode.py +++ b/pennylane/workflow/qnode.py @@ -910,7 +910,7 @@ def _execution_component(self, args: tuple, kwargs: dict) -> qml.typing.Result: # numpy. If we do not do this, numpy will become autograd in `qml.execute`. # If the user specified interface="numpy", it would've already been converted to # "autograd", and it wouldn't be affected. - interface = self.interface if self.interface != "numpy" else None + interface = None if self.interface == "numpy" else self.interface # pylint: disable=unexpected-keyword-arg res = qml.execute( From 125e06cffc2975e97474f44c0f0da0f81e36725f Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 11 Sep 2024 16:12:24 -0400 Subject: [PATCH 50/59] Add reference.qubit to torch tests --- pennylane/devices/reference_qubit.py | 2 ++ tests/interfaces/test_torch.py | 15 +++++++++++++++ tests/interfaces/test_torch_qnode.py | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index 629634629ee..52a7bfbb5b5 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -74,6 +74,8 @@ def simulate(tape: qml.tape.QuantumTape, seed=None) -> qml.typing.Result: results = [res[0] for res in results] if not tape.shots.has_partitioned_shots: results = results[0] + else: + results = tuple(results) else: results = tuple(mp.process_state(state, tape.wires) for mp in tape.measurements) if len(tape.measurements) == 1: diff --git a/tests/interfaces/test_torch.py b/tests/interfaces/test_torch.py index 3cdcf5eae30..3640d31de9c 100644 --- a/tests/interfaces/test_torch.py +++ b/tests/interfaces/test_torch.py @@ -159,6 +159,21 @@ def cost_cache(x): Shots((100000, 100000)), ParamShiftDerivativesDevice(), ), + ( + {"gradient_fn": param_shift}, + Shots(None), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots(100000), + qml.device("reference.qubit"), + ), + ( + {"gradient_fn": param_shift}, + Shots((100000, 100000)), + qml.device("reference.qubit"), + ), ] diff --git a/tests/interfaces/test_torch_qnode.py b/tests/interfaces/test_torch_qnode.py index 35854f13694..2779ff8ab37 100644 --- a/tests/interfaces/test_torch_qnode.py +++ b/tests/interfaces/test_torch_qnode.py @@ -47,6 +47,7 @@ [ParamShiftDerivativesDevice(), "best", False, False], [ParamShiftDerivativesDevice(), "parameter-shift", True, False], [ParamShiftDerivativesDevice(), "parameter-shift", False, True], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] interface_and_qubit_device_and_diff_method = [ @@ -1128,6 +1129,8 @@ def test_projector( tol = TOL_FOR_SPSA elif diff_method == "hadamard": pytest.skip("Hadamard does not support variances.") + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = torch.tensor(state, requires_grad=False) @@ -1164,6 +1167,9 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") + @qml.qnode( dev, diff_method=diff_method, From b91fc7bfdc16938cb183671c39f1de642f32dbfb Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Wed, 11 Sep 2024 16:14:14 -0400 Subject: [PATCH 51/59] add reference.qubit to more tensorflow tests --- tests/interfaces/test_tensorflow_qnode.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/interfaces/test_tensorflow_qnode.py b/tests/interfaces/test_tensorflow_qnode.py index e3c4597ae06..374996d84ef 100644 --- a/tests/interfaces/test_tensorflow_qnode.py +++ b/tests/interfaces/test_tensorflow_qnode.py @@ -38,6 +38,7 @@ [qml.device("lightning.qubit", wires=4), "adjoint", False, False], [qml.device("lightning.qubit", wires=4), "adjoint", True, True], [qml.device("lightning.qubit", wires=4), "adjoint", True, False], + [qml.device("reference.qubit"), "parameter-shift", False, False], ] TOL_FOR_SPSA = 1.0 @@ -974,6 +975,8 @@ def test_projector( kwargs["sampler_rng"] = np.random.default_rng(SEED_FOR_SPSA) kwargs["num_directions"] = 20 tol = TOL_FOR_SPSA + if dev.name == "reference.qubit": + pytest.xfail("diagonalize_measurements do not support projectors (sc-72911)") P = tf.constant(state, dtype=dtype) @@ -1008,6 +1011,9 @@ def test_postselection_differentiation( if diff_method in ["adjoint", "spsa", "hadamard"]: pytest.skip("Diff method does not support postselection.") + if dev.name == "reference.qubit": + pytest.skip("reference.qubit does not support postselection.") + @qml.qnode( dev, diff_method=diff_method, From b6ad427c8689609ef37b10d6c25039669a538d34 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 12 Sep 2024 10:30:04 -0400 Subject: [PATCH 52/59] changelog --- doc/releases/changelog-dev.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md index c46feffc318..716c87ab5c3 100644 --- a/doc/releases/changelog-dev.md +++ b/doc/releases/changelog-dev.md @@ -7,18 +7,14 @@

Improvements 🛠

* `qml.qchem.excitations` now optionally returns fermionic operators. - [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) + [(#6171)](https://github.com/PennyLaneAI/pennylane/pull/6171) * The `diagonalize_measurements` transform now uses a more efficient method of diagonalization when possible, based on the `pauli_rep` of the relevant observables. [#6113](https://github.com/PennyLaneAI/pennylane/pull/6113/) -

Capturing and representing hybrid programs

- -* Differentiation of hybrid programs via `qml.grad` can now be captured into plxpr. - When evaluating a captured `qml.grad` instruction, it will dispatch to `jax.grad`, - which differs from the Autograd implementation of `qml.grad` itself. - [(#6120)](https://github.com/PennyLaneAI/pennylane/pull/6120) +* The `Hermitian` operator now has a `compute_sparse_matrix` implementation. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225)

Capturing and representing hybrid programs

@@ -101,12 +97,19 @@ * The ``qml.Qubitization`` template now orders the ``control`` wires first and the ``hamiltonian`` wires second, which is the expected according to other templates. [(#6229)](https://github.com/PennyLaneAI/pennylane/pull/6229) -*

Contributors ✍️

+* Fixes a bug where a circuit using the `autograd` interface sometimes returns nested values that are not of the `autograd` interface. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225) + +* Fixes a bug where a simple circuit with no parameters or only builtin/numpy arrays as parameters returns autograd tensors. + [(#6225)](https://github.com/PennyLaneAI/pennylane/pull/6225) + +

Contributors ✍️

This release contains contributions from (in alphabetical order): Guillermo Alonso, Utkarsh Azad, +Astral Cai, Lillian M. A. Frederiksen, Christina Lee, William Maxwell, From 8fe18d1fd3e243a99f60a1e8cc718544465f700c Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Thu, 12 Sep 2024 15:40:05 -0400 Subject: [PATCH 53/59] fix test --- tests/interfaces/test_autograd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py index 04c8bab3459..d206f1758d3 100644 --- a/tests/interfaces/test_autograd.py +++ b/tests/interfaces/test_autograd.py @@ -845,7 +845,7 @@ def test_multiple_hamiltonians_trainable(self, execute_kwargs, cost_fn, shots): else: assert np.allclose(res, expected, atol=atol_for_shots(shots), rtol=0) - res = np.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2)) + res = pnp.hstack(qml.jacobian(cost_fn)(weights, coeffs1, coeffs2)) expected = self.cost_fn_jacobian(weights, coeffs1, coeffs2) if shots.has_partitioned_shots: pytest.xfail( From 5b6427f35679f49d1f4e9d6ad6197bc447569251 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 11:38:35 -0400 Subject: [PATCH 54/59] Update pennylane/devices/reference_qubit.py Co-authored-by: Christina Lee --- pennylane/devices/reference_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index 52a7bfbb5b5..947f32c1ee1 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -139,7 +139,7 @@ def preprocess(self, execution_config=DefaultExecutionConfig): decompose, stopping_condition=supports_operation, skip_initial_state_prep=False, - name="mini.qubit", + name="reference.qubit", ) program.add_transform(qml.transforms.diagonalize_measurements) program.add_transform(validate_measurements, name="reference.qubit") From 52caafcb89c353b023b8897050f81399fe50b079 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 11:43:59 -0400 Subject: [PATCH 55/59] Update pennylane/devices/reference_qubit.py Co-authored-by: Christina Lee --- pennylane/devices/reference_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index 947f32c1ee1..b06706467ba 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -135,13 +135,13 @@ def preprocess(self, execution_config=DefaultExecutionConfig): program.add_transform(validate_device_wires, wires=self.wires, name="reference.qubit") program.add_transform(qml.defer_measurements) program.add_transform(qml.transforms.split_non_commuting) + program.add_transform(qml.transforms.diagonalize_measurements) program.add_transform( decompose, stopping_condition=supports_operation, skip_initial_state_prep=False, name="reference.qubit", ) - program.add_transform(qml.transforms.diagonalize_measurements) program.add_transform(validate_measurements, name="reference.qubit") program.add_transform(qml.transforms.broadcast_expand) From b6fadc60ffb49b29080bca4d76b6e151751ecc69 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 15:38:40 -0400 Subject: [PATCH 56/59] update skip message --- tests/interfaces/test_jax_jit_qnode.py | 5 ++++- tests/interfaces/test_jax_qnode.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py index 64c35d7b6fc..8b828e5b04e 100644 --- a/tests/interfaces/test_jax_jit_qnode.py +++ b/tests/interfaces/test_jax_jit_qnode.py @@ -1514,7 +1514,10 @@ def test_hamiltonian_expansion_analytic( and the first and second order gradients are correctly evaluated""" gradient_kwargs = {} if dev.name == "reference.qubit": - pytest.xfail("reference.qubit does not support hamiltonians") + pytest.skip( + "Cannot add transform to the transform program in preprocessing" + "when using mocker.spy on it." + ) if dev.name == "param_shift.qubit": pytest.xfail("gradients transforms have a different vjp shape convention.") if diff_method == "adjoint": diff --git a/tests/interfaces/test_jax_qnode.py b/tests/interfaces/test_jax_qnode.py index 1b8694becc7..1c63596de27 100644 --- a/tests/interfaces/test_jax_qnode.py +++ b/tests/interfaces/test_jax_qnode.py @@ -1391,7 +1391,10 @@ def test_split_non_commuting_analytic( } tol = TOL_FOR_SPSA if dev.name == "reference.qubit": - pytest.skip("The reference device does not support Hamiltonians") + pytest.skip( + "Cannot add transform to the transform program in preprocessing" + "when using mocker.spy on it." + ) spy = mocker.spy(qml.transforms, "split_non_commuting") obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1), qml.PauliZ(0) @ qml.PauliZ(1)] From d31c0c79ae65463d941c7fd7bcddc5bdb49f2113 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Fri, 13 Sep 2024 15:40:31 -0400 Subject: [PATCH 57/59] add tape assertion --- pennylane/devices/reference_qubit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index b06706467ba..2bff748b3c3 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -149,4 +149,6 @@ def preprocess(self, execution_config=DefaultExecutionConfig): return program, execution_config def execute(self, circuits, execution_config=DefaultExecutionConfig): + for tape in circuits: + assert all(supports_operation(op) for op in tape.operations) return tuple(simulate(tape, seed=self._rng) for tape in circuits) From bf4b212c727941c6f80a77616fceed03e55a94de Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 16 Sep 2024 16:42:13 -0400 Subject: [PATCH 58/59] Update pennylane/devices/reference_qubit.py Co-authored-by: lillian542 <38584660+lillian542@users.noreply.github.com> --- pennylane/devices/reference_qubit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pennylane/devices/reference_qubit.py b/pennylane/devices/reference_qubit.py index 2bff748b3c3..49537d71a6e 100644 --- a/pennylane/devices/reference_qubit.py +++ b/pennylane/devices/reference_qubit.py @@ -106,7 +106,7 @@ class ReferenceQubit(Device): Args: wires (int, Iterable[Number, str]): Number of wires present on the device, or iterable that contains unique labels for the wires as numbers (i.e., ``[-1, 0, 2]``) or strings - (``['ancilla', 'q1', 'q2']``). Default ``None`` if not specified. While this device allows + (``['aux', 'q1', 'q2']``). Default ``None`` if not specified. While this device allows for ``wires`` to be unspecified at construction time, other devices may make this argument mandatory. Devices can also implement additional restrictions on the possible wires. shots (int, Sequence[int], Sequence[Union[int, Sequence[int]]]): The default number of shots From 449a0cde8c9c15360de118a0d8b286fbdd5f9865 Mon Sep 17 00:00:00 2001 From: Astral Cai Date: Mon, 16 Sep 2024 16:44:19 -0400 Subject: [PATCH 59/59] add ReferenceQubit to devices --- pennylane/devices/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pennylane/devices/__init__.py b/pennylane/devices/__init__.py index a542ba7df1d..ac9581ede40 100644 --- a/pennylane/devices/__init__.py +++ b/pennylane/devices/__init__.py @@ -37,6 +37,7 @@ _qubit_device _qutrit_device null_qubit + reference_qubit tests Next generation devices @@ -58,6 +59,7 @@ DefaultQubit DefaultTensor NullQubit + ReferenceQubit DefaultQutritMixed LegacyDeviceFacade @@ -160,6 +162,7 @@ def execute(self, circuits, execution_config = qml.devices.DefaultExecutionConfi from .default_clifford import DefaultClifford from .default_tensor import DefaultTensor from .null_qubit import NullQubit +from .reference_qubit import ReferenceQubit from .default_qutrit import DefaultQutrit from .default_qutrit_mixed import DefaultQutritMixed from ._legacy_device import Device as LegacyDevice