From 8d1e0590a972bcd0f4e1568287cf2ad877718954 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 2 May 2024 14:06:28 -0400 Subject: [PATCH 1/8] use map wires instead of map_to_standard_wires --- .github/CHANGELOG.md | 2 + .../lightning_qubit/lightning_qubit.py | 44 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index f80ca8802d..8c027df4cd 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -105,6 +105,8 @@ ### Bug fixes +* Lightning Qubit once again respects the wire order specified on device instantiation. + * `dynamic_one_shot` was refactored to use `SampleMP` measurements as a way to return the mid-circuit measurement samples. `LightningQubit`'s `simulate` is modified accordingly. [(#694)](https://github.com/PennyLaneAI/pennylane/pull/694) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 8350804c18..d7ad53ac07 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -17,6 +17,7 @@ from dataclasses import replace from numbers import Number from pathlib import Path +from this import d from typing import Callable, Optional, Sequence, Tuple, Union import numpy as np @@ -87,7 +88,7 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, mcmc: dict = N return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) -def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False): +def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): """Compute the Jacobian for a single quantum script. Args: @@ -96,17 +97,19 @@ def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False) batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: TensorLike: The Jacobian of the quantum script """ - circuit = circuit.map_to_standard_wires() + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) state.reset_state() final_state = state.get_final_state(circuit) return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) -def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False): +def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): """Simulate a single quantum script and compute its Jacobian. Args: @@ -115,20 +118,22 @@ def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, bat batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. Default is False. + wire_map (Optional[dict]): a map from wire labels to simulation indices Returns: Tuple[TensorLike]: The results of the simulation and the calculated Jacobian Note that this function can return measurements for non-commuting observables simultaneously. """ - circuit = circuit.map_to_standard_wires() + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) res = simulate(circuit, state) jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit) return res, jac def vjp( - circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False + circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False, wire_map=None, ): """Compute the Vector-Jacobian Product (VJP) for a single quantum script. Args: @@ -141,10 +146,13 @@ def vjp( batch_obs (bool): Determine whether we process observables in parallel when computing the VJP. This value is only relevant when the lightning qubit is built with OpenMP. + wire_map (Optional[dict]): a map from wire labels to simulation indices + Returns: TensorLike: The VJP of the quantum script """ - circuit = circuit.map_to_standard_wires() + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) state.reset_state() final_state = state.get_final_state(circuit) return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp( @@ -153,7 +161,7 @@ def vjp( def simulate_and_vjp( - circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False + circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False, wire_map=None ): """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). Args: @@ -166,11 +174,14 @@ def simulate_and_vjp( batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. + wire_map (Optional[dict]): a map from wire labels to simulation indices + Returns: Tuple[TensorLike]: The results of the simulation and the calculated VJP Note that this function can return measurements for non-commuting observables simultaneously. """ - circuit = circuit.map_to_standard_wires() + if wire_map is not None: + [circuit], _ = qml.map_wires(circuit, wire_map) res = simulate(circuit, state) _vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents) return res, _vjp @@ -449,6 +460,11 @@ def __init__( # pylint: disable=too-many-arguments super().__init__(wires=wires, shots=shots) + if isinstance(wires, int): + self._wire_map = None # should just use wires as is + else: + self._wire_map = {w: i for i, w in enumerate(self.wires)} + self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype) # TODO: Investigate usefulness of creating numpy random generator @@ -568,7 +584,8 @@ def execute( } results = [] for circuit in circuits: - circuit = circuit.map_to_standard_wires() + if self._wire_map is not None: + [circuit], _ = qml.map_wires(circuit, self._wire_map) results.append(simulate(circuit, self._statevector, mcmc=mcmc)) return tuple(results) @@ -613,8 +630,9 @@ def compute_derivatives( Tuple: The jacobian for each trainable parameter """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) + return tuple( - jacobian(circuit, self._statevector, batch_obs=batch_obs) for circuit in circuits + jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for circuit in circuits ) def execute_and_compute_derivatives( @@ -633,7 +651,7 @@ def execute_and_compute_derivatives( """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) results = tuple( - simulate_and_jacobian(c, self._statevector, batch_obs=batch_obs) for c in circuits + simulate_and_jacobian(c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for c in circuits ) return tuple(zip(*results)) @@ -686,7 +704,7 @@ def compute_vjp( """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) return tuple( - vjp(circuit, cots, self._statevector, batch_obs=batch_obs) + vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for circuit, cots in zip(circuits, cotangents) ) @@ -708,7 +726,7 @@ def execute_and_compute_vjp( """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) results = tuple( - simulate_and_vjp(circuit, cots, self._statevector, batch_obs=batch_obs) + simulate_and_vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for circuit, cots in zip(circuits, cotangents) ) return tuple(zip(*results)) From f1accdf6ecfa859b2d357656834ab0ce526d9092 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 2 May 2024 14:07:57 -0400 Subject: [PATCH 2/8] oops --- pennylane_lightning/lightning_qubit/lightning_qubit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index d7ad53ac07..1c4c35c9a4 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -17,7 +17,6 @@ from dataclasses import replace from numbers import Number from pathlib import Path -from this import d from typing import Callable, Optional, Sequence, Tuple, Union import numpy as np From 6f253f77eb209039ba71f75ea401f08dbf8201c2 Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Thu, 2 May 2024 14:15:55 -0400 Subject: [PATCH 3/8] Update .github/CHANGELOG.md --- .github/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 8c027df4cd..438e85ee1f 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -106,6 +106,7 @@ ### Bug fixes * Lightning Qubit once again respects the wire order specified on device instantiation. + [(#705)](https://github.com/PennyLaneAI/pennylane-lightning/pull/705/) * `dynamic_one_shot` was refactored to use `SampleMP` measurements as a way to return the mid-circuit measurement samples. `LightningQubit`'s `simulate` is modified accordingly. [(#694)](https://github.com/PennyLaneAI/pennylane/pull/694) From 41c673ec3a23719d785f4f1d6321c3346955d055 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 2 May 2024 14:19:14 -0400 Subject: [PATCH 4/8] black --- .../lightning_qubit/lightning_qubit.py | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit.py b/pennylane_lightning/lightning_qubit/lightning_qubit.py index 1c4c35c9a4..bc28bd0914 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit.py @@ -108,7 +108,9 @@ def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit) -def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None): +def simulate_and_jacobian( + circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None +): """Simulate a single quantum script and compute its Jacobian. Args: @@ -132,7 +134,11 @@ def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, bat def vjp( - circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False, wire_map=None, + circuit: QuantumTape, + cotangents: Tuple[Number], + state: LightningStateVector, + batch_obs=False, + wire_map=None, ): """Compute the Vector-Jacobian Product (VJP) for a single quantum script. Args: @@ -160,7 +166,11 @@ def vjp( def simulate_and_vjp( - circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False, wire_map=None + circuit: QuantumTape, + cotangents: Tuple[Number], + state: LightningStateVector, + batch_obs=False, + wire_map=None, ): """Simulate a single quantum script and compute its Vector-Jacobian Product (VJP). Args: @@ -460,7 +470,7 @@ def __init__( # pylint: disable=too-many-arguments super().__init__(wires=wires, shots=shots) if isinstance(wires, int): - self._wire_map = None # should just use wires as is + self._wire_map = None # should just use wires as is else: self._wire_map = {w: i for i, w in enumerate(self.wires)} @@ -631,7 +641,8 @@ def compute_derivatives( batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) return tuple( - jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for circuit in circuits + jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + for circuit in circuits ) def execute_and_compute_derivatives( @@ -650,7 +661,10 @@ def execute_and_compute_derivatives( """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) results = tuple( - simulate_and_jacobian(c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) for c in circuits + simulate_and_jacobian( + c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) + for c in circuits ) return tuple(zip(*results)) @@ -725,7 +739,9 @@ def execute_and_compute_vjp( """ batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs) results = tuple( - simulate_and_vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map) + simulate_and_vjp( + circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map + ) for circuit, cots in zip(circuits, cotangents) ) return tuple(zip(*results)) From 96e140e1e228c759b83f399bd9dc8c7310921b84 Mon Sep 17 00:00:00 2001 From: AmintorDusko Date: Thu, 2 May 2024 14:30:24 -0400 Subject: [PATCH 5/8] Trigger CIs From 2f4bbf8acce30f097d36a6c0651cdae89abe5ecb Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 2 May 2024 16:13:29 -0400 Subject: [PATCH 6/8] add test --- tests/new_api/test_device.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index d713745b68..4278e02996 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -456,6 +456,26 @@ def test_custom_wires(self, phi, theta, wires): assert np.allclose(result[0], np.cos(phi)) assert np.allclose(result[1], np.cos(phi) * np.cos(theta)) + @pytest.mark.parametrize("wires, wire_order", [(3, (0,1,2)), (("a", "b", "c"), ("a", "b", "c"))]) + def test_probs_different_wire_orders(self, wires, wire_order): + """Test that measuring probabilities works with custom wires.""" + + dev = LightningDevice(wires=wires) + + op = qml.Hadamard(wire_order[1]) + + tape = QuantumScript([op], [qml.probs(wires=(wire_order[0], wire_order[1]))]) + + res = dev.execute(tape) + assert qml.math.allclose(res, np.array([0.5, 0.5, 0.0, 0.0])) + + tape2 = QuantumScript([op], [qml.probs(wires=(wire_order[1], wire_order[2]))]) + res2 = dev.execute(tape2) + assert qml.math.allclose(res2, np.array([0.5, 0.0, 0.5, 0.0])) + + tape3 = QuantumScript([op], [qml.probs(wires=(wire_order[1], wire_order[0]))]) + res3 = dev.execute(tape3) + assert qml.math.allclose(res3, np.array([0.5, 0.0, 0.5, 0.0])) @pytest.mark.parametrize("batch_obs", [True, False]) class TestDerivatives: From abcff9759358620294c0bebcfe1916da5eabecf4 Mon Sep 17 00:00:00 2001 From: albi3ro Date: Thu, 2 May 2024 17:17:38 -0400 Subject: [PATCH 7/8] black on tests --- tests/new_api/test_device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/new_api/test_device.py b/tests/new_api/test_device.py index 4278e02996..a1a5912160 100644 --- a/tests/new_api/test_device.py +++ b/tests/new_api/test_device.py @@ -456,7 +456,9 @@ def test_custom_wires(self, phi, theta, wires): assert np.allclose(result[0], np.cos(phi)) assert np.allclose(result[1], np.cos(phi) * np.cos(theta)) - @pytest.mark.parametrize("wires, wire_order", [(3, (0,1,2)), (("a", "b", "c"), ("a", "b", "c"))]) + @pytest.mark.parametrize( + "wires, wire_order", [(3, (0, 1, 2)), (("a", "b", "c"), ("a", "b", "c"))] + ) def test_probs_different_wire_orders(self, wires, wire_order): """Test that measuring probabilities works with custom wires.""" @@ -477,6 +479,7 @@ def test_probs_different_wire_orders(self, wires, wire_order): res3 = dev.execute(tape3) assert qml.math.allclose(res3, np.array([0.5, 0.0, 0.5, 0.0])) + @pytest.mark.parametrize("batch_obs", [True, False]) class TestDerivatives: """Unit tests for calculating derivatives with a device""" From 4a5f1563a264e900e34125813a47f08171510fe9 Mon Sep 17 00:00:00 2001 From: Christina Lee Date: Thu, 2 May 2024 17:17:55 -0400 Subject: [PATCH 8/8] Update .github/CHANGELOG.md Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com> --- .github/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 438e85ee1f..7727e7bbf3 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -106,7 +106,7 @@ ### Bug fixes * Lightning Qubit once again respects the wire order specified on device instantiation. - [(#705)](https://github.com/PennyLaneAI/pennylane-lightning/pull/705/) + [(#705)](https://github.com/PennyLaneAI/pennylane-lightning/pull/705) * `dynamic_one_shot` was refactored to use `SampleMP` measurements as a way to return the mid-circuit measurement samples. `LightningQubit`'s `simulate` is modified accordingly. [(#694)](https://github.com/PennyLaneAI/pennylane/pull/694)