Skip to content

Commit 834ca47

Browse files
albi3roAmintorDuskomaliasadi
authored
Use wire order specified on instantiation (#705)
* use map wires instead of map_to_standard_wires * oops * Update .github/CHANGELOG.md * black * Trigger CIs * add test * black on tests * Update .github/CHANGELOG.md --------- Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: Ali Asadi <[email protected]>
1 parent e11e58b commit 834ca47

File tree

3 files changed

+72
-13
lines changed

3 files changed

+72
-13
lines changed

.github/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@
108108

109109
### Bug fixes
110110

111+
* Lightning Qubit once again respects the wire order specified on device instantiation.
112+
[(#705)](https://github.com/PennyLaneAI/pennylane-lightning/pull/705)
113+
111114
* `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.
112115
[(#694)](https://github.com/PennyLaneAI/pennylane-lightning/pull/694)
113116

pennylane_lightning/lightning_qubit/lightning_qubit.py

+46-13
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def simulate(circuit: QuantumScript, state: LightningStateVector, mcmc: dict = N
8787
return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit)
8888

8989

90-
def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False):
90+
def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None):
9191
"""Compute the Jacobian for a single quantum script.
9292
9393
Args:
@@ -96,17 +96,21 @@ def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False)
9696
batch_obs (bool): Determine whether we process observables in parallel when
9797
computing the jacobian. This value is only relevant when the lightning
9898
qubit is built with OpenMP. Default is False.
99+
wire_map (Optional[dict]): a map from wire labels to simulation indices
99100
100101
Returns:
101102
TensorLike: The Jacobian of the quantum script
102103
"""
103-
circuit = circuit.map_to_standard_wires()
104+
if wire_map is not None:
105+
[circuit], _ = qml.map_wires(circuit, wire_map)
104106
state.reset_state()
105107
final_state = state.get_final_state(circuit)
106108
return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_jacobian(circuit)
107109

108110

109-
def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False):
111+
def simulate_and_jacobian(
112+
circuit: QuantumTape, state: LightningStateVector, batch_obs=False, wire_map=None
113+
):
110114
"""Simulate a single quantum script and compute its Jacobian.
111115
112116
Args:
@@ -115,20 +119,26 @@ def simulate_and_jacobian(circuit: QuantumTape, state: LightningStateVector, bat
115119
batch_obs (bool): Determine whether we process observables in parallel when
116120
computing the jacobian. This value is only relevant when the lightning
117121
qubit is built with OpenMP. Default is False.
122+
wire_map (Optional[dict]): a map from wire labels to simulation indices
118123
119124
Returns:
120125
Tuple[TensorLike]: The results of the simulation and the calculated Jacobian
121126
122127
Note that this function can return measurements for non-commuting observables simultaneously.
123128
"""
124-
circuit = circuit.map_to_standard_wires()
129+
if wire_map is not None:
130+
[circuit], _ = qml.map_wires(circuit, wire_map)
125131
res = simulate(circuit, state)
126132
jac = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_jacobian(circuit)
127133
return res, jac
128134

129135

130136
def vjp(
131-
circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False
137+
circuit: QuantumTape,
138+
cotangents: Tuple[Number],
139+
state: LightningStateVector,
140+
batch_obs=False,
141+
wire_map=None,
132142
):
133143
"""Compute the Vector-Jacobian Product (VJP) for a single quantum script.
134144
Args:
@@ -141,10 +151,13 @@ def vjp(
141151
batch_obs (bool): Determine whether we process observables in parallel when
142152
computing the VJP. This value is only relevant when the lightning
143153
qubit is built with OpenMP.
154+
wire_map (Optional[dict]): a map from wire labels to simulation indices
155+
144156
Returns:
145157
TensorLike: The VJP of the quantum script
146158
"""
147-
circuit = circuit.map_to_standard_wires()
159+
if wire_map is not None:
160+
[circuit], _ = qml.map_wires(circuit, wire_map)
148161
state.reset_state()
149162
final_state = state.get_final_state(circuit)
150163
return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp(
@@ -153,7 +166,11 @@ def vjp(
153166

154167

155168
def simulate_and_vjp(
156-
circuit: QuantumTape, cotangents: Tuple[Number], state: LightningStateVector, batch_obs=False
169+
circuit: QuantumTape,
170+
cotangents: Tuple[Number],
171+
state: LightningStateVector,
172+
batch_obs=False,
173+
wire_map=None,
157174
):
158175
"""Simulate a single quantum script and compute its Vector-Jacobian Product (VJP).
159176
Args:
@@ -166,11 +183,14 @@ def simulate_and_vjp(
166183
batch_obs (bool): Determine whether we process observables in parallel when
167184
computing the jacobian. This value is only relevant when the lightning
168185
qubit is built with OpenMP.
186+
wire_map (Optional[dict]): a map from wire labels to simulation indices
187+
169188
Returns:
170189
Tuple[TensorLike]: The results of the simulation and the calculated VJP
171190
Note that this function can return measurements for non-commuting observables simultaneously.
172191
"""
173-
circuit = circuit.map_to_standard_wires()
192+
if wire_map is not None:
193+
[circuit], _ = qml.map_wires(circuit, wire_map)
174194
res = simulate(circuit, state)
175195
_vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents)
176196
return res, _vjp
@@ -449,6 +469,11 @@ def __init__( # pylint: disable=too-many-arguments
449469

450470
super().__init__(wires=wires, shots=shots)
451471

472+
if isinstance(wires, int):
473+
self._wire_map = None # should just use wires as is
474+
else:
475+
self._wire_map = {w: i for i, w in enumerate(self.wires)}
476+
452477
self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype)
453478

454479
# TODO: Investigate usefulness of creating numpy random generator
@@ -568,7 +593,8 @@ def execute(
568593
}
569594
results = []
570595
for circuit in circuits:
571-
circuit = circuit.map_to_standard_wires()
596+
if self._wire_map is not None:
597+
[circuit], _ = qml.map_wires(circuit, self._wire_map)
572598
results.append(simulate(circuit, self._statevector, mcmc=mcmc))
573599

574600
return tuple(results)
@@ -613,8 +639,10 @@ def compute_derivatives(
613639
Tuple: The jacobian for each trainable parameter
614640
"""
615641
batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs)
642+
616643
return tuple(
617-
jacobian(circuit, self._statevector, batch_obs=batch_obs) for circuit in circuits
644+
jacobian(circuit, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map)
645+
for circuit in circuits
618646
)
619647

620648
def execute_and_compute_derivatives(
@@ -633,7 +661,10 @@ def execute_and_compute_derivatives(
633661
"""
634662
batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs)
635663
results = tuple(
636-
simulate_and_jacobian(c, self._statevector, batch_obs=batch_obs) for c in circuits
664+
simulate_and_jacobian(
665+
c, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map
666+
)
667+
for c in circuits
637668
)
638669
return tuple(zip(*results))
639670

@@ -686,7 +717,7 @@ def compute_vjp(
686717
"""
687718
batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs)
688719
return tuple(
689-
vjp(circuit, cots, self._statevector, batch_obs=batch_obs)
720+
vjp(circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map)
690721
for circuit, cots in zip(circuits, cotangents)
691722
)
692723

@@ -708,7 +739,9 @@ def execute_and_compute_vjp(
708739
"""
709740
batch_obs = execution_config.device_options.get("batch_obs", self._batch_obs)
710741
results = tuple(
711-
simulate_and_vjp(circuit, cots, self._statevector, batch_obs=batch_obs)
742+
simulate_and_vjp(
743+
circuit, cots, self._statevector, batch_obs=batch_obs, wire_map=self._wire_map
744+
)
712745
for circuit, cots in zip(circuits, cotangents)
713746
)
714747
return tuple(zip(*results))

tests/new_api/test_device.py

+23
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,29 @@ def test_custom_wires(self, phi, theta, wires):
456456
assert np.allclose(result[0], np.cos(phi))
457457
assert np.allclose(result[1], np.cos(phi) * np.cos(theta))
458458

459+
@pytest.mark.parametrize(
460+
"wires, wire_order", [(3, (0, 1, 2)), (("a", "b", "c"), ("a", "b", "c"))]
461+
)
462+
def test_probs_different_wire_orders(self, wires, wire_order):
463+
"""Test that measuring probabilities works with custom wires."""
464+
465+
dev = LightningDevice(wires=wires)
466+
467+
op = qml.Hadamard(wire_order[1])
468+
469+
tape = QuantumScript([op], [qml.probs(wires=(wire_order[0], wire_order[1]))])
470+
471+
res = dev.execute(tape)
472+
assert qml.math.allclose(res, np.array([0.5, 0.5, 0.0, 0.0]))
473+
474+
tape2 = QuantumScript([op], [qml.probs(wires=(wire_order[1], wire_order[2]))])
475+
res2 = dev.execute(tape2)
476+
assert qml.math.allclose(res2, np.array([0.5, 0.0, 0.5, 0.0]))
477+
478+
tape3 = QuantumScript([op], [qml.probs(wires=(wire_order[1], wire_order[0]))])
479+
res3 = dev.execute(tape3)
480+
assert qml.math.allclose(res3, np.array([0.5, 0.0, 0.5, 0.0]))
481+
459482

460483
@pytest.mark.parametrize("batch_obs", [True, False])
461484
class TestDerivatives:

0 commit comments

Comments
 (0)