diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 6ce2321532..d76910abdf 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,9 @@ ### New features since last release +* `lightning.kokkos` supports mid-circuit measurements. + [(#672)](https://github.com/PennyLaneAI/pennylane-lightning/pull/672) + * Add dynamic linking to LAPACK/OpenBlas shared objects in scipy.libs for both C++ and Python layer. [(#653)](https://github.com/PennyLaneAI/pennylane-lightning/pull/653) diff --git a/.github/workflows/tests_gpu_cuda.yml b/.github/workflows/tests_gpu_cuda.yml index 24c95ac426..9d276b1704 100644 --- a/.github/workflows/tests_gpu_cuda.yml +++ b/.github/workflows/tests_gpu_cuda.yml @@ -311,7 +311,7 @@ jobs: OMP_PROC_BIND: false run: | cd main/ - PL_DEVICE=lightning.qubit python -m pytest tests/ $COVERAGE_FLAGS + PL_DEVICE=lightning.qubit python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS pl-device-test --device lightning.qubit --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.qubit --shots=None --skip-ops $COVERAGE_FLAGS --cov-append PL_DEVICE=lightning.gpu python -m pytest tests/ $COVERAGE_FLAGS diff --git a/.github/workflows/tests_gpu_kokkos.yml b/.github/workflows/tests_gpu_kokkos.yml index 4e54602a14..663f020e9e 100644 --- a/.github/workflows/tests_gpu_kokkos.yml +++ b/.github/workflows/tests_gpu_kokkos.yml @@ -303,9 +303,9 @@ jobs: run: | cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` + PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS 2> /dev/null || echo Something went wrong with Pytest pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append 2> /dev/null || echo Something went wrong with pl-device-test shot=20000 pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append 2> /dev/null || echo Something went wrong with pl-device-test shot=None - PL_DEVICE=${DEVICENAME} python -m pytest tests/ $COVERAGE_FLAGS 2> /dev/null || echo Something went wrong with Pytest mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml - name: Install all backend devices @@ -325,10 +325,10 @@ jobs: OMP_PROC_BIND: false run: | cd main/ - PL_DEVICE=lightning.qubit python -m pytest tests/ $COVERAGE_FLAGS + PL_DEVICE=lightning.qubit python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS pl-device-test --device lightning.qubit --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.qubit --shots=None --skip-ops $COVERAGE_FLAGS --cov-append - PL_DEVICE=lightning.kokkos python -m pytest tests/ $COVERAGE_FLAGS + PL_DEVICE=lightning.kokkos python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.kokkos --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.kokkos --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv coverage.xml coverage-${{ github.job }}-${{ matrix.pl_backend }}.xml diff --git a/.github/workflows/tests_linux.yml b/.github/workflows/tests_linux.yml index 1ed10a4fcb..6f88852119 100644 --- a/.github/workflows/tests_linux.yml +++ b/.github/workflows/tests_linux.yml @@ -21,6 +21,7 @@ env: COVERAGE_FLAGS: "--cov=pennylane_lightning --cov-report=term-missing --no-flaky-report -p no:warnings --tb=native" GCC_VERSION: 11 OMP_NUM_THREADS: "2" + OMP_PROC_BIND: "false" concurrency: group: tests_linux-${{ github.ref }}-${{ inputs.lightning-version }}-${{ inputs.pennylane-version }} @@ -353,8 +354,8 @@ jobs: python -m pip install pytest-xdist cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS - PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct" $COVERAGE_FLAGS --cov-append + OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "not unitary_correct and not test_native_mcm" $COVERAGE_FLAGS + PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "unitary_correct and not test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} @@ -558,7 +559,8 @@ jobs: python -m pip install pytest-xdist cd main/ DEVICENAME=`echo ${{ matrix.pl_backend }} | sed "s/_/./g"` - PL_DEVICE=${DEVICENAME} python -m pytest tests/ $COVERAGE_FLAGS + PL_DEVICE=${DEVICENAME} python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS + OMP_NUM_THREADS=1 PL_DEVICE=${DEVICENAME} python -m pytest -n auto tests/ -k "test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device ${DEVICENAME} --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} @@ -579,11 +581,11 @@ jobs: # TODO: Remove installing pytest-xdist with release v0.36.0 python -m pip install pytest-xdist cd main/ - OMP_NUM_THREADS=1 PL_DEVICE=lightning.qubit python -m pytest -n auto tests/ -k "not unitary_correct" $COVERAGE_FLAGS - PL_DEVICE=lightning.qubit python -m pytest tests/ -k "unitary_correct" $COVERAGE_FLAGS --cov-append + OMP_NUM_THREADS=1 PL_DEVICE=lightning.qubit python -m pytest -n auto tests/ -k "not unitary_correct and not test_native_mcm" $COVERAGE_FLAGS + PL_DEVICE=lightning.qubit python -m pytest tests/ -k "unitary_correct and not test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.qubit --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.qubit --shots=None --skip-ops $COVERAGE_FLAGS --cov-append - PL_DEVICE=lightning.kokkos python -m pytest tests/ $COVERAGE_FLAGS --cov-append + PL_DEVICE=lightning.kokkos python -m pytest tests/ -k "not test_native_mcm" $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.kokkos --skip-ops --shots=20000 $COVERAGE_FLAGS --cov-append pl-device-test --device lightning.kokkos --shots=None --skip-ops $COVERAGE_FLAGS --cov-append mv .coverage .coverage-${{ github.job }}-${{ matrix.pl_backend }} diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 9be711cbdb..3da22800fb 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-dev27" +__version__ = "0.36.0-dev28" diff --git a/pennylane_lightning/core/lightning_base.py b/pennylane_lightning/core/lightning_base.py index c4f72293a0..38183ea784 100644 --- a/pennylane_lightning/core/lightning_base.py +++ b/pennylane_lightning/core/lightning_base.py @@ -94,9 +94,9 @@ def stopping_condition(self): and observable) and returns ``True`` if supported by the device.""" def accepts_obj(obj): - if obj.name == "QFT": + if isinstance(obj, qml.QFT): return len(obj.wires) < 10 - if obj.name == "GroverOperator": + if isinstance(obj, qml.GroverOperator): return len(obj.wires) < 13 is_not_tape = not isinstance(obj, qml.tape.QuantumTape) is_supported = getattr(self, "supports_operation", lambda name: False)(obj.name) diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/StateVectorKokkos.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/StateVectorKokkos.hpp index 87ac3b302a..d691526661 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/StateVectorKokkos.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/StateVectorKokkos.hpp @@ -757,6 +757,53 @@ class StateVectorKokkos final return -static_cast(0.5); } + /** + * @brief Collapse the state vector after having measured one of the + * qubits. + * + * The branch parameter imposes the measurement result on the given wire. + * + * @param wire Wire to collapse. + * @param branch Branch 0 or 1. + */ + void collapse(std::size_t wire, bool branch) { + KokkosVector matrix("gate_matrix", 4); + Kokkos::parallel_for( + matrix.size(), KOKKOS_LAMBDA(std::size_t k) { + matrix(k) = ((k == 0 && branch == 0) || (k == 3 && branch == 1)) + ? ComplexT{1.0, 0.0} + : ComplexT{0.0, 0.0}; + }); + applyMultiQubitOp(matrix, {wire}, false); + normalize(); + } + + /** + * @brief Normalize vector (to have norm 1). + */ + void normalize() { + auto sv_view = getView(); + + PrecisionT squaredNorm = 0.0; + Kokkos::parallel_reduce( + sv_view.size(), + KOKKOS_LAMBDA(std::size_t i, PrecisionT & sum) { + const PrecisionT norm = Kokkos::abs(sv_view(i)); + sum += norm * norm; + }, + squaredNorm); + + PL_ABORT_IF(squaredNorm < + std::numeric_limits::epsilon() * 1e2, + "vector has norm close to zero and can't be normalized"); + + const std::complex inv_norm = + 1. / Kokkos::sqrt(squaredNorm); + Kokkos::parallel_for( + sv_view.size(), + KOKKOS_LAMBDA(std::size_t i) { sv_view(i) *= inv_norm; }); + } + /** * @brief Update data of the class * diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/bindings/LKokkosBindings.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/bindings/LKokkosBindings.hpp index 6432864c4e..f476ceba50 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/bindings/LKokkosBindings.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/bindings/LKokkosBindings.hpp @@ -137,7 +137,11 @@ void registerBackendClassSpecificBindings(PyClass &pyclass) { sv.applyOperation(str, wires, inv, std::vector{}, conv_matrix); }, - "Apply operation via the gate matrix"); + "Apply operation via the gate matrix") + .def("collapse", &StateVectorT::collapse, + "Collapse the statevector onto the 0 or 1 branch of a given wire.") + .def("normalize", &StateVectorT::normalize, + "Normalize the statevector to norm 1."); } /** diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/gates/GateFunctorsGenerator.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/gates/GateFunctorsGenerator.hpp index eea42d7937..f8c19b1425 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/gates/GateFunctorsGenerator.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/gates/GateFunctorsGenerator.hpp @@ -972,4 +972,5 @@ template struct generatorMultiRZFunctor { 1 - 2 * int(Kokkos::Impl::bit_count(k & wires_parity) % 2)); } }; + } // namespace Pennylane::LightningKokkos::Functors \ No newline at end of file diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp index 3fa7c5a0a2..d4f85aeead 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #pragma once +#include #include #include @@ -682,7 +683,10 @@ class Measurements final }); // Sampling using Random_XorShift64_Pool - Kokkos::Random_XorShift64_Pool<> rand_pool(5374857); + Kokkos::Random_XorShift64_Pool<> rand_pool( + std::chrono::high_resolution_clock::now() + .time_since_epoch() + .count()); Kokkos::parallel_for( Kokkos::RangePolicy(0, num_samples), diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/tests/Test_StateVectorLKokkos.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/tests/Test_StateVectorLKokkos.cpp index d238b3b5cb..7f94b6256b 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/tests/Test_StateVectorLKokkos.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/tests/Test_StateVectorLKokkos.cpp @@ -308,4 +308,72 @@ TEMPLATE_TEST_CASE("StateVectorKokkos::StateVectorKokkos", REQUIRE(sv.getDataVector() == data_); // REQUIRE(sv.getDataVector() == approx(st_data)); } -} \ No newline at end of file +} + +TEMPLATE_TEST_CASE("StateVectorKokkos::collapse", "[StateVectorKokkos]", float, + double) { + using PrecisionT = TestType; + using ComplexT = typename StateVectorKokkos::ComplexT; + using TestVectorT = TestVector; + + const std::size_t num_qubits = 3; + + // TODO @tomlqc use same template for testing all Lightning flavours? + + SECTION("Collapse the state vector after having measured one of the " + "qubits.") { + TestVectorT init_state = createPlusState_(num_qubits); + + const ComplexT coef{0.5, PrecisionT{0.0}}; + const ComplexT zero{PrecisionT{0.0}, PrecisionT{0.0}}; + + std::vector>> expected_state = { + {{coef, coef, coef, coef, zero, zero, zero, zero}, + {coef, coef, zero, zero, coef, coef, zero, zero}, + {coef, zero, coef, zero, coef, zero, coef, zero}}, + {{zero, zero, zero, zero, coef, coef, coef, coef}, + {zero, zero, coef, coef, zero, zero, coef, coef}, + {zero, coef, zero, coef, zero, coef, zero, coef}}, + }; + + std::size_t wire = GENERATE(0, 1, 2); + std::size_t branch = GENERATE(0, 1); + StateVectorKokkos sv( + reinterpret_cast(init_state.data()), init_state.size()); + sv.collapse(wire, branch); + + PrecisionT eps = std::numeric_limits::epsilon() * 10e3; + REQUIRE(isApproxEqual(sv.getData(), sv.getDataVector().size(), + expected_state[branch][wire].data(), + expected_state[branch][wire].size(), eps)); + } +} + +TEMPLATE_TEST_CASE("StateVectorKokkos::normalize", "[StateVectorKokkos]", float, + double) { + using PrecisionT = TestType; + using ComplexT = typename StateVectorKokkos::ComplexT; + + // TODO @tomlqc use same template for testing all Lightning flavours? + + SECTION("Normalize state vector.") { + const ComplexT init{1.0, PrecisionT{0.0}}; + const ComplexT half{0.5, PrecisionT{0.0}}; + const ComplexT zero{PrecisionT{0.0}, PrecisionT{0.0}}; + + std::vector init_state = {init, zero, init, init, + zero, zero, init, zero}; + + std::vector expected_state = {half, zero, half, half, + zero, zero, half, zero}; + + StateVectorKokkos sv( + reinterpret_cast(init_state.data()), init_state.size()); + sv.normalize(); + + PrecisionT eps = std::numeric_limits::epsilon() * 1e3; + REQUIRE(isApproxEqual(sv.getData(), sv.getDataVector().size(), + expected_state.data(), expected_state.size(), + eps)); + } +} diff --git a/pennylane_lightning/core/src/simulators/lightning_qubit/tests/Test_StateVectorLQubit.cpp b/pennylane_lightning/core/src/simulators/lightning_qubit/tests/Test_StateVectorLQubit.cpp index 310cb4cb68..7fe7936cb8 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/tests/Test_StateVectorLQubit.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/tests/Test_StateVectorLQubit.cpp @@ -245,9 +245,35 @@ TEMPLATE_PRODUCT_TEST_CASE("StateVectorLQubit::collapse", "[StateVectorLQubit]", std::size_t wire = GENERATE(0, 1, 2); std::size_t branch = GENERATE(0, 1); - StateVectorLQubitManaged sv(init_state); + StateVectorT sv(init_state.data(), init_state.size()); sv.collapse(wire, branch); REQUIRE(sv.getDataVector() == approx(expected_state[branch][wire])); } } + +TEMPLATE_PRODUCT_TEST_CASE("StateVectorLQubit::normalize", + "[StateVectorLQubit]", + (StateVectorLQubitManaged, StateVectorLQubitRaw), + (float, double)) { + using StateVectorT = TestType; + using PrecisionT = typename StateVectorT::PrecisionT; + using ComplexT = typename StateVectorT::ComplexT; + + SECTION("Normalize state vector.") { + const ComplexT init{1.0, PrecisionT{0.0}}; + const ComplexT half{0.5, PrecisionT{0.0}}; + const ComplexT zero{PrecisionT{0.0}, PrecisionT{0.0}}; + + std::vector init_state = {init, zero, init, init, + zero, zero, init, zero}; + + std::vector expected_state = {half, zero, half, half, + zero, zero, half, zero}; + + StateVectorT sv(init_state.data(), init_state.size()); + sv.normalize(); + + REQUIRE(sv.getDataVector() == approx(expected_state)); + } +} diff --git a/pennylane_lightning/core/src/utils/TestHelpers.hpp b/pennylane_lightning/core/src/utils/TestHelpers.hpp index af1a46618a..f4263cdb0e 100644 --- a/pennylane_lightning/core/src/utils/TestHelpers.hpp +++ b/pennylane_lightning/core/src/utils/TestHelpers.hpp @@ -288,6 +288,19 @@ auto createZeroState(size_t num_qubits) -> TestVector { return res; } +/** + * @brief create |+>^N + */ +template +auto createPlusState_(size_t num_qubits) -> TestVector { + TestVector res(size_t{1U} << num_qubits, 1.0, + getBestAllocator()); + for (auto &elem : res) { + elem /= std::sqrt(1U << num_qubits); + } + return res; +} + /** * @brief create |+>^N */ diff --git a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py index f330ff69fa..7064a49594 100644 --- a/pennylane_lightning/lightning_kokkos/lightning_kokkos.py +++ b/pennylane_lightning/lightning_kokkos/lightning_kokkos.py @@ -21,6 +21,8 @@ from warnings import warn import numpy as np +from pennylane.measurements import MidMeasureMP +from pennylane.ops import Conditional from pennylane_lightning.core.lightning_base import ( LightningBase, @@ -210,6 +212,29 @@ def __init__( if not LightningKokkos.kokkos_config: LightningKokkos.kokkos_config = _kokkos_configuration() + @property + def stopping_condition(self): + """.BooleanFn: Returns the stopping condition for the device. The returned + function accepts a queueable object (including a PennyLane operation + and observable) and returns ``True`` if supported by the device.""" + fun = super().stopping_condition + + def accepts_obj(obj): + return fun(obj) or isinstance( + obj, (qml.measurements.MidMeasureMP, qml.ops.Conditional) + ) + + return qml.BooleanFn(accepts_obj) + + # pylint: disable=missing-function-docstring + @classmethod + def capabilities(cls): + capabilities = super().capabilities().copy() + capabilities.update( + supports_mid_measure=True, + ) + return capabilities + @staticmethod def _asarray(arr, dtype=None): arr = np.asarray(arr) # arr is not copied @@ -370,7 +395,25 @@ def _apply_basis_state(self, state, wires): num = self._get_basis_state_index(state, wires) self._create_basis_state(num) - def apply_lightning(self, operations): + def _apply_lightning_midmeasure(self, operation: MidMeasureMP, mid_measurements: dict): + """Execute a MidMeasureMP operation and return the sample in mid_measurements. + Args: + operation (~pennylane.operation.Operation): mid-circuit measurement + Returns: + None + """ + wires = self.wires.indices(operation.wires) + wire = list(wires)[0] + sample = qml.math.reshape(self.generate_samples(shots=1), (-1,))[wire] + if operation.postselect is not None and sample != operation.postselect: + mid_measurements[operation] = -1 + return + mid_measurements[operation] = sample + getattr(self.state_vector, "collapse")(wire, bool(sample)) + if operation.reset and bool(sample): + self.apply([qml.PauliX(operation.wires)], mid_measurements=mid_measurements) + + def apply_lightning(self, operations, mid_measurements=None): """Apply a list of operations to the state tensor. Args: @@ -392,12 +435,17 @@ def apply_lightning(self, operations): else: name = ops.name invert_param = False - if name == "Identity": + if isinstance(ops, qml.Identity): continue method = getattr(state, name, None) wires = self.wires.indices(ops.wires) - if ops.name == "C(GlobalPhase)": + if isinstance(ops, Conditional): + if ops.meas_val.concretize(mid_measurements): + self.apply_lightning([ops.then_op]) + elif isinstance(ops, MidMeasureMP): + self._apply_lightning_midmeasure(ops, mid_measurements) + elif ops.name == "C(GlobalPhase)": controls = ops.control_wires control_values = ops.control_values param = ops.base.parameters[0] @@ -425,7 +473,7 @@ def apply_lightning(self, operations): method(wires, invert_param, param) # pylint: disable=unused-argument - def apply(self, operations, rotations=None, **kwargs): + def apply(self, operations, rotations=None, mid_measurements=None, **kwargs): """Applies a list of operations to the state tensor.""" # State preparation is currently done in Python if operations: # make sure operations[0] exists @@ -445,7 +493,9 @@ def apply(self, operations, rotations=None, **kwargs): + f"Operations have already been applied on a {self.short_name} device." ) - self.apply_lightning(operations) + self.apply_lightning(operations, mid_measurements=mid_measurements) + if mid_measurements is not None and any(v == -1 for v in mid_measurements.values()): + self._apply_basis_state(np.zeros(self.num_wires), wires=self.wires) # pylint: disable=protected-access def expval(self, observable, shot_range=None, bin_size=None): @@ -485,7 +535,7 @@ def expval(self, observable, shot_range=None, bin_size=None): if self.use_csingle else MeasurementsC128(self.state_vector) ) - if observable.name == "SparseHamiltonian": + if isinstance(observable, qml.SparseHamiltonian): csr_hamiltonian = observable.sparse_matrix(wire_order=self.wires).tocsr(copy=False) return measure.expval( csr_hamiltonian.indptr, @@ -494,7 +544,7 @@ def expval(self, observable, shot_range=None, bin_size=None): ) # use specialized functors to compute expval(Hermitian) - if observable.name == "Hermitian": + if isinstance(observable, qml.Hermitian): observable_wires = self.map_wires(observable.wires) matrix = observable.matrix() return measure.expval(matrix, observable_wires) @@ -528,9 +578,7 @@ def var(self, observable, shot_range=None, bin_size=None): Returns: Variance of the observable """ - if observable.name in [ - "Projector", - ]: + if isinstance(observable, qml.Projector): diagonalizing_gates = observable.diagonalizing_gates() if self.shots is None and diagonalizing_gates: self.apply(diagonalizing_gates) @@ -552,7 +600,7 @@ def var(self, observable, shot_range=None, bin_size=None): else MeasurementsC128(self.state_vector) ) - if observable.name == "SparseHamiltonian": + if isinstance(observable, qml.SparseHamiltonian): csr_hamiltonian = observable.sparse_matrix(wire_order=self.wires).tocsr(copy=False) return measure.var( csr_hamiltonian.indptr, @@ -561,7 +609,7 @@ def var(self, observable, shot_range=None, bin_size=None): ) if ( - observable.name in ["Hamiltonian", "Hermitian"] + isinstance(observable, (qml.Hamiltonian, qml.Hermitian)) or (observable.arithmetic_depth > 0) or isinstance(observable.name, List) ): @@ -575,19 +623,20 @@ def var(self, observable, shot_range=None, bin_size=None): return measure.var(observable.name, observable_wires) - def generate_samples(self): + def generate_samples(self, shots=None): """Generate samples Returns: array[int]: array of samples in binary representation with shape ``(dev.shots, dev.num_wires)`` """ + shots = self.shots if shots is None else shots measure = ( MeasurementsC64(self._kokkos_state) if self.use_csingle else MeasurementsC128(self._kokkos_state) ) - return measure.generate_samples(len(self.wires), self.shots).astype(int, copy=False) + return measure.generate_samples(len(self.wires), shots).astype(int, copy=False) def probability_lightning(self, wires): """Return the probability of each computational basis state. diff --git a/tests/test_native_mcm.py b/tests/test_native_mcm.py index 72fe6a1c76..2e6dd41e7b 100644 --- a/tests/test_native_mcm.py +++ b/tests/test_native_mcm.py @@ -22,8 +22,8 @@ from flaky import flaky from pennylane._device import DeviceError -if not LightningDevice._new_API: - pytest.skip("Exclusive tests for new device API. Skipping.", allow_module_level=True) +if device_name not in ("lightning.qubit", "lightning.kokkos"): + pytest.skip("Native MCM not supported. Skipping.", allow_module_level=True) if not LightningDevice._CPP_BINARY_AVAILABLE: # pylint: disable=protected-access pytest.skip("No binary module found. Skipping.", allow_module_level=True) @@ -79,11 +79,18 @@ def func(x, y): qml.cond(m0, qml.RY)(y, wires=1) return qml.classical_shadow(wires=0) - with pytest.raises( - DeviceError, - match=f"not accepted with finite shots on lightning.qubit", - ): - func(*params) + if device_name == "lightning.qubit": + with pytest.raises( + DeviceError, + match=f"not accepted with finite shots on lightning.qubit", + ): + func(*params) + else: + with pytest.raises( + TypeError, + match=f"Native mid-circuit measurement mode does not support ClassicalShadowMP measurements.", + ): + func(*params) @flaky(max_runs=5) @@ -287,7 +294,7 @@ def func(x): @flaky(max_runs=5) -@pytest.mark.parametrize("shots", [5000, [5000, 5001]]) +@pytest.mark.parametrize("shots", [10000, [10000, 10001]]) @pytest.mark.parametrize("postselect", [None, 0, 1]) @pytest.mark.parametrize("reset", [False, True]) @pytest.mark.parametrize("measure_f", [qml.counts, qml.expval, qml.probs, qml.sample, qml.var])