diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 0daad04b2b..0f64e15764 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,9 @@ ### New features since last release +* Add finite shots support in `lightning.qubit2`. + [(#630)](https://github.com/PennyLaneAI/pennylane-lightning/pull/630) + * 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. [(#645)](https://github.com/PennyLaneAI/pennylane-lightning/pull/645) diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 1a69e3023e..91e4143c2b 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.11' - name: Install dependencies run: @@ -27,10 +27,10 @@ jobs: uses: actions/checkout@v3 - name: Run isort - run: isort --profile black ./pennylane_lightning/ ./mpitests ./tests --check --diff + run: python -m isort --profile black ./pennylane_lightning/ ./mpitests ./tests --check --diff - name: Run Black - run: black -l 100 pennylane_lightning/ tests/ --check --verbose + run: python -m black -l 100 pennylane_lightning/ tests/ --check --verbose format-cpp: name: Format (C++) diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 5bc57662cf..fb85e127e0 100644 --- a/pennylane_lightning/core/_version.py +++ b/pennylane_lightning/core/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.36.0-dev11" +__version__ = "0.36.0-dev12" diff --git a/pennylane_lightning/lightning_qubit/_measurements.py b/pennylane_lightning/lightning_qubit/_measurements.py index 4a81b1c8e9..aae3d8a20c 100644 --- a/pennylane_lightning/lightning_qubit/_measurements.py +++ b/pennylane_lightning/lightning_qubit/_measurements.py @@ -24,17 +24,24 @@ except ImportError: pass -from typing import Callable, List +from typing import Callable, List, Union import numpy as np import pennylane as qml +from pennylane.devices.qubit.sampling import _group_measurements from pennylane.measurements import ( + ClassicalShadowMP, + CountsMP, ExpectationMP, MeasurementProcess, ProbabilityMP, + SampleMeasurement, + ShadowExpvalMP, + Shots, StateMeasurement, VarianceMP, ) +from pennylane.ops import Hamiltonian, SparseHamiltonian, Sum from pennylane.tape import QuantumScript from pennylane.typing import Result, TensorLike from pennylane.wires import Wires @@ -51,24 +58,41 @@ class LightningMeasurements: Args: qubit_state(LightningStateVector): Lightning state-vector class containing the state vector to be measured. + mcmc (bool): Determine whether to use the approximate Markov Chain Monte Carlo + sampling method when generating samples. + kernel_name (str): name of MCMC transition kernel. The current version supports + two kernels: ``"Local"`` and ``"NonZeroRandom"``. + The local kernel conducts a bit-flip local transition between states. + The local kernel generates a random qubit site and then generates a random + number to determine the new bit at that qubit site. The ``"NonZeroRandom"`` kernel + randomly transits between states that have nonzero probability. + num_burnin (int): number of MCMC steps that will be dropped. Increasing this value will + result in a closer approximation but increased runtime. """ - def __init__(self, qubit_state: LightningStateVector) -> None: + def __init__( + self, + qubit_state: LightningStateVector, + mcmc: bool = None, + kernel_name: str = None, + num_burnin: int = None, + ) -> None: self._qubit_state = qubit_state - self._state = qubit_state.state_vector self._dtype = qubit_state.dtype - self._measurement_lightning = self._measurement_dtype()(self.state) + self._measurement_lightning = self._measurement_dtype()(qubit_state.state_vector) + self._mcmc = mcmc + self._kernel_name = kernel_name + self._num_burnin = num_burnin + if self._mcmc and not self._kernel_name: + self._kernel_name = "Local" + if self._mcmc and not self._num_burnin: + self._num_burnin = 100 @property def qubit_state(self): """Returns a handle to the LightningStateVector class.""" return self._qubit_state - @property - def state(self): - """Returns a handle to the Lightning internal data class.""" - return self._state - @property def dtype(self): """Returns the simulation data type.""" @@ -92,14 +116,11 @@ def state_diagonalizing_gates(self, measurementprocess: StateMeasurement) -> Ten TensorLike: the result of the measurement """ diagonalizing_gates = measurementprocess.diagonalizing_gates() - self._qubit_state.apply_operations(measurementprocess.diagonalizing_gates()) + self._qubit_state.apply_operations(diagonalizing_gates) state_array = self._qubit_state.state wires = Wires(range(self._qubit_state.num_wires)) - result = measurementprocess.process_state(state_array, wires) - self._qubit_state.apply_operations([qml.adjoint(g) for g in reversed(diagonalizing_gates)]) - return result # pylint: disable=protected-access @@ -251,7 +272,169 @@ def measure_final_state(self, circuit: QuantumScript) -> Result: Tuple[TensorLike]: The measurement results """ + if not circuit.shots: + # analytic case + if len(circuit.measurements) == 1: + return self.measurement(circuit.measurements[0]) + + return tuple(self.measurement(mp) for mp in circuit.measurements) + + # finite-shot case + results = self.measure_with_samples( + circuit.measurements, + shots=circuit.shots, + ) + if len(circuit.measurements) == 1: - return self.measurement(circuit.measurements[0]) + if circuit.shots.has_partitioned_shots: + return tuple(res[0] for res in results) + + return results[0] + + return results + + # pylint:disable = too-many-arguments + def measure_with_samples( + self, + mps: List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]], + shots: Shots, + ) -> List[TensorLike]: + """ + Returns the samples of the measurement process performed on the given state. + This function assumes that the user-defined wire labels in the measurement process + have already been mapped to integer wires used in the device. + + Args: + mps (List[Union[SampleMeasurement, ClassicalShadowMP, ShadowExpvalMP]]): + The sample measurements to perform + shots (Shots): The number of samples to take - return tuple(self.measurement(mp) for mp in circuit.measurements) + Returns: + List[TensorLike[Any]]: Sample measurement results + """ + + groups, indices = _group_measurements(mps) + + all_res = [] + for group in groups: + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( + group[0].obs, SparseHamiltonian + ): + raise TypeError("ExpectationMP(SparseHamiltonian) cannot be computed with samples.") + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance( + group[0].obs, Hamiltonian + ): + raise TypeError("ExpectationMP(Hamiltonian) cannot be computed with samples.") + if isinstance(group[0], (ExpectationMP, VarianceMP)) and isinstance(group[0].obs, Sum): + raise TypeError("ExpectationMP(Sum) cannot be computed with samples.") + if isinstance(group[0], (ClassicalShadowMP, ShadowExpvalMP)): + raise TypeError( + "ExpectationMP(ClassicalShadowMP, ShadowExpvalMP) cannot be computed with samples." + ) + all_res.extend(self._measure_with_samples_diagonalizing_gates(group, shots)) + + # reorder results + flat_indices = [] + for row in indices: + flat_indices += row + sorted_res = tuple( + res for _, res in sorted(list(enumerate(all_res)), key=lambda r: flat_indices[r[0]]) + ) + + # put the shot vector axis before the measurement axis + if shots.has_partitioned_shots: + sorted_res = tuple(zip(*sorted_res)) + + return sorted_res + + def _apply_diagonalizing_gates(self, mps: List[SampleMeasurement], adjoint: bool = False): + if len(mps) == 1: + diagonalizing_gates = mps[0].diagonalizing_gates() + elif all(mp.obs for mp in mps): + diagonalizing_gates = qml.pauli.diagonalize_qwc_pauli_words([mp.obs for mp in mps])[0] + else: + diagonalizing_gates = [] + + if adjoint: + diagonalizing_gates = [ + qml.adjoint(g, lazy=False) for g in reversed(diagonalizing_gates) + ] + + self._qubit_state.apply_operations(diagonalizing_gates) + + def _measure_with_samples_diagonalizing_gates( + self, + mps: List[SampleMeasurement], + shots: Shots, + ) -> TensorLike: + """ + Returns the samples of the measurement process performed on the given state, + by rotating the state into the measurement basis using the diagonalizing gates + given by the measurement process. + + Args: + mps (~.measurements.SampleMeasurement): The sample measurements to perform + shots (~.measurements.Shots): The number of samples to take + + Returns: + TensorLike[Any]: Sample measurement results + """ + # apply diagonalizing gates + self._apply_diagonalizing_gates(mps) + + total_indices = self._qubit_state.num_wires + wires = qml.wires.Wires(range(total_indices)) + + def _process_single_shot(samples): + processed = [] + for mp in mps: + res = mp.process_samples(samples, wires) + if not isinstance(mp, CountsMP): + res = qml.math.squeeze(res) + + processed.append(res) + + return tuple(processed) + + # if there is a shot vector, build a list containing results for each shot entry + if shots.has_partitioned_shots: + processed_samples = [] + for s in shots: + # currently we call sample_state for each shot entry, but it may be + # better to call sample_state just once with total_shots, then use + # the shot_range keyword argument + try: + if self._mcmc: + samples = self._measurement_lightning.generate_mcmc_samples( + len(wires), self._kernel_name, self._num_burnin, s + ).astype(int, copy=False) + else: + samples = self._measurement_lightning.generate_samples( + len(wires), s + ).astype(int, copy=False) + except ValueError as e: + if str(e) != "probabilities contain NaN": + raise e + samples = qml.math.full((s, len(wires)), 0) + + processed_samples.append(_process_single_shot(samples)) + self._apply_diagonalizing_gates(mps, adjoint=True) + return tuple(zip(*processed_samples)) + + try: + if self._mcmc: + samples = self._measurement_lightning.generate_mcmc_samples( + len(wires), self._kernel_name, self._num_burnin, shots.total_shots + ).astype(int, copy=False) + else: + samples = self._measurement_lightning.generate_samples( + len(wires), shots.total_shots + ).astype(int, copy=False) + except ValueError as e: + if str(e) != "probabilities contain NaN": + raise e + samples = qml.math.full((shots.total_shots, len(wires)), 0) + + self._apply_diagonalizing_gates(mps, adjoint=True) + + return _process_single_shot(samples) diff --git a/pennylane_lightning/lightning_qubit/_state_vector.py b/pennylane_lightning/lightning_qubit/_state_vector.py index 63cb791b8f..f24293b1ac 100644 --- a/pennylane_lightning/lightning_qubit/_state_vector.py +++ b/pennylane_lightning/lightning_qubit/_state_vector.py @@ -29,6 +29,7 @@ import numpy as np import pennylane as qml from pennylane import BasisState, DeviceError, StatePrep +from pennylane.ops.op_math import Adjoint from pennylane.tape import QuantumScript from pennylane.wires import Wires @@ -259,16 +260,20 @@ def _apply_lightning(self, operations): # Skip over identity operations instead of performing # matrix multiplication with it. for operation in operations: - name = operation.name - if name == "Identity": + if isinstance(operation, qml.Identity): continue + if isinstance(operation, Adjoint): + name = operation.base.name + invert_param = True + else: + name = operation.name + invert_param = False method = getattr(state, name, None) wires = list(operation.wires) if method is not None: # apply specialized gate - inv = False param = operation.parameters - method(wires, inv, param) + method(wires, invert_param, param) elif isinstance(operation, qml.ops.Controlled): # apply n-controlled gate self._apply_lightning_controlled(operation) else: # apply gate as a matrix diff --git a/pennylane_lightning/lightning_qubit/lightning_qubit2.py b/pennylane_lightning/lightning_qubit/lightning_qubit2.py index 7bd9e3fa02..4a0d8b6200 100644 --- a/pennylane_lightning/lightning_qubit/lightning_qubit2.py +++ b/pennylane_lightning/lightning_qubit/lightning_qubit2.py @@ -15,6 +15,7 @@ This module contains the LightningQubit2 class that inherits from the new device interface. """ from dataclasses import replace +from pathlib import Path from typing import Callable, Optional, Sequence, Union import numpy as np @@ -40,7 +41,7 @@ try: # pylint: disable=import-error, unused-import - import pennylane_lightning.lightning_qubit_ops + from pennylane_lightning.lightning_qubit_ops import backend_info LQ_CPP_BINARY_AVAILABLE = True except ImportError: @@ -52,22 +53,26 @@ PostprocessingFn = Callable[[ResultBatch], Result_or_ResultBatch] -def simulate(circuit: QuantumScript, state: LightningStateVector) -> Result: +def simulate(circuit: QuantumScript, state: LightningStateVector, mcmc: dict = None) -> Result: """Simulate a single quantum script. Args: circuit (QuantumTape): The single circuit to simulate state (LightningStateVector): handle to Lightning state vector + mcmc (dict): Dictionary containing the Markov Chain Monte Carlo + parameters: mcmc, kernel_name, num_burnin. Descriptions of + these fields are found in :class:`~.LightningQubit2`. Returns: Tuple[TensorLike]: The results of the simulation Note that this function can return measurements for non-commuting observables simultaneously. """ - circuit = circuit.map_to_standard_wires() + if mcmc is None: + mcmc = {} state.reset_state() final_state = state.get_final_state(circuit) - return LightningMeasurements(final_state).measure_final_state(circuit) + return LightningMeasurements(final_state, **mcmc).measure_final_state(circuit) def jacobian(circuit: QuantumTape, state: LightningStateVector, batch_obs=False): @@ -297,13 +302,13 @@ class LightningQubit2(Device): will pull a seed from the OS entropy. mcmc (bool): Determine whether to use the approximate Markov Chain Monte Carlo sampling method when generating samples. - kernel_name (str): name of transition kernel. The current version supports + kernel_name (str): name of transition MCMC kernel. The current version supports two kernels: ``"Local"`` and ``"NonZeroRandom"``. The local kernel conducts a bit-flip local transition between states. The local kernel generates a random qubit site and then generates a random number to determine the new bit at that qubit site. The ``"NonZeroRandom"`` kernel randomly transits between states that have nonzero probability. - num_burnin (int): number of steps that will be dropped. Increasing this value will + num_burnin (int): number of MCMC steps that will be dropped. Increasing this value will result in a closer approximation but increased runtime. batch_obs (bool): Determine whether we process observables in parallel when computing the jacobian. This value is only relevant when the lightning @@ -313,6 +318,8 @@ class LightningQubit2(Device): _device_options = ("rng", "c_dtype", "batch_obs", "mcmc", "kernel_name", "num_burnin") _CPP_BINARY_AVAILABLE = LQ_CPP_BINARY_AVAILABLE _new_API = True + _backend_info = backend_info if LQ_CPP_BINARY_AVAILABLE else None + _config = Path(__file__).parent / "lightning_qubit.toml" # TODO: Move supported ops/obs to TOML file operations = _operations @@ -360,7 +367,8 @@ def __init__( # pylint: disable=too-many-arguments f"The {kernel_name} is not supported and currently " "only 'Local' and 'NonZeroRandom' kernels are supported." ) - if num_burnin >= shots: + shots = shots if isinstance(shots, Sequence) else [shots] + if any(num_burnin >= s for s in shots): raise ValueError("Shots should be greater than num_burnin.") self._kernel_name = kernel_name self._num_burnin = num_burnin @@ -417,8 +425,6 @@ def preprocess(self, execution_config: ExecutionConfig = DefaultExecutionConfig) program = TransformProgram() program.add_transform(validate_measurements, name=self.name) - # TODO: Remove no_sampling from preprocess after shots support is added - program.add_transform(no_sampling) program.add_transform(validate_observables, accepted_observables, name=self.name) program.add_transform(validate_device_wires, self.wires, name=self.name) program.add_transform(qml.defer_measurements, device=self) @@ -444,10 +450,15 @@ def execute( Returns: TensorLike, tuple[TensorLike], tuple[tuple[TensorLike]]: A numeric result of the computation. """ + mcmc = { + "mcmc": self._mcmc, + "kernel_name": self._kernel_name, + "num_burnin": self._num_burnin, + } results = [] for circuit in circuits: circuit = circuit.map_to_standard_wires() - results.append(simulate(circuit, self._statevector)) + results.append(simulate(circuit, self._statevector, mcmc=mcmc)) return tuple(results) diff --git a/requirements-dev.txt b/requirements-dev.txt index a4ceae1c8c..e9467a9e54 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -12,7 +12,8 @@ pre-commit>=2.19.0 black==23.7.0 clang-tidy~=16.0 clang-format~=16.0 -isort~=5.13.2 +isort==5.13.2 +click==8.0.4 cmake custatevec-cu12 pylint diff --git a/tests/conftest.py b/tests/conftest.py index e4a79e22ab..40b4c56eb3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -151,8 +151,8 @@ def get_device(): params=[np.complex64, np.complex128], ) def qubit_device(request): - def _device(wires): - return qml.device(device_name, wires=wires, c_dtype=request.param) + def _device(wires, shots=None): + return qml.device(device_name, wires=wires, shots=shots, c_dtype=request.param) return _device diff --git a/tests/lightning_qubit/test_measurements_class.py b/tests/lightning_qubit/test_measurements_class.py index 428825cced..6717dec859 100644 --- a/tests/lightning_qubit/test_measurements_class.py +++ b/tests/lightning_qubit/test_measurements_class.py @@ -80,7 +80,6 @@ def test_initialization(lightning_sv): m = LightningMeasurements(statevector) assert m.qubit_state is statevector - assert m.state is statevector.state_vector assert m.dtype == statevector.dtype @@ -480,6 +479,7 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): assert np.allclose(result, expected, max(tol, 1.0e-5)) @flaky(max_runs=5) + @pytest.mark.parametrize("shots", [None, 1000000]) @pytest.mark.parametrize("measurement", [qml.expval, qml.probs, qml.var]) @pytest.mark.parametrize( "obs0_", @@ -515,23 +515,21 @@ def test_single_return_value(self, measurement, observable, lightning_sv, tol): qml.SparseHamiltonian(get_sparse_hermitian_matrix(2**4), wires=range(4)), ), ) - def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol): - if measurement is qml.probs and isinstance( - obs0_, - (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), + def test_double_return_value(self, shots, measurement, obs0_, obs1_, lightning_sv, tol): + skip_list = ( + qml.ops.Sum, + qml.ops.SProd, + qml.ops.Prod, + qml.Hamiltonian, + qml.SparseHamiltonian, + ) + if measurement is qml.probs and ( + isinstance(obs0_, skip_list) or isinstance(obs1_, skip_list) ): pytest.skip( f"Observable of type {type(obs0_).__name__} is not supported for rotating probabilities." ) - if measurement is qml.probs and isinstance( - obs1_, - (qml.ops.Sum, qml.ops.SProd, qml.ops.Prod, qml.Hamiltonian, qml.SparseHamiltonian), - ): - pytest.skip( - f"Observable of type {type(obs1_).__name__} is not supported for rotating probabilities." - ) - n_qubits = 4 n_layers = 1 np.random.seed(0) @@ -539,21 +537,38 @@ def test_double_return_value(self, measurement, obs0_, obs1_, lightning_sv, tol) ops = [qml.Hadamard(i) for i in range(n_qubits)] ops += [qml.StronglyEntanglingLayers(weights, wires=range(n_qubits))] measurements = [measurement(op=obs0_), measurement(op=obs1_)] - tape = qml.tape.QuantumScript(ops, measurements) + tape = qml.tape.QuantumScript(ops, measurements, shots=shots) - expected = self.calculate_reference(tape, lightning_sv) - if len(expected) == 1: - expected = expected[0] statevector = lightning_sv(n_qubits) statevector = statevector.get_final_state(tape) m = LightningMeasurements(statevector) - result = m.measure_final_state(tape) + + skip_list = ( + qml.ops.Sum, + qml.Hamiltonian, + qml.SparseHamiltonian, + ) + if ( + (measurement is qml.expval or measurement is qml.var) + and shots is not None + and (isinstance(obs0_, skip_list) or isinstance(obs1_, skip_list)) + ): + with pytest.raises(TypeError): + _ = m.measure_final_state(tape) + return + else: + result = m.measure_final_state(tape) + + expected = self.calculate_reference(tape, lightning_sv) + if len(expected) == 1: + expected = expected[0] assert isinstance(result, Sequence) assert len(result) == len(expected) # a few tests may fail in single precision, and hence we increase the tolerance + dtol = tol if shots is None else max(tol, 1.0e-2) for r, e in zip(result, expected): - assert np.allclose(r, e, max(tol, 1.0e-5)) + assert np.allclose(r, e, atol=dtol, rtol=dtol) @pytest.mark.parametrize( "cases", diff --git a/tests/lightning_qubit/test_measurements_samples_MCMC.py b/tests/lightning_qubit/test_measurements_samples_MCMC.py index 531c0e6164..bf2104364b 100644 --- a/tests/lightning_qubit/test_measurements_samples_MCMC.py +++ b/tests/lightning_qubit/test_measurements_samples_MCMC.py @@ -17,14 +17,13 @@ import numpy as np import pennylane as qml import pytest -from conftest import LightningDevice # tested device +from conftest import LightningDevice as ld +from conftest import device_name -from pennylane_lightning.lightning_qubit import LightningQubit - -if LightningDevice != LightningQubit: +if device_name not in ("lightning.qubit", "lightning.qubit2"): pytest.skip("Exclusive tests for lightning.qubit. Skipping.", allow_module_level=True) -if not LightningDevice._CPP_BINARY_AVAILABLE: +if not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -33,7 +32,7 @@ class TestMCMCSample: @pytest.fixture(params=[np.complex64, np.complex128]) def dev(self, request): - return qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True, c_dtype=request.param) + return qml.device(device_name, wires=2, shots=1000, mcmc=True, c_dtype=request.param) test_data_no_parameters = [ (100, [0], qml.PauliZ(wires=[0]), 100), @@ -46,12 +45,16 @@ def test_mcmc_sample_dimensions(self, dev, num_shots, measured_wires, operation, """Tests if the samples returned by sample have the correct dimensions """ - dev.apply([qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])]) - - dev.shots = num_shots - dev._wires_measured = measured_wires - dev._samples = dev.generate_samples() - s1 = dev.sample(operation) + ops = [qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=operation)], shots=num_shots) + s1 = dev.execute(tape) + else: + dev.apply(ops) + dev.shots = num_shots + dev._wires_measured = measured_wires + dev._samples = dev.generate_samples() + s1 = dev.sample(operation) assert np.array_equal(s1.shape, (shape,)) @@ -61,13 +64,17 @@ def test_sample_values(self, tol, kernel): the correct values """ dev = qml.device( - "lightning.qubit", wires=2, shots=1000, mcmc=True, kernel_name=kernel, num_burnin=100 + device_name, wires=2, shots=1000, mcmc=True, kernel_name=kernel, num_burnin=100 ) - - dev.apply([qml.RX(1.5708, wires=[0])]) - dev._wires_measured = {0} - dev._samples = dev.generate_samples() - s1 = dev.sample(qml.PauliZ(0)) + ops = [qml.RX(1.5708, wires=[0])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=qml.PauliZ(0))], shots=1000) + s1 = dev.execute(tape) + else: + dev.apply([qml.RX(1.5708, wires=[0])]) + dev._wires_measured = {0} + dev._samples = dev.generate_samples() + s1 = dev.sample(qml.PauliZ(0)) # s1 should only contain 1 and -1, which is guaranteed if # they square to 1 @@ -83,7 +90,7 @@ def test_unsupported_sample_kernels(self, tol, kernel): match=f"The {kernel} is not supported and currently only 'Local' and 'NonZeroRandom' kernels are supported.", ): dev = qml.device( - "lightning.qubit", + device_name, wires=2, shots=1000, mcmc=True, @@ -93,4 +100,4 @@ def test_unsupported_sample_kernels(self, tol, kernel): def test_wrong_num_burnin(self): with pytest.raises(ValueError, match="Shots should be greater than num_burnin."): - dev = qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True, num_burnin=1000) + dev = qml.device(device_name, wires=2, shots=1000, mcmc=True, num_burnin=1000) diff --git a/tests/lightning_qubit2/test_new_api_device.py b/tests/lightning_qubit2/test_new_api_device.py index 9d9428cf6c..c121c28158 100644 --- a/tests/lightning_qubit2/test_new_api_device.py +++ b/tests/lightning_qubit2/test_new_api_device.py @@ -231,7 +231,6 @@ def test_preprocess(self, adjoint): expected_program = qml.transforms.core.TransformProgram() expected_program.add_transform(validate_measurements, name=device.name) - expected_program.add_transform(no_sampling) expected_program.add_transform(validate_observables, accepted_observables, name=device.name) expected_program.add_transform(validate_device_wires, device.wires, name=device.name) expected_program.add_transform(qml.defer_measurements, device=device) diff --git a/tests/test_apply.py b/tests/test_apply.py index 2fa37dab5a..39f9e886b5 100644 --- a/tests/test_apply.py +++ b/tests/test_apply.py @@ -533,7 +533,6 @@ def test_apply_state_vector_lightning_handle(self, qubit_device, tol): class TestExpval: """Tests that expectation values are properly calculated or that the proper errors are raised.""" - @pytest.mark.skipif(ld._new_API, reason="Old API required") @pytest.mark.parametrize( "operation,input,expected_output", [ @@ -561,14 +560,17 @@ def test_expval_single_wire_no_parameters( """Tests that expectation values are properly calculated for single-wire observables without parameters.""" dev = qubit_device(wires=1) obs = operation(wires=[0]) - - dev.reset() - dev.apply([stateprep(np.array(input), wires=[0])], obs.diagonalizing_gates()) - res = dev.expval(obs) + ops = [stateprep(np.array(input), wires=[0])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(op=obs)]) + res = dev.execute(tape) + else: + dev.reset() + dev.apply(ops, obs.diagonalizing_gates()) + res = dev.expval(obs) assert np.isclose(res, expected_output, atol=tol, rtol=0) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_expval_estimate(self): """Test that the expectation value is not analytically calculated""" dev = qml.device(device_name, wires=1, shots=3) @@ -587,7 +589,6 @@ def circuit(): class TestVar: """Tests that variances are properly calculated.""" - @pytest.mark.skipif(ld._new_API, reason="Old API required") @pytest.mark.parametrize( "operation,input,expected_output", [ @@ -615,14 +616,17 @@ def test_var_single_wire_no_parameters( """Tests that variances are properly calculated for single-wire observables without parameters.""" dev = qubit_device(wires=1) obs = operation(wires=[0]) - - dev.reset() - dev.apply([stateprep(np.array(input), wires=[0])], obs.diagonalizing_gates()) - res = dev.var(obs) + ops = [stateprep(np.array(input), wires=[0])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.var(op=obs)]) + res = dev.execute(tape) + else: + dev.reset() + dev.apply(ops, obs.diagonalizing_gates()) + res = dev.var(obs) assert np.isclose(res, expected_output, atol=tol, rtol=0) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_var_estimate(self): """Test that the variance is not analytically calculated""" @@ -639,7 +643,6 @@ def circuit(): assert var != 1.0 -@pytest.mark.skipif(ld._new_API, reason="Old API required") class TestSample: """Tests that samples are properly calculated.""" @@ -652,29 +655,47 @@ def test_sample_dimensions(self, qubit_device): # state is set to None in __init__ and only properly # initialized during reset dev = qubit_device(wires=2) - dev.reset() - - dev.apply([qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])]) - - dev.shots = 10 - dev._wires_measured = {0} - dev._samples = dev.generate_samples() - s1 = dev.sample(qml.PauliZ(wires=[0])) - assert np.array_equal(s1.shape, (10,)) - - dev.reset() - dev.shots = 12 - dev._wires_measured = {1} - dev._samples = dev.generate_samples() - s2 = dev.sample(qml.PauliZ(wires=[1])) - assert np.array_equal(s2.shape, (12,)) + ops = [qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])] - dev.reset() - dev.shots = 17 - dev._wires_measured = {0, 1} - dev._samples = dev.generate_samples() - s3 = dev.sample(qml.PauliX(0) @ qml.PauliZ(1)) - assert np.array_equal(s3.shape, (17,)) + shots = 10 + obs = qml.PauliZ(wires=[0]) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s1 = dev.execute(tape) + else: + dev.reset() + dev.apply(ops) + dev.shots = shots + dev._wires_measured = {0} + dev._samples = dev.generate_samples() + s1 = dev.sample(obs) + assert np.array_equal(s1.shape, (shots,)) + + shots = 12 + obs = qml.PauliZ(wires=[1]) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s2 = dev.execute(tape) + else: + dev.reset() + dev.shots = shots + dev._wires_measured = {1} + dev._samples = dev.generate_samples() + s2 = dev.sample(qml.PauliZ(wires=[1])) + assert np.array_equal(s2.shape, (shots,)) + + shots = 17 + obs = qml.PauliX(0) @ qml.PauliZ(1) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s3 = dev.execute(tape) + else: + dev.reset() + dev.shots = shots + dev._wires_measured = {0, 1} + dev._samples = dev.generate_samples() + s3 = dev.sample(qml.PauliZ(wires=[1])) + assert np.array_equal(s3.shape, (shots,)) def test_sample_values(self, qubit_device, tol): """Tests if the samples returned by sample have @@ -685,14 +706,21 @@ def test_sample_values(self, qubit_device, tol): # state is set to None in __init__ and only properly # initialized during reset dev = qubit_device(wires=2) - dev.reset() - dev.shots = 1000 - dev.apply([qml.RX(1.5708, wires=[0])]) - dev._wires_measured = {0} - dev._samples = dev.generate_samples() + ops = [qml.RX(1.5708, wires=[0])] - s1 = dev.sample(qml.PauliZ(0)) + shots = 1000 + obs = qml.PauliZ(0) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s1 = dev.execute(tape) + else: + dev.reset() + dev.apply(ops) + dev.shots = shots + dev._wires_measured = {0} + dev._samples = dev.generate_samples() + s1 = dev.sample(obs) # s1 should only contain 1 and -1, which is guaranteed if # they square to 1 @@ -715,6 +743,7 @@ def test_load_default_qubit_device(self): assert dev.num_wires == 2 assert dev.short_name == device_name + @pytest.mark.xfail(ld._new_API, reason="Old device API required.") @pytest.mark.skipif(not ld._CPP_BINARY_AVAILABLE, reason="Lightning binary required") def test_no_backprop(self): """Test that lightning device does not support the backprop @@ -729,6 +758,7 @@ def circuit(): with pytest.raises(qml.QuantumFunctionError): qml.QNode(circuit, dev, diff_method="backprop") + @pytest.mark.xfail(ld._new_API, reason="New device API currently has the wrong module path.") @pytest.mark.skipif(not ld._CPP_BINARY_AVAILABLE, reason="Lightning binary required") def test_best_gets_lightning(self): """Test that the best differentiation method returns lightning @@ -777,7 +807,6 @@ def circuit(x): assert np.isclose(circuit(p), 1, atol=tol, rtol=0) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_nonzero_shots(self, tol_stochastic): """Test that the default qubit plugin provides correct result for high shot number""" @@ -1128,25 +1157,27 @@ def circuit(): assert np.isclose(circuit(), expected_output, atol=tol, rtol=0) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_multi_samples_return_correlated_results(self, qubit_device): """Tests if the samples returned by the sample function have the correct dimensions """ - dev = qubit_device(wires=2) - dev.shots = 1000 + dev = qubit_device(wires=2, shots=1000) @qml.qnode(dev) def circuit(): qml.Hadamard(0) qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) + if ld._new_API: + return qml.sample(wires=[0, 1]) + else: + return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) outcomes = circuit() + if ld._new_API: + outcomes = outcomes.T assert np.array_equal(outcomes[0], outcomes[1]) - @pytest.mark.xfail(ld._new_API, reason="Old API required") @pytest.mark.parametrize("num_wires", [3, 4, 5, 6, 7, 8]) def test_multi_samples_return_correlated_results_more_wires_than_size_of_observable( self, num_wires @@ -1161,13 +1192,17 @@ def test_multi_samples_return_correlated_results_more_wires_than_size_of_observa def circuit(): qml.Hadamard(0) qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) + if ld._new_API: + return qml.sample(wires=[0, 1]) + else: + return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) outcomes = circuit() + if ld._new_API: + outcomes = outcomes.T assert np.array_equal(outcomes[0], outcomes[1]) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_snapshot_is_ignored_without_shot(self): """Tests if the Snapshot operator is ignored correctly""" dev = qml.device(device_name, wires=4) @@ -1184,7 +1219,6 @@ def circuit(): assert np.allclose(outcomes, [0.0]) - @pytest.mark.xfail(ld._new_API, reason="Old API required") def test_snapshot_is_ignored_with_shots(self): """Tests if the Snapshot operator is ignored correctly""" dev = qml.device(device_name, wires=4, shots=1000) @@ -1195,9 +1229,14 @@ def circuit(): qml.Snapshot() qml.adjoint(qml.Snapshot()) qml.CNOT(wires=[0, 1]) - return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) + if ld._new_API: + return qml.sample(wires=[0, 1]) + else: + return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1)) outcomes = circuit() + if ld._new_API: + outcomes = outcomes.T assert np.array_equal(outcomes[0], outcomes[1]) diff --git a/tests/test_expval.py b/tests/test_expval.py index 877dd10266..9725028014 100644 --- a/tests/test_expval.py +++ b/tests/test_expval.py @@ -19,9 +19,10 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, VARPHI, LightningDevice, device_name +from conftest import PHI, THETA, VARPHI +from conftest import LightningDevice as ld -if LightningDevice._new_API and not LightningDevice._CPP_BINARY_AVAILABLE: +if ld._new_API and not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -29,86 +30,105 @@ class TestExpval: """Test expectation values""" - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_identity_expectation(self, theta, phi, qubit_device, tol): """Test that identity expectation value (i.e. the trace) is 1""" dev = qubit_device(wires=3) O1 = qml.Identity(wires=[0]) O2 = qml.Identity(wires=[1]) - - dev.apply( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], - ) - - res = np.array([dev.expval(O1), dev.expval(O2)]) + ops = [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(O1), qml.expval(O2)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], + ) + res = np.array([dev.expval(O1), dev.expval(O2)]) assert np.allclose(res, np.array([1, 1]), tol) - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_pauliz_expectation(self, theta, phi, qubit_device, tol): """Test that PauliZ expectation value is correct""" dev = qubit_device(wires=3) O1 = qml.PauliZ(wires=[0]) O2 = qml.PauliZ(wires=[1]) - - dev.apply( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], - ) - - res = np.array([dev.expval(O1), dev.expval(O2)]) + ops = [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(O1), qml.expval(O2)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], + ) + + res = np.array([dev.expval(O1), dev.expval(O2)]) assert np.allclose(res, np.array([np.cos(theta), np.cos(theta) * np.cos(phi)]), tol) - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_paulix_expectation(self, theta, phi, qubit_device, tol): """Test that PauliX expectation value is correct""" dev = qubit_device(wires=3) O1 = qml.PauliX(wires=[0]) O2 = qml.PauliX(wires=[1]) - - dev.apply( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], - ) - - res = np.array([dev.expval(O1), dev.expval(O2)], dtype=dev.C_DTYPE) + ops = [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(O1), qml.expval(O2)]) + res = dev.execute(tape) + + else: + dev.apply( + ops, + rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], + ) + + res = np.array([dev.expval(O1), dev.expval(O2)], dtype=dev.C_DTYPE) assert np.allclose( - res, np.array([np.sin(theta) * np.sin(phi), np.sin(phi)], dtype=dev.C_DTYPE), tol * 10 + res, + np.array([np.sin(theta) * np.sin(phi), np.sin(phi)], dtype=dev.dtype), + tol * 10, ) - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_pauliy_expectation(self, theta, phi, qubit_device, tol): """Test that PauliY expectation value is correct""" dev = qubit_device(wires=3) O1 = qml.PauliY(wires=[0]) O2 = qml.PauliY(wires=[1]) - - dev.apply( - [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], - ) - - res = np.array([dev.expval(O1), dev.expval(O2)]) + ops = [qml.RX(theta, wires=[0]), qml.RX(phi, wires=[1]), qml.CNOT(wires=[0, 1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(O1), qml.expval(O2)]) + res = dev.execute(tape) + + else: + dev.apply( + ops, + rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], + ) + + res = np.array([dev.expval(O1), dev.expval(O2)]) assert np.allclose(res, np.array([0, -np.cos(theta) * np.sin(phi)]), tol) - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_hadamard_expectation(self, theta, phi, qubit_device, tol): """Test that Hadamard expectation value is correct""" dev = qubit_device(wires=3) O1 = qml.Hadamard(wires=[0]) O2 = qml.Hadamard(wires=[1]) - - dev.apply( - [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])], - rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], - ) - - res = np.array([dev.expval(O1), dev.expval(O2)]) + ops = [qml.RY(theta, wires=[0]), qml.RY(phi, wires=[1]), qml.CNOT(wires=[0, 1])] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(O1), qml.expval(O2)]) + res = dev.execute(tape) + + else: + dev.apply( + ops, + rotations=[*O1.diagonalizing_gates(), *O2.diagonalizing_gates()], + ) + + res = np.array([dev.expval(O1), dev.expval(O2)]) expected = np.array( [np.sin(theta) * np.sin(phi) + np.cos(theta), np.cos(theta) * np.cos(phi) + np.sin(phi)] ) / np.sqrt(2) @@ -256,7 +276,6 @@ def circuit(x, y): assert qml.math.allclose(g, expected) -@pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") @pytest.mark.parametrize("theta,phi,varphi", list(zip(THETA, PHI, VARPHI))) class TestTensorExpval: """Test tensor expectation values""" @@ -266,18 +285,19 @@ def test_paulix_pauliy(self, theta, phi, varphi, qubit_device, tol): correctly""" dev = qubit_device(wires=3) obs = qml.PauliX(0) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - rotations=obs.diagonalizing_gates(), - ) - res = dev.expval(obs) + ops = [ + qml.RX(theta, wires=[0]), + qml.RX(phi, wires=[1]), + qml.RX(varphi, wires=[2]), + qml.CNOT(wires=[0, 1]), + qml.CNOT(wires=[1, 2]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(op=obs)]) + res = dev.execute(tape) + else: + dev.apply(ops, rotations=obs.diagonalizing_gates()) + res = dev.expval(obs) expected = np.sin(theta) * np.sin(phi) * np.sin(varphi) @@ -288,19 +308,23 @@ def test_pauliz_identity(self, theta, phi, varphi, qubit_device, tol): correctly""" dev = qubit_device(wires=3) obs = qml.PauliZ(0) @ qml.Identity(1) @ qml.PauliZ(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - rotations=obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) + ops = [ + qml.RX(theta, wires=[0]), + qml.RX(phi, wires=[1]), + qml.RX(varphi, wires=[2]), + qml.CNOT(wires=[0, 1]), + qml.CNOT(wires=[1, 2]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(op=obs)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=obs.diagonalizing_gates(), + ) + + res = dev.expval(obs) expected = np.cos(varphi) * np.cos(phi) @@ -311,19 +335,22 @@ def test_pauliz_hadamard_pauliy(self, theta, phi, varphi, qubit_device, tol): works correctly""" dev = qubit_device(wires=3) obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - rotations=obs.diagonalizing_gates(), - ) - - res = dev.expval(obs) + ops = [ + qml.RX(theta, wires=[0]), + qml.RX(phi, wires=[1]), + qml.RX(varphi, wires=[2]), + qml.CNOT(wires=[0, 1]), + qml.CNOT(wires=[1, 2]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.expval(op=obs)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=obs.diagonalizing_gates(), + ) + res = dev.expval(obs) expected = -(np.cos(varphi) * np.sin(phi) + np.sin(varphi) * np.cos(theta)) / np.sqrt(2) assert np.allclose(res, expected, tol) diff --git a/tests/test_gates.py b/tests/test_gates.py index 18f925068e..3cf15c0513 100644 --- a/tests/test_gates.py +++ b/tests/test_gates.py @@ -20,9 +20,11 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, LightningDevice, device_name +from conftest import PHI, THETA +from conftest import LightningDevice as ld +from conftest import device_name -if LightningDevice._new_API and not LightningDevice._CPP_BINARY_AVAILABLE: +if ld._new_API and not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -85,7 +87,7 @@ def op(op_name): return ops_list.get(op_name) -@pytest.mark.parametrize("op_name", LightningDevice.operations) +@pytest.mark.parametrize("op_name", ld.operations) def test_gate_unitary_correct(op, op_name): """Test if lightning device correctly applies gates by reconstructing the unitary matrix and comparing to the expected version""" @@ -143,7 +145,7 @@ def output(input): assert np.allclose(unitary, unitary_expected) -@pytest.mark.parametrize("op_name", LightningDevice.operations) +@pytest.mark.parametrize("op_name", ld.operations) def test_inverse_unitary_correct(op, op_name): """Test if lightning device correctly applies inverse gates by reconstructing the unitary matrix and comparing to the expected version""" @@ -247,8 +249,8 @@ def output(input): assert np.allclose(unitary, random_unitary_inv) -@pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") -@pytest.mark.skipif(not LightningDevice._CPP_BINARY_AVAILABLE, reason="Lightning binary required") +@pytest.mark.skipif(ld._new_API, reason="Old API required") +@pytest.mark.skipif(not ld._CPP_BINARY_AVAILABLE, reason="Lightning binary required") @pytest.mark.parametrize( "obs,has_rotation", [ diff --git a/tests/test_measurements.py b/tests/test_measurements.py index baca67189b..a1bfa173c4 100644 --- a/tests/test_measurements.py +++ b/tests/test_measurements.py @@ -592,7 +592,6 @@ def circuit2(): assert np.allclose(circuit1(), circuit2(), atol=tol) -@pytest.mark.skipif(ld._new_API, reason="Old API required") class TestSample: """Tests that samples are properly calculated.""" @@ -608,28 +607,35 @@ def test_sample_dimensions(self, qubit_device, shots, wires): """Tests if the samples returned by the sample function have the correct dimensions """ - dev = qubit_device(wires=2) - - dev.apply([qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])]) - - dev.shots = shots - dev._wires_measured = wires - dev._samples = dev.generate_samples() - s1 = dev.sample(qml.PauliZ(wires=[0])) - assert np.array_equal(s1.shape, (dev.shots,)) + dev = qubit_device(wires=2, shots=shots) + ops = [qml.RX(1.5708, wires=[0]), qml.RX(1.5708, wires=[1])] + obs = qml.PauliZ(wires=[0]) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s1 = dev.execute(tape) + else: + dev.apply(ops) + dev._wires_measured = wires + dev._samples = dev.generate_samples() + s1 = dev.sample(obs) + assert np.array_equal(s1.shape, (shots,)) def test_sample_values(self, qubit_device, tol): """Tests if the samples returned by sample have the correct values """ - dev = qubit_device(wires=2) - - dev.shots = 1000 - dev.apply([qml.RX(1.5708, wires=[0])]) - dev._wires_measured = {0} - dev._samples = dev.generate_samples() - - s1 = dev.sample(qml.PauliZ(0)) + shots = 1000 + dev = qubit_device(wires=2, shots=shots) + ops = [qml.RX(1.5708, wires=[0])] + obs = qml.PauliZ(0) + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.sample(op=obs)], shots=shots) + s1 = dev.execute(tape) + else: + dev.apply(ops) + dev._wires_measured = {0} + dev._samples = dev.generate_samples() + s1 = dev.sample(qml.PauliZ(0)) # s1 should only contain 1 and -1, which is guaranteed if # they square to 1 @@ -683,7 +689,6 @@ def circuit2(): @flaky(max_runs=5) -@pytest.mark.skipif(ld._new_API, reason="Old API required") @pytest.mark.parametrize("shots", [10000, [10000, 11111]]) @pytest.mark.parametrize("measure_f", [qml.counts, qml.expval, qml.probs, qml.sample, qml.var]) @pytest.mark.parametrize( diff --git a/tests/test_var.py b/tests/test_var.py index 8c3d9defe8..aaa86f1ce7 100644 --- a/tests/test_var.py +++ b/tests/test_var.py @@ -17,9 +17,10 @@ import numpy as np import pennylane as qml import pytest -from conftest import PHI, THETA, VARPHI, LightningDevice +from conftest import PHI, THETA, VARPHI +from conftest import LightningDevice as ld -if LightningDevice._new_API and not LightningDevice._CPP_BINARY_AVAILABLE: +if ld._new_API and not ld._CPP_BINARY_AVAILABLE: pytest.skip("No binary module found. Skipping.", allow_module_level=True) np.random.seed(42) @@ -29,23 +30,25 @@ class TestVar: """Tests for the variance""" - @pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") def test_var(self, theta, phi, qubit_device, tol): """Tests for variance calculation""" dev = qubit_device(wires=3) # test correct variance for of a rotated state - observable = qml.PauliZ(wires=[0]) - - dev.apply( - [ - qml.RX(phi, wires=[0]), - qml.RY(theta, wires=[0]), - ], - rotations=[*observable.diagonalizing_gates()], - ) - - var = dev.var(observable) + obs = qml.PauliZ(wires=[0]) + ops = [ + qml.RX(phi, wires=[0]), + qml.RY(theta, wires=[0]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.var(op=obs)]) + var = dev.execute(tape) + else: + dev.apply( + ops, + rotations=[*obs.diagonalizing_gates()], + ) + var = dev.var(obs) expected = 0.25 * (3 - np.cos(2 * theta) - 2 * np.cos(theta) ** 2 * np.cos(2 * phi)) assert np.allclose(var, expected, tol) @@ -75,7 +78,6 @@ def circuit(): assert np.allclose(circ(), circ_def(), tol) -@pytest.mark.skipif(LightningDevice._new_API, reason="Old API required") @pytest.mark.parametrize("theta, phi, varphi", list(zip(THETA, PHI, VARPHI))) class TestTensorVar: """Tests for variance of tensor observables""" @@ -84,19 +86,22 @@ def test_paulix_pauliy(self, theta, phi, varphi, qubit_device, tol): """Test that a tensor product involving PauliX and PauliY works correctly""" dev = qubit_device(wires=3) obs = qml.PauliX(0) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - rotations=obs.diagonalizing_gates(), - ) - - res = dev.var(obs) + ops = [ + qml.RX(theta, wires=[0]), + qml.RX(phi, wires=[1]), + qml.RX(varphi, wires=[2]), + qml.CNOT(wires=[0, 1]), + qml.CNOT(wires=[1, 2]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.var(op=obs)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=obs.diagonalizing_gates(), + ) + res = dev.var(obs) expected = ( 8 * np.sin(theta) ** 2 * np.cos(2 * varphi) * np.sin(phi) ** 2 @@ -113,19 +118,22 @@ def test_pauliz_hadamard_pauliy(self, theta, phi, varphi, qubit_device, tol): """Test that a tensor product involving PauliZ and PauliY and hadamard works correctly""" dev = qubit_device(wires=3) obs = qml.PauliZ(0) @ qml.Hadamard(1) @ qml.PauliY(2) - - dev.apply( - [ - qml.RX(theta, wires=[0]), - qml.RX(phi, wires=[1]), - qml.RX(varphi, wires=[2]), - qml.CNOT(wires=[0, 1]), - qml.CNOT(wires=[1, 2]), - ], - rotations=obs.diagonalizing_gates(), - ) - - res = dev.var(obs) + ops = [ + qml.RX(theta, wires=[0]), + qml.RX(phi, wires=[1]), + qml.RX(varphi, wires=[2]), + qml.CNOT(wires=[0, 1]), + qml.CNOT(wires=[1, 2]), + ] + if ld._new_API: + tape = qml.tape.QuantumScript(ops, [qml.var(op=obs)]) + res = dev.execute(tape) + else: + dev.apply( + ops, + rotations=obs.diagonalizing_gates(), + ) + res = dev.var(obs) expected = ( 3