Skip to content

Commit 4aa532e

Browse files
vincentmrAmintorDuskoalbi3rogithub-actions[bot]mudit2812
authored
Lightning qubit2 supports shots (#630)
* Merge Master * update version * update version * update lightning_qubit_2 * add LightningQubit2 to init * add LightningStateVector class * add LightningMeasurements class * add new QuantumScriptSerializer class * allow lightning.qubit2 to be tested within our suite * add tests and CI workflow for lightning_qubit_2 * update CI * update CI * add wire mapping, black * add tests for custom wires * add tests for custom wires * add review suggestions * format * update * adding tests from add-simulate branch * remove python class to reverse order of PRs * merge conflicts * update simulate to get a LightningStateVector * add reset state * update simulate * update docs * add Result import * create state vector on initialization * remove import of modifier from lightning * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py * Update pennylane_lightning/lightning_qubit/_measurements.py Co-authored-by: Christina Lee <[email protected]> * Update pennylane_lightning/lightning_qubit/_measurements.py Co-authored-by: Christina Lee <[email protected]> * minor test updates * fix reset state * Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee <[email protected]> * Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee <[email protected]> * Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee <[email protected]> * remove LightningQubit2 references * remove unnecessary modules * merging Serializer classes * update serialize tests * update measurements with new serialize class * remove outdated test * register with setup.py, state vector fixes * remove obsolete tests * remove unused dtype input from simulate * update measurements * update state_vector * update lightning_qubit2 * format * pylint * Update pennylane_lightning/lightning_qubit/_state_vector.py Co-authored-by: Christina Lee <[email protected]> * remove old comment * some review suggestions * Auto update version * remove print * update state vector class * add state vector class tests * adding measurement tests * update state vector and tests * move and rename test files, and format * Auto update version * skip measurements class for other devices and in the absence of binaries * format * add LightningQubit2 to init and format * update measurements class * expand measurement class testing * garbage collection * typo * update coverage and StateVector class * expand measurements class coverage * Auto update version * add coverage for n-controlled operations * add map to standard wires to get_final_state for safety * update jax config import * Auto update version * trigger CI * update state vector class and tests for improved coverage * update measurement class tests * update dev version * add cpp binary available variable * remove device definition * update dev version * Auto update version * reduce dependency on DefaultQubit for tests * update LightningQubit2 * clean test_measurements_class.py * isort+black * review suggestion * fix docs * Add qml.var support. * Add probs support. * increase tolerance * Auto update version * isort * Add double-obs tests. * Pin pytest version (#624) * update dev version * update changelog * pin pytest version in requirement files * add a requirements file for tests against Pennylane master * update wheels' workflows * Version Bump (#626) * post release version bump * trigger CI --------- Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: AmintorDusko <[email protected]> * increase tolerance * Introduce isort. (#623) * Introduce isort. * Auto update version * Update changelog * Auto update version * Update changelog. * trigger ci --------- Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> * Auto update version * isort * Add qml.var support. * Add probs support. * Add measurement tests with wires. * review suggestions * remove unused imports * Introduce _new_API and fix/skip few tests. * Fix few more tests. * Skip shots, adjoint, vjp with new API. * remove diagonalization gate application from state vector * pytest.skip tests * Auto update version * Fix format * Fix no-bin interface. * WIP * Initial shots support + fix test_measurement tests. * update * adding tests from add-simulate branch * merge conflicts * create state vector on initialization * remove import of modifier from lightning * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py * minor test updates * register with setup.py, state vector fixes * add LightningQubit2 to init and format * add cpp binary available variable * reduce dependency on DefaultQubit for tests * update LightningQubit2 * Fixing rebase artifacts * Add fewLQ2 tests. * remove adjoint diff support from supports derivatives * Remove print from test_apply * Add expval/var tests. * Remove duplicate class data. * Include LQ2 in linux ests. * Add _group_measurements support. * --cov-append * Add mcmc capability + tests. * Auto update version * update dev version * add LightningAdjointJacobian class * add unit tests for the LightningAdjointJacobian class * format * add changelog for PR #613 * [skip ci] Added skeleton file for LQ2 unit tests * update changelog * update adjoint Jacobian * Auto update version * codefactor * Add shots tests and fix bugs in LQ, LQ2. * Lightning qubit2 upgrade api (#628) * update * adding tests from add-simulate branch * merge conflicts * create state vector on initialization * remove import of modifier from lightning * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py * minor test updates * register with setup.py, state vector fixes * add LightningQubit2 to init and format * add cpp binary available variable * Auto update version * reduce dependency on DefaultQubit for tests * update LightningQubit2 * Introduce _new_API and fix/skip few tests. * Fix few more tests. * Skip shots, adjoint, vjp with new API. * Fix no-bin interface. * Remove duplicate class data. * Include LQ2 in linux ests. * --cov-append --------- Co-authored-by: albi3ro <[email protected]> Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> * fix processing_fn_expval * make a proper new_tape * Added init tests; Added skeleton tests for helpers * Fix more bug with shots. * trigger CI * Change pennylane branch for CI. * Update .github/CHANGELOG.md Co-authored-by: Vincent Michaud-Rioux <[email protected]> * Update pennylane_lightning/lightning_qubit/_adjoint_jacobian.py Co-authored-by: Vincent Michaud-Rioux <[email protected]> * Update pennylane_lightning/lightning_qubit/_adjoint_jacobian.py Co-authored-by: Vincent Michaud-Rioux <[email protected]> * Add probs support. * Add double-obs tests. * Add qml.var support. * Add probs support. * Add measurement tests with wires. * pytest.skip tests * Fix format * update * adding tests from add-simulate branch * merge conflicts * create state vector on initialization * remove import of modifier from lightning * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py * minor test updates * register with setup.py, state vector fixes * add LightningQubit2 to init and format * add cpp binary available variable * reduce dependency on DefaultQubit for tests * update LightningQubit2 * Fixing rebase artifacts * remove adjoint diff support from supports derivatives * [skip ci] Added skeleton file for LQ2 unit tests * Lightning qubit2 upgrade api (#628) * update * adding tests from add-simulate branch * merge conflicts * create state vector on initialization * remove import of modifier from lightning * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py * minor test updates * register with setup.py, state vector fixes * add LightningQubit2 to init and format * add cpp binary available variable * Auto update version * reduce dependency on DefaultQubit for tests * update LightningQubit2 * Introduce _new_API and fix/skip few tests. * Fix few more tests. * Skip shots, adjoint, vjp with new API. * Fix no-bin interface. * Remove duplicate class data. * Include LQ2 in linux ests. * --cov-append --------- Co-authored-by: albi3ro <[email protected]> Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> * Added init tests; Added skeleton tests for helpers * Resolving rebase artifacts * Refactor shots test. * Added tests; integrated jacobian * Update pennylane_lightning/lightning_qubit/lightning_qubit2.py Co-authored-by: Amintor Dusko <[email protected]> * Auto update version * Small update to simulate_and_jacobian * Auto update version * Rerun isort. * Uncomment integration tests. * Reformat * Delete symlink * Fix pylint. * Run linux tests in parallel (when possible). * Run double obs tests with shots. * Revert linux tests * Fix bg in diag_gates. * Call isort/black with python -m * Add docstrings, rm C_DTYPE. * Auto update version * trigger ci * Update tests/test_expval.py Co-authored-by: Amintor Dusko <[email protected]> * Init mcmc params to None in measurements. * Reformat with python3.11 * Reformat black --------- Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: Christina Lee <[email protected]> Co-authored-by: Amintor Dusko <[email protected]> Co-authored-by: Dev version update bot <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: AmintorDusko <[email protected]> Co-authored-by: Mudit Pandey <[email protected]>
1 parent 5b4ef77 commit 4aa532e

16 files changed

+580
-275
lines changed

.github/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
### New features since last release
44

5+
* Add finite shots support in `lightning.qubit2`.
6+
[(#630)](https://github.com/PennyLaneAI/pennylane-lightning/pull/630)
7+
58
* Add `collapse` and `normalize` methods to the `StateVectorLQubit` classes, enabling "branching" of the wavefunction. Add methods to create and seed an RNG in the `Measurements` modules.
69
[(#645)](https://github.com/PennyLaneAI/pennylane-lightning/pull/645)
710

.github/workflows/format.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Set up Python
1818
uses: actions/setup-python@v4
1919
with:
20-
python-version: '3.9'
20+
python-version: '3.11'
2121

2222
- name: Install dependencies
2323
run:
@@ -27,10 +27,10 @@ jobs:
2727
uses: actions/checkout@v3
2828

2929
- name: Run isort
30-
run: isort --profile black ./pennylane_lightning/ ./mpitests ./tests --check --diff
30+
run: python -m isort --profile black ./pennylane_lightning/ ./mpitests ./tests --check --diff
3131

3232
- name: Run Black
33-
run: black -l 100 pennylane_lightning/ tests/ --check --verbose
33+
run: python -m black -l 100 pennylane_lightning/ tests/ --check --verbose
3434

3535
format-cpp:
3636
name: Format (C++)

pennylane_lightning/core/_version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@
1616
Version number (major.minor.patch[-label])
1717
"""
1818

19-
__version__ = "0.36.0-dev11"
19+
__version__ = "0.36.0-dev12"

pennylane_lightning/lightning_qubit/_measurements.py

+198-15
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,24 @@
2424
except ImportError:
2525
pass
2626

27-
from typing import Callable, List
27+
from typing import Callable, List, Union
2828

2929
import numpy as np
3030
import pennylane as qml
31+
from pennylane.devices.qubit.sampling import _group_measurements
3132
from pennylane.measurements import (
33+
ClassicalShadowMP,
34+
CountsMP,
3235
ExpectationMP,
3336
MeasurementProcess,
3437
ProbabilityMP,
38+
SampleMeasurement,
39+
ShadowExpvalMP,
40+
Shots,
3541
StateMeasurement,
3642
VarianceMP,
3743
)
44+
from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum
3845
from pennylane.tape import QuantumScript
3946
from pennylane.typing import Result, TensorLike
4047
from pennylane.wires import Wires
@@ -51,24 +58,41 @@ class LightningMeasurements:
5158
5259
Args:
5360
qubit_state(LightningStateVector): Lightning state-vector class containing the state vector to be measured.
61+
mcmc (bool): Determine whether to use the approximate Markov Chain Monte Carlo
62+
sampling method when generating samples.
63+
kernel_name (str): name of MCMC transition kernel. The current version supports
64+
two kernels: ``"Local"`` and ``"NonZeroRandom"``.
65+
The local kernel conducts a bit-flip local transition between states.
66+
The local kernel generates a random qubit site and then generates a random
67+
number to determine the new bit at that qubit site. The ``"NonZeroRandom"`` kernel
68+
randomly transits between states that have nonzero probability.
69+
num_burnin (int): number of MCMC steps that will be dropped. Increasing this value will
70+
result in a closer approximation but increased runtime.
5471
"""
5572

56-
def __init__(self, qubit_state: LightningStateVector) -> None:
73+
def __init__(
74+
self,
75+
qubit_state: LightningStateVector,
76+
mcmc: bool = None,
77+
kernel_name: str = None,
78+
num_burnin: int = None,
79+
) -> None:
5780
self._qubit_state = qubit_state
58-
self._state = qubit_state.state_vector
5981
self._dtype = qubit_state.dtype
60-
self._measurement_lightning = self._measurement_dtype()(self.state)
82+
self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector)
83+
self._mcmc = mcmc
84+
self._kernel_name = kernel_name
85+
self._num_burnin = num_burnin
86+
if self._mcmc and not self._kernel_name:
87+
self._kernel_name = "Local"
88+
if self._mcmc and not self._num_burnin:
89+
self._num_burnin = 100
6190

6291
@property
6392
def qubit_state(self):
6493
"""Returns a handle to the LightningStateVector class."""
6594
return self._qubit_state
6695

67-
@property
68-
def state(self):
69-
"""Returns a handle to the Lightning internal data class."""
70-
return self._state
71-
7296
@property
7397
def dtype(self):
7498
"""Returns the simulation data type."""
@@ -92,14 +116,11 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten
92116
TensorLike: the result of the measurement
93117
"""
94118
diagonalizing_gates = measurementprocess.diagonalizing_gates()
95-
self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates())
119+
self._qubit_state.apply_operations(diagonalizing_gates)
96120
state_array = self._qubit_state.state
97121
wires = Wires(range(self._qubit_state.num_wires))
98-
99122
result = measurementprocess.process_state(state_array, wires)
100-
101123
self._qubit_state.apply_operations([qml.adjoint(g) for g in reversed(diagonalizing_gates)])
102-
103124
return result
104125

105126
# pylint: disable=protected-access
@@ -251,7 +272,169 @@ def measure_final_state(self, circuit: QuantumScript) -> Result:
251272
Tuple[TensorLike]: The measurement results
252273
"""
253274

275+
if not circuit.shots:
276+
# analytic case
277+
if len(circuit.measurements) == 1:
278+
return self.measurement(circuit.measurements[0])
279+
280+
return tuple(self.measurement(mp) for mp in circuit.measurements)
281+
282+
# finite-shot case
283+
results = self.measure_with_samples(
284+
circuit.measurements,
285+
shots=circuit.shots,
286+
)
287+
254288
if len(circuit.measurements) == 1:
255-
return self.measurement(circuit.measurements[0])
289+
if circuit.shots.has_partitioned_shots:
290+
return tuple(res[0] for res in results)
291+
292+
return results[0]
293+
294+
return results
295+
296+
# pylint:disable = too-many-arguments
297+
def measure_with_samples(
298+
self,
299+
mps: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]],
300+
shots: Shots,
301+
) -> List[TensorLike]:
302+
"""
303+
Returns the samples of the measurement process performed on the given state.
304+
This function assumes that the user-defined wire labels in the measurement process
305+
have already been mapped to integer wires used in the device.
306+
307+
Args:
308+
mps (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]):
309+
The sample measurements to perform
310+
shots (Shots): The number of samples to take
256311
257-
return tuple(self.measurement(mp) for mp in circuit.measurements)
312+
Returns:
313+
List[TensorLike[Any]]: Sample measurement results
314+
"""
315+
316+
groups, indices = _group_measurements(mps)
317+
318+
all_res = []
319+
for group in groups:
320+
if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance(
321+
group[0].obs, SparseHamiltonian
322+
):
323+
raise TypeError("ExpectationMP(SparseHamiltonian) cannot be computed with samples.")
324+
if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance(
325+
group[0].obs, Hamiltonian
326+
):
327+
raise TypeError("ExpectationMP(Hamiltonian) cannot be computed with samples.")
328+
if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance(group[0].obs, Sum):
329+
raise TypeError("ExpectationMP(Sum) cannot be computed with samples.")
330+
if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)):
331+
raise TypeError(
332+
"ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples."
333+
)
334+
all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots))
335+
336+
# reorder results
337+
flat_indices = []
338+
for row in indices:
339+
flat_indices += row
340+
sorted_res = tuple(
341+
res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]])
342+
)
343+
344+
# put the shot vector axis before the measurement axis
345+
if shots.has_partitioned_shots:
346+
sorted_res = tuple(zip(*sorted_res))
347+
348+
return sorted_res
349+
350+
def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False):
351+
if len(mps) == 1:
352+
diagonalizing_gates = mps[0].diagonalizing_gates()
353+
elif all(mp.obs for mp in mps):
354+
diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0]
355+
else:
356+
diagonalizing_gates = []
357+
358+
if adjoint:
359+
diagonalizing_gates = [
360+
qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates)
361+
]
362+
363+
self._qubit_state.apply_operations(diagonalizing_gates)
364+
365+
def _measure_with_samples_diagonalizing_gates(
366+
self,
367+
mps: List[SampleMeasurement],
368+
shots: Shots,
369+
) -> TensorLike:
370+
"""
371+
Returns the samples of the measurement process performed on the given state,
372+
by rotating the state into the measurement basis using the diagonalizing gates
373+
given by the measurement process.
374+
375+
Args:
376+
mps (~.measurements.SampleMeasurement): The sample measurements to perform
377+
shots (~.measurements.Shots): The number of samples to take
378+
379+
Returns:
380+
TensorLike[Any]: Sample measurement results
381+
"""
382+
# apply diagonalizing gates
383+
self._apply_diagonalizing_gates(mps)
384+
385+
total_indices = self._qubit_state.num_wires
386+
wires = qml.wires.Wires(range(total_indices))
387+
388+
def _process_single_shot(samples):
389+
processed = []
390+
for mp in mps:
391+
res = mp.process_samples(samples, wires)
392+
if not isinstance(mp, CountsMP):
393+
res = qml.math.squeeze(res)
394+
395+
processed.append(res)
396+
397+
return tuple(processed)
398+
399+
# if there is a shot vector, build a list containing results for each shot entry
400+
if shots.has_partitioned_shots:
401+
processed_samples = []
402+
for s in shots:
403+
# currently we call sample_state for each shot entry, but it may be
404+
# better to call sample_state just once with total_shots, then use
405+
# the shot_range keyword argument
406+
try:
407+
if self._mcmc:
408+
samples = self._measurement_lightning.generate_mcmc_samples(
409+
len(wires), self._kernel_name, self._num_burnin, s
410+
).astype(int, copy=False)
411+
else:
412+
samples = self._measurement_lightning.generate_samples(
413+
len(wires), s
414+
).astype(int, copy=False)
415+
except ValueError as e:
416+
if str(e) != "probabilities contain NaN":
417+
raise e
418+
samples = qml.math.full((s, len(wires)), 0)
419+
420+
processed_samples.append(_process_single_shot(samples))
421+
self._apply_diagonalizing_gates(mps, adjoint=True)
422+
return tuple(zip(*processed_samples))
423+
424+
try:
425+
if self._mcmc:
426+
samples = self._measurement_lightning.generate_mcmc_samples(
427+
len(wires), self._kernel_name, self._num_burnin, shots.total_shots
428+
).astype(int, copy=False)
429+
else:
430+
samples = self._measurement_lightning.generate_samples(
431+
len(wires), shots.total_shots
432+
).astype(int, copy=False)
433+
except ValueError as e:
434+
if str(e) != "probabilities contain NaN":
435+
raise e
436+
samples = qml.math.full((shots.total_shots, len(wires)), 0)
437+
438+
self._apply_diagonalizing_gates(mps, adjoint=True)
439+
440+
return _process_single_shot(samples)

pennylane_lightning/lightning_qubit/_state_vector.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import numpy as np
3030
import pennylane as qml
3131
from pennylane import BasisState, DeviceError, StatePrep
32+
from pennylane.ops.op_math import Adjoint
3233
from pennylane.tape import QuantumScript
3334
from pennylane.wires import Wires
3435

@@ -259,16 +260,20 @@ def _apply_lightning(self, operations):
259260
# Skip over identity operations instead of performing
260261
# matrix multiplication with it.
261262
for operation in operations:
262-
name = operation.name
263-
if name == "Identity":
263+
if isinstance(operation, qml.Identity):
264264
continue
265+
if isinstance(operation, Adjoint):
266+
name = operation.base.name
267+
invert_param = True
268+
else:
269+
name = operation.name
270+
invert_param = False
265271
method = getattr(state, name, None)
266272
wires = list(operation.wires)
267273

268274
if method is not None: # apply specialized gate
269-
inv = False
270275
param = operation.parameters
271-
method(wires, inv, param)
276+
method(wires, invert_param, param)
272277
elif isinstance(operation, qml.ops.Controlled): # apply n-controlled gate
273278
self._apply_lightning_controlled(operation)
274279
else: # apply gate as a matrix

0 commit comments

Comments
 (0)