diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 5f5c6a1f7c..ae007ca3bc 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -2,6 +2,9 @@ ### New features since last release +* Add MCMC sampler. +[(#384)] (https://github.com/PennyLaneAI/pennylane-lightning/pull/384) + ### Breaking changes ### Improvements @@ -68,7 +71,7 @@ Amintor Dusko, Vincent Michaud-Rioux, Lee James O'Riordan, Chae-Yeun Park This release contains contributions from (in alphabetical order): -Amintor Dusko +Amintor Dusko, Shuli Shu, Trevor Vincent --- diff --git a/doc/devices.rst b/doc/devices.rst index 253f17b4cb..10b6aab1c7 100644 --- a/doc/devices.rst +++ b/doc/devices.rst @@ -98,3 +98,26 @@ If you are computing a large number of expectation values, or if you are using a os.environ["OMP_NUM_THREADS"] = 4 import pennylane as qml dev = qml.device("lightning.qubit", wires=2, batch_obs=True) + +.. raw:: html + + + +**Markov Chain Monte Carlo sampling support:** + +The ``lightning.qubit`` device allows users to use the Markov Chain Monte Carlo (MCMC) sampling method to generate approximate samples. To enable the MCMC sampling method for sample generation, initialize a ``lightning.qubit`` device with the ``mcmc=True`` keyword argument, as: + +.. code-block:: python + + import pennylane as qml + dev = qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True) + +By default, the ``kernel_name`` is ``"Local"`` and ``num_burnin`` is ``100``. 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 ``lightning.qubit`` device also supports a ``"NonZeroRandom"`` kernel. This kernel randomly transits between states that have nonzero probability. It can be enabled by initializing the device as: + +.. code-block:: python + + import pennylane as qml + dev = qml.device("lightning.qubit", wires=2, shots=1000, mcmc=True, kernel_name="NonZeroRandom", num_burnin=200) + diff --git a/pennylane_lightning/_version.py b/pennylane_lightning/_version.py index 2af226f590..ae308c22f6 100644 --- a/pennylane_lightning/_version.py +++ b/pennylane_lightning/_version.py @@ -16,4 +16,4 @@ Version number (major.minor.patch[-label]) """ -__version__ = "0.30.0-dev" +__version__ = "0.30.0-dev1" diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 4f72bd77f4..1dfe776e95 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -160,6 +160,13 @@ class LightningQubit(QubitDevice): the expectation values. Defaults to ``None`` if not specified. Setting to ``None`` results in computing statistics like expectation values and variances analytically. + 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 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 + result in a closer approximation but increased runtime. batch_obs (bool): Determine whether we process observables parallelly when computing the jacobian. This value is only relevant when the lightning qubit is built with OpenMP. """ @@ -173,7 +180,18 @@ class LightningQubit(QubitDevice): operations = allowed_operations observables = allowed_observables - def __init__(self, wires, *, c_dtype=np.complex128, shots=None, batch_obs=False, analytic=None): + def __init__( + self, + wires, + *, + c_dtype=np.complex128, + shots=None, + mcmc=False, + kernel_name="Local", + num_burnin=100, + batch_obs=False, + analytic=None, + ): if c_dtype is np.complex64: r_dtype = np.float32 self.use_csingle = True @@ -190,6 +208,20 @@ def __init__(self, wires, *, c_dtype=np.complex128, shots=None, batch_obs=False, self._state = self._create_basis_state(0) self._pre_rotated_state = self._state + self._mcmc = mcmc + if self._mcmc: + if kernel_name not in [ + "Local", + "NonZeroRandom", + ]: + raise NotImplementedError( + f"The {kernel_name} is not supported and currently only 'Local' and 'NonZeroRandom' kernels are supported." + ) + if num_burnin >= shots: + raise ValueError("Shots should be greater than num_burnin.") + self._kernel_name = kernel_name + self._num_burnin = num_burnin + @property def stopping_condition(self): """.BooleanFn: Returns the stopping condition for the device. The returned @@ -776,14 +808,17 @@ def generate_samples(self): Returns: array[int]: array of samples in binary representation with shape ``(dev.shots, dev.num_wires)`` """ - # Initialization of state ket = np.ravel(self._state) state_vector = StateVectorC64(ket) if self.use_csingle else StateVectorC128(ket) M = MeasuresC64(state_vector) if self.use_csingle else MeasuresC128(state_vector) - - return M.generate_samples(len(self.wires), self.shots).astype(int, copy=False) + if self._mcmc: + return M.generate_mcmc_samples( + len(self.wires), self._kernel_name, self._num_burnin, self.shots + ).astype(int, copy=False) + else: + return M.generate_samples(len(self.wires), self.shots).astype(int, copy=False) def expval(self, observable, shot_range=None, bin_size=None): """Expectation value of the supplied observable. diff --git a/pennylane_lightning/src/bindings/Bindings.cpp b/pennylane_lightning/src/bindings/Bindings.cpp index a664ed0bf6..13e3a60477 100644 --- a/pennylane_lightning/src/bindings/Bindings.cpp +++ b/pennylane_lightning/src/bindings/Bindings.cpp @@ -135,6 +135,27 @@ void lightning_class_bindings(py::module_ &m) { strides /* strides for each axis */ )); }) + .def("generate_mcmc_samples", + [](Measures &M, size_t num_wires, + const std::string &kernelname, size_t num_burnin, + size_t num_shots) { + std::vector &&result = M.generate_samples_metropolis( + kernelname, num_burnin, num_shots); + + const size_t ndim = 2; + const std::vector shape{num_shots, num_wires}; + constexpr auto sz = sizeof(size_t); + const std::vector strides{sz * num_wires, sz}; + // return 2-D NumPy array + return py::array(py::buffer_info( + result.data(), /* data as contiguous array */ + sz, /* size of one scalar */ + py::format_descriptor::format(), /* data type */ + ndim, /* number of dimensions */ + shape, /* shape of the matrix */ + strides /* strides for each axis */ + )); + }) .def("var", [](Measures &M, const std::string &operation, const std::vector &wires) { diff --git a/pennylane_lightning/src/simulator/CMakeLists.txt b/pennylane_lightning/src/simulator/CMakeLists.txt index b0659641e8..9aab1d2f55 100644 --- a/pennylane_lightning/src/simulator/CMakeLists.txt +++ b/pennylane_lightning/src/simulator/CMakeLists.txt @@ -1,6 +1,6 @@ project(lightning_simulator) -set(SIMULATOR_FILES StateVectorRawCPU.cpp Observables.cpp StateVectorManagedCPU.cpp Measures.cpp CACHE INTERNAL "" FORCE) +set(SIMULATOR_FILES StateVectorRawCPU.cpp Observables.cpp StateVectorManagedCPU.cpp TransitionKernels.cpp Measures.cpp CACHE INTERNAL "" FORCE) add_library(lightning_simulator STATIC ${SIMULATOR_FILES}) diff --git a/pennylane_lightning/src/simulator/Measures.hpp b/pennylane_lightning/src/simulator/Measures.hpp index c0bf75fad5..07b93f9e95 100644 --- a/pennylane_lightning/src/simulator/Measures.hpp +++ b/pennylane_lightning/src/simulator/Measures.hpp @@ -33,6 +33,7 @@ #include "Observables.hpp" #include "StateVectorManagedCPU.hpp" #include "StateVectorRawCPU.hpp" +#include "TransitionKernels.hpp" namespace Pennylane::Simulators { @@ -331,6 +332,106 @@ class Measures { return expected_value_list; }; + /** + * @brief Complete a single Metropolis-Hastings step. + * + * @param sv state vector + * @param tk User-defined functor for producing transitions + * between metropolis states. + * @param gen Random number generator. + * @param distrib Random number distribution. + * @param init_idx Init index of basis state. + */ + size_t metropolis_step(const SVType &sv, + const std::unique_ptr> &tk, + std::mt19937 &gen, + std::uniform_real_distribution &distrib, + size_t init_idx) { + auto init_plog = std::log( + (sv.getData()[init_idx] * std::conj(sv.getData()[init_idx])) + .real()); + + auto init_qratio = tk->operator()(init_idx); + + // transition kernel outputs these two + auto &trans_idx = init_qratio.first; + auto &trans_qratio = init_qratio.second; + + auto trans_plog = std::log( + (sv.getData()[trans_idx] * std::conj(sv.getData()[trans_idx])) + .real()); + + auto alph = + std::min(1., trans_qratio * std::exp(trans_plog - init_plog)); + auto ran = distrib(gen); + + if (ran < alph) { + return trans_idx; + } + return init_idx; + } + + /** + * @brief Generate samples using the Metropolis-Hastings method. + * Reference: Numerical Recipes, NetKet paper + * + * @param transition_kernel User-defined functor for producing transitions + * between metropolis states. + * @param num_burnin Number of Metropolis burn-in steps. + * @param num_samples The number of samples to generate. + * @return 1-D vector of samples in binary, each sample is + * separated by a stride equal to the number of qubits. + */ + std::vector + generate_samples_metropolis(const std::string &kernelname, + size_t num_burnin, size_t num_samples) { + size_t num_qubits = original_statevector.getNumQubits(); + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution distrib(0.0, 1.0); + std::vector samples(num_samples * num_qubits, 0); + std::unordered_map cache; + + TransitionKernelType transition_kernel = + Pennylane::TransitionKernelType::Local; + if (kernelname == "NonZeroRandom") { + transition_kernel = Pennylane::TransitionKernelType::NonZeroRandom; + } + + auto tk = + kernel_factory(transition_kernel, original_statevector.getData(), + original_statevector.getNumQubits()); + size_t idx = 0; + + // Burn In + for (size_t i = 0; i < num_burnin; i++) { + idx = metropolis_step(original_statevector, tk, gen, distrib, + idx); // Burn-in. + } + + // Sample + for (size_t i = 0; i < num_samples; i++) { + idx = metropolis_step(original_statevector, tk, gen, distrib, idx); + + if (cache.contains(idx)) { + size_t cache_id = cache[idx]; + auto it_temp = samples.begin() + cache_id * num_qubits; + std::copy(it_temp, it_temp + num_qubits, + samples.begin() + i * num_qubits); + } + + // If not cached, compute + else { + for (size_t j = 0; j < num_qubits; j++) { + samples[i * num_qubits + (num_qubits - 1 - j)] = + (idx >> j) & 1U; + } + cache[idx] = i; + } + } + return samples; + } + /** * @brief Variance of a Sparse Hamiltonian. * diff --git a/pennylane_lightning/src/simulator/TransitionKernels.cpp b/pennylane_lightning/src/simulator/TransitionKernels.cpp new file mode 100644 index 0000000000..d426e367c3 --- /dev/null +++ b/pennylane_lightning/src/simulator/TransitionKernels.cpp @@ -0,0 +1,9 @@ +#include "TransitionKernels.hpp" + +// explicit instantiation +template class Pennylane::TransitionKernel; +template class Pennylane::TransitionKernel; +template class Pennylane::LocalTransitionKernel; +template class Pennylane::LocalTransitionKernel; +template class Pennylane::NonZeroRandomTransitionKernel; +template class Pennylane::NonZeroRandomTransitionKernel; diff --git a/pennylane_lightning/src/simulator/TransitionKernels.hpp b/pennylane_lightning/src/simulator/TransitionKernels.hpp new file mode 100644 index 0000000000..926e114086 --- /dev/null +++ b/pennylane_lightning/src/simulator/TransitionKernels.hpp @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "StateVectorManagedCPU.hpp" +#include "StateVectorRawCPU.hpp" + +namespace Pennylane { + +enum class TransitionKernelType { Local, NonZeroRandom }; + +/** + * @brief Parent class to define interface for Transition Kernel + * + * @tparam fp_t Floating point precision of underlying measurements. + */ +template class TransitionKernel { + protected: + TransitionKernel() = default; + TransitionKernel(const TransitionKernel &) = default; + TransitionKernel(TransitionKernel &&) noexcept = default; + TransitionKernel &operator=(const TransitionKernel &) = default; + TransitionKernel &operator=(TransitionKernel &&) noexcept = default; + + public: + // outputs the next state and the qratio + virtual std::pair operator()(size_t) = 0; + virtual ~TransitionKernel() = default; +}; + +/** + * @brief Transition Kernel for a 'SpinFlip' local transition between states + * + * This class implements a local transition kernel for a spin flip operation. + * It goes about this by generating a random qubit site and then generating + * a random number to determine the new bit at that qubit site. + * @tparam fp_t Floating point precision of underlying measurements. + */ +template +class LocalTransitionKernel : public TransitionKernel { + private: + size_t num_qubits_; + std::random_device rd_; + std::mt19937 gen_; + std::uniform_int_distribution distrib_num_qubits_; + std::uniform_int_distribution distrib_binary_; + + public: + explicit LocalTransitionKernel(size_t num_qubits) + : num_qubits_(num_qubits), gen_(std::mt19937(rd_())), + distrib_num_qubits_( + std::uniform_int_distribution(0, num_qubits - 1)), + distrib_binary_(std::uniform_int_distribution(0, 1)) {} + + std::pair operator()(size_t init_idx) final { + size_t qubit_site = distrib_num_qubits_(gen_); + size_t qubit_value = distrib_binary_(gen_); + size_t current_bit = (static_cast(init_idx) >> + static_cast(qubit_site)) & + 1U; + + if (qubit_value == current_bit) { + return std::pair(init_idx, 1); + } + if (current_bit == 0) { + return std::pair(init_idx + std::pow(2, qubit_site), + 1); + } + return std::pair(init_idx - std::pow(2, qubit_site), 1); + } +}; + +/** + * @brief Transition Kernel for a random transition between non-zero states + * + * This class randomly transitions between states that have nonzero probability. + * To determine the states with non-zero probability we have O(2^num_qubits) + * overhead. Despite this, this method is still fast. This transition kernel + * can sample even GHZ states. + */ +template +class NonZeroRandomTransitionKernel : public TransitionKernel { + private: + std::random_device rd_; + std::mt19937 gen_; + std::uniform_int_distribution distrib_; + size_t sv_length_; + std::vector non_zeros_; + + public: + NonZeroRandomTransitionKernel(const std::complex *sv, + size_t sv_length, fp_t min_error) { + auto data = sv; + sv_length_ = sv_length; + // find nonzero candidates + for (size_t i = 0; i < sv_length_; i++) { + if (std::abs(data[i]) > min_error) { + non_zeros_.push_back(i); + } + } + gen_ = std::mt19937(rd_()); + distrib_ = + std::uniform_int_distribution(0, non_zeros_.size() - 1); + } + std::pair operator()([[maybe_unused]] size_t init_idx) final { + auto trans_idx = distrib_(gen_); + return std::pair(non_zeros_[trans_idx], 1); + } +}; + +/** + * @brief Factory function to create a transition kernel + * + * @param kernel_type Type of transition kernel to create + * @param sv pointer to the statevector data + * @param num_qubits number of qubits + * @tparam fp_t Floating point precision of underlying measurements. + * @return std::unique_ptr of the transition kernel + */ +template +std::unique_ptr> +kernel_factory(const TransitionKernelType kernel_type, + const std::complex *sv, size_t num_qubits) { + auto sv_length = Util::exp2(num_qubits); + if (kernel_type == TransitionKernelType::Local) { + return std::unique_ptr>( + new NonZeroRandomTransitionKernel( + sv, sv_length, std::numeric_limits::epsilon())); + } + return std::unique_ptr>( + new LocalTransitionKernel(num_qubits)); +} +} // namespace Pennylane diff --git a/pennylane_lightning/src/tests/TestHelpers.hpp b/pennylane_lightning/src/tests/TestHelpers.hpp index acdf740705..d381b06f99 100644 --- a/pennylane_lightning/src/tests/TestHelpers.hpp +++ b/pennylane_lightning/src/tests/TestHelpers.hpp @@ -369,8 +369,8 @@ template StateVectorManagedCPU Initializing_StateVector(size_t num_qubits = 3) { size_t data_size = Util::exp2(num_qubits); - std::vector> arr(data_size, 0); - arr[0] = 1; + std::vector> arr(data_size, {0, 0}); + arr[0] = {1, 0}; StateVectorManagedCPU Measured_StateVector(arr.data(), data_size); std::vector gates; diff --git a/pennylane_lightning/src/tests/Test_Measures.cpp b/pennylane_lightning/src/tests/Test_Measures.cpp index eeef76bcd2..4a4cc5307f 100644 --- a/pennylane_lightning/src/tests/Test_Measures.cpp +++ b/pennylane_lightning/src/tests/Test_Measures.cpp @@ -196,6 +196,120 @@ TEMPLATE_TEST_CASE("Sample", "[Measures]", float, double) { } } +TEMPLATE_TEST_CASE("Sample with Metropolis (Local Kernel)", "[Measures]", float, + double) { + constexpr uint32_t twos[] = { + 1U << 0U, 1U << 1U, 1U << 2U, 1U << 3U, 1U << 4U, 1U << 5U, + 1U << 6U, 1U << 7U, 1U << 8U, 1U << 9U, 1U << 10U, 1U << 11U, + 1U << 12U, 1U << 13U, 1U << 14U, 1U << 15U, 1U << 16U, 1U << 17U, + 1U << 18U, 1U << 19U, 1U << 20U, 1U << 21U, 1U << 22U, 1U << 23U, + 1U << 24U, 1U << 25U, 1U << 26U, 1U << 27U, 1U << 28U, 1U << 29U, + 1U << 30U, 1U << 31U}; + + // Defining the State Vector that will be measured. + StateVectorManagedCPU Measured_StateVector = + Initializing_StateVector(); + + // Initializing the measures class. + // This object attaches to the statevector allowing several measures. + Measures> Measurer( + Measured_StateVector); + vector expected_probabilities = { + 0.67078706, 0.03062806, 0.0870997, 0.00397696, + 0.17564072, 0.00801973, 0.02280642, 0.00104134}; + + size_t num_qubits = 3; + size_t N = std::pow(2, num_qubits); + size_t num_samples = 100000; + size_t num_burnin = 1000; + + std::string kernel = "Local"; + auto &&samples = + Measurer.generate_samples_metropolis(kernel, num_burnin, num_samples); + + std::vector counts(N, 0); + std::vector samples_decimal(num_samples, 0); + + // convert samples to decimal and then bin them in counts + for (size_t i = 0; i < num_samples; i++) { + for (size_t j = 0; j < num_qubits; j++) { + if (samples[i * num_qubits + j] != 0) { + samples_decimal[i] += twos[(num_qubits - 1 - j)]; + } + } + counts[samples_decimal[i]] += 1; + } + + // compute estimated probabilities from histogram + std::vector probabilities(counts.size()); + for (size_t i = 0; i < counts.size(); i++) { + probabilities[i] = counts[i] / (TestType)num_samples; + } + + // compare estimated probabilities to real probabilities + SECTION("No wires provided:") { + REQUIRE_THAT(probabilities, + Catch::Approx(expected_probabilities).margin(.05)); + } +} + +TEMPLATE_TEST_CASE("Sample with Metropolis (NonZeroRandom Kernel)", + "[Measures]", float, double) { + constexpr uint32_t twos[] = { + 1U << 0U, 1U << 1U, 1U << 2U, 1U << 3U, 1U << 4U, 1U << 5U, + 1U << 6U, 1U << 7U, 1U << 8U, 1U << 9U, 1U << 10U, 1U << 11U, + 1U << 12U, 1U << 13U, 1U << 14U, 1U << 15U, 1U << 16U, 1U << 17U, + 1U << 18U, 1U << 19U, 1U << 20U, 1U << 21U, 1U << 22U, 1U << 23U, + 1U << 24U, 1U << 25U, 1U << 26U, 1U << 27U, 1U << 28U, 1U << 29U, + 1U << 30U, 1U << 31U}; + + // Defining the State Vector that will be measured. + StateVectorManagedCPU Measured_StateVector = + Initializing_StateVector(); + + // Initializing the measures class. + // This object attaches to the statevector allowing several measures. + Measures> Measurer( + Measured_StateVector); + vector expected_probabilities = { + 0.67078706, 0.03062806, 0.0870997, 0.00397696, + 0.17564072, 0.00801973, 0.02280642, 0.00104134}; + + size_t num_qubits = 3; + size_t N = std::pow(2, num_qubits); + size_t num_samples = 100000; + size_t num_burnin = 1000; + + const std::string kernel = "NonZeroRandom"; + auto &&samples = + Measurer.generate_samples_metropolis(kernel, num_burnin, num_samples); + + std::vector counts(N, 0); + std::vector samples_decimal(num_samples, 0); + + // convert samples to decimal and then bin them in counts + for (size_t i = 0; i < num_samples; i++) { + for (size_t j = 0; j < num_qubits; j++) { + if (samples[i * num_qubits + j] != 0) { + samples_decimal[i] += twos[(num_qubits - 1 - j)]; + } + } + counts[samples_decimal[i]] += 1; + } + + // compute estimated probabilities from histogram + std::vector probabilities(counts.size()); + for (size_t i = 0; i < counts.size(); i++) { + probabilities[i] = counts[i] / (TestType)num_samples; + } + + // compare estimated probabilities to real probabilities + SECTION("No wires provided:") { + REQUIRE_THAT(probabilities, + Catch::Approx(expected_probabilities).margin(.05)); + } +} + TEMPLATE_TEST_CASE("Variances", "[Measures]", float, double) { // Defining the State Vector that will be measured. StateVectorManagedCPU Measured_StateVector = diff --git a/tests/test_measures.py b/tests/test_measures.py index 16108d6d06..504c7adcec 100644 --- a/tests/test_measures.py +++ b/tests/test_measures.py @@ -611,6 +611,79 @@ def test_sample_values(self, qubit_device, tol): assert np.allclose(s1**2, 1, atol=tol, rtol=0) +class TestMCMCSample: + """Tests that samples are properly calculated.""" + + @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) + + test_data_no_parameters = [ + (10, 100, [0], qml.PauliZ(wires=[0]), 100), + (10, 110, [1], qml.PauliZ(wires=[1]), 110), + (10, 120, [0, 1], qml.PauliX(0) @ qml.PauliZ(1), 120), + ] + + @pytest.mark.parametrize("kernel", ["Local", "NonZeroRandom"]) + @pytest.mark.parametrize( + "num_burnin,num_shots,measured_wires,operation,shape", test_data_no_parameters + ) + def test_mcmc_sample_dimensions( + self, dev, kernel, num_burnin, num_shots, measured_wires, operation, shape + ): + """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) + + assert np.array_equal(s1.shape, (shape,)) + + @pytest.mark.parametrize("kernel", ["Local", "NonZeroRandom"]) + def test_sample_values(self, tol, kernel): + """Tests if the samples returned by sample have + the correct values + """ + dev = qml.device( + "lightning.qubit", 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)) + + # s1 should only contain 1 and -1, which is guaranteed if + # they square to 1 + assert np.allclose(s1**2, 1, atol=tol, rtol=0) + + @pytest.mark.parametrize("kernel", ["local", "nonZeroRandom", "Global", "global"]) + def test_unsupported_sample_kernels(self, tol, kernel): + """Tests if the samples returned by sample have + the correct values + """ + with pytest.raises( + NotImplementedError, + match=f"The {kernel} is not supported and currently only 'Local' and 'NonZeroRandom' kernels are supported.", + ): + dev = qml.device( + "lightning.qubit", + wires=2, + shots=1000, + mcmc=True, + kernel_name=kernel, + num_burnin=100, + ) + + 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) + + class TestWiresInVar: """Test different Wires settings in Lightning's var."""