Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use wire order specified on instantiation #705

Merged
merged 10 commits into from
May 3, 2024
3 changes: 3 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@

### 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)

Expand Down
59 changes: 46 additions & 13 deletions pennylane_lightning/lightning_qubit/lightning_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@
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:
Expand All @@ -96,17 +96,21 @@
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)

Check warning on line 105 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L104-L105

Added lines #L104 - L105 were not covered by tests
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:
Expand All @@ -115,20 +119,26 @@
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)

Check warning on line 130 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L129-L130

Added lines #L129 - L130 were not covered by tests
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:
Expand All @@ -141,10 +151,13 @@
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)

Check warning on line 160 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L159-L160

Added lines #L159 - L160 were not covered by tests
state.reset_state()
final_state = state.get_final_state(circuit)
return LightningAdjointJacobian(final_state, batch_obs=batch_obs).calculate_vjp(
Expand All @@ -153,7 +166,11 @@


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:
Expand All @@ -166,11 +183,14 @@
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)

Check warning on line 193 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L192-L193

Added lines #L192 - L193 were not covered by tests
res = simulate(circuit, state)
_vjp = LightningAdjointJacobian(state, batch_obs=batch_obs).calculate_vjp(circuit, cotangents)
return res, _vjp
Expand Down Expand Up @@ -449,6 +469,11 @@

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

if isinstance(wires, int):
self._wire_map = None # should just use wires as is

Check warning on line 473 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L472-L473

Added lines #L472 - L473 were not covered by tests
else:
self._wire_map = {w: i for i, w in enumerate(self.wires)}

Check warning on line 475 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L475

Added line #L475 was not covered by tests

self._statevector = LightningStateVector(num_wires=len(self.wires), dtype=c_dtype)

# TODO: Investigate usefulness of creating numpy random generator
Expand Down Expand Up @@ -568,7 +593,8 @@
}
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)

Check warning on line 597 in pennylane_lightning/lightning_qubit/lightning_qubit.py

View check run for this annotation

Codecov / codecov/patch

pennylane_lightning/lightning_qubit/lightning_qubit.py#L596-L597

Added lines #L596 - L597 were not covered by tests
results.append(simulate(circuit, self._statevector, mcmc=mcmc))

return tuple(results)
Expand Down Expand Up @@ -613,8 +639,10 @@
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(
Expand All @@ -633,7 +661,10 @@
"""
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))

Expand Down Expand Up @@ -686,7 +717,7 @@
"""
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)
)

Expand All @@ -708,7 +739,9 @@
"""
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))
Loading