diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 2e14f6dc0..863ee5d42 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -40,6 +40,9 @@ ### Improvements +* The `generate_samples` methods of lightning.{qubit/kokkos} can now take in a seed number to make the generated samples deterministic. This can be useful when, among other things, fixing flaky tests in CI. + [(#927)](https://github.com/PennyLaneAI/pennylane-lightning/pull/927) + * Always decompose `qml.QFT` in Lightning. [(#924)](https://github.com/PennyLaneAI/pennylane-lightning/pull/924) @@ -111,7 +114,7 @@ This release contains contributions from (in alphabetical order): -Ali Asadi, Amintor Dusko, Luis Alfredo Nuñez Meneses, Vincent Michaud-Rioux, Lee J. O'Riordan, Mudit Pandey, Shuli Shu +Ali Asadi, Amintor Dusko, Luis Alfredo Nuñez Meneses, Vincent Michaud-Rioux, Lee J. O'Riordan, Mudit Pandey, Shuli Shu, Haochen Paul Wang --- diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index eb1de07d4..df5a1de73 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.39.0-dev37" +__version__ = "0.39.0-dev38" diff --git a/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp b/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp index 50a76610d..62fd82e1a 100644 --- a/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp +++ b/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp @@ -77,7 +77,6 @@ template class MeasurementsBase { /** * @brief Randomly set the seed of the internal random generator * - * @param seed Seed */ void setRandomSeed() { std::random_device rd; diff --git a/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp b/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp index 674659a9c..0cd7223bf 100644 --- a/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp +++ b/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp @@ -20,6 +20,7 @@ using Pennylane::Util::isApproxEqual; } // namespace /// @endcond #include +#include #include #ifdef _ENABLE_PLQUBIT @@ -1251,7 +1252,9 @@ TEST_CASE("Var Shot- TensorProdObs", "[MeasurementsBase][Observables]") { testTensorProdObsVarShot(); } } -template void testSamples() { + +template +void testSamples(const std::optional &seed = std::nullopt) { if constexpr (!std::is_same_v) { using StateVectorT = typename TypeList::Type; using PrecisionT = typename StateVectorT::PrecisionT; @@ -1281,7 +1284,10 @@ template void testSamples() { std::size_t num_qubits = 3; std::size_t N = std::pow(2, num_qubits); std::size_t num_samples = 100000; - auto &&samples = Measurer.generate_samples(num_samples); + auto &&samples = + seed.has_value() + ? Measurer.generate_samples(num_samples, seed.value()) + : Measurer.generate_samples(num_samples); std::vector counts(N, 0); std::vector samples_decimal(num_samples, 0); @@ -1307,7 +1313,7 @@ template void testSamples() { REQUIRE_THAT(probabilities, Catch::Approx(expected_probabilities).margin(.05)); } - testSamples(); + testSamples(seed); } } @@ -1317,6 +1323,12 @@ TEST_CASE("Samples", "[MeasurementsBase]") { } } +TEST_CASE("Seeded samples", "[MeasurementsBase]") { + if constexpr (BACKEND_FOUND) { + testSamples(37); + } +} + template void testSamplesCountsObs() { if constexpr (!std::is_same_v) { using StateVectorT = typename TypeList::Type; @@ -1729,4 +1741,4 @@ TEST_CASE("Measure Shot - SparseHObs ", "[MeasurementsBase][Observables]") { if constexpr (BACKEND_FOUND) { testSparseHObsMeasureShot(); } -} \ No newline at end of file +} diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp index fe19b5d02..f2da5d03e 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp @@ -25,6 +25,7 @@ #include #include #include // custatevecApplyMatrix +#include #include #include #include @@ -218,7 +219,9 @@ class Measurements final * be accessed using the stride sample_id*num_qubits, where sample_id is a * number between 0 and num_samples-1. */ - auto generate_samples(std::size_t num_samples) -> std::vector { + auto generate_samples(std::size_t num_samples, + const std::optional &seed = std::nullopt) + -> std::vector { std::vector rand_nums(num_samples); custatevecSamplerDescriptor_t sampler; @@ -238,7 +241,11 @@ class Measurements final data_type = CUDA_C_32F; } - this->setRandomSeed(); + if (seed.has_value()) { + this->setSeed(seed.value()); + } else { + this->setRandomSeed(); + } std::uniform_real_distribution dis(0.0, 1.0); for (std::size_t n = 0; n < num_samples; n++) { rand_nums[n] = dis(this->rng); diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp index 655b305a5..cfe3a9c5d 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp @@ -342,13 +342,22 @@ void LightningKokkosSimulator::PartialProbs( std::move(dv_probs.begin(), dv_probs.end(), probs.begin()); } -void LightningKokkosSimulator::Sample(DataView &samples, - std::size_t shots) { +std::vector LightningKokkosSimulator::GenerateSamples(size_t shots) { + // generate_samples is a member function of the Measures class. Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + // PL-Lightning-Kokkos generates samples using the alias method. // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling - auto li_samples = m.generate_samples(shots); + if (this->gen) { + return m.generate_samples(shots, (*(this->gen))()); + } + return m.generate_samples(shots); +} + +void LightningKokkosSimulator::Sample(DataView &samples, + std::size_t shots) { + auto li_samples = this->GenerateSamples(shots); RT_FAIL_IF(samples.size() != li_samples.size(), "Invalid size for the pre-allocated samples"); @@ -381,13 +390,7 @@ void LightningKokkosSimulator::PartialSample( // get device wires auto &&dev_wires = getDeviceWires(wires); - // generate_samples is a member function of the MeasuresKokkos class. - Pennylane::LightningKokkos::Measures::Measurements m{ - *(this->device_sv)}; - - // PL-Lightning-Kokkos generates samples using the alias method. - // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling - auto li_samples = m.generate_samples(shots); + auto li_samples = this->GenerateSamples(shots); // The lightning samples are layed out as a single vector of size // shots*qubits, where each element represents a single bit. The @@ -411,13 +414,7 @@ void LightningKokkosSimulator::Counts(DataView &eigvals, RT_FAIL_IF(eigvals.size() != numElements || counts.size() != numElements, "Invalid size for the pre-allocated counts"); - // generate_samples is a member function of the MeasuresKokkos class. - Pennylane::LightningKokkos::Measures::Measurements m{ - *(this->device_sv)}; - - // PL-Lightning-Kokkos generates samples using the alias method. - // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling - auto li_samples = m.generate_samples(shots); + auto li_samples = this->GenerateSamples(shots); // Fill the eigenvalues with the integer representation of the corresponding // computational basis bitstring. In the future, eigenvalues can also be @@ -455,13 +452,7 @@ void LightningKokkosSimulator::PartialCounts( // get device wires auto &&dev_wires = getDeviceWires(wires); - // generate_samples is a member function of the MeasuresKokkos class. - Pennylane::LightningKokkos::Measures::Measurements m{ - *(this->device_sv)}; - - // PL-Lightning-Kokkos generates samples using the alias method. - // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling - auto li_samples = m.generate_samples(shots); + auto li_samples = this->GenerateSamples(shots); // Fill the eigenvalues with the integer representation of the corresponding // computational basis bitstring. In the future, eigenvalues can also be diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp index 890c3a267..d28959f7c 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp @@ -96,6 +96,8 @@ class LightningKokkosSimulator final : public Catalyst::Runtime::QuantumDevice { return res; } + auto GenerateSamples(size_t shots) -> std::vector; + public: explicit LightningKokkosSimulator(const std::string &kwargs = "{}") { auto &&args = Catalyst::Runtime::parse_kwargs(kwargs); diff --git a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp index 7208732a3..d32e6100e 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/tests/Test_LightningKokkosMeasures.cpp @@ -1754,26 +1754,71 @@ TEST_CASE("Counts and PartialCounts tests with numWires=0-4 shots=100", } TEST_CASE("Measurement with a seeded device", "[Measures]") { - for (std::size_t _ = 0; _ < 5; _++) { - std::unique_ptr sim = std::make_unique(); - std::unique_ptr sim1 = std::make_unique(); + std::array, 2> sims; + std::vector gens{std::mt19937{37}, std::mt19937{37}}; - std::mt19937 gen(37); - sim->SetDevicePRNG(&gen); + auto circuit = [](LKSimulator &sim, std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); std::vector Qs; Qs.reserve(1); - Qs.push_back(sim->AllocateQubit()); - sim->NamedOperation("Hadamard", {}, {Qs[0]}, false); - auto m = sim->Measure(Qs[0]); - - std::mt19937 gen1(37); - sim1->SetDevicePRNG(&gen1); - std::vector Qs1; - Qs1.reserve(1); - Qs1.push_back(sim1->AllocateQubit()); - sim1->NamedOperation("Hadamard", {}, {Qs1[0]}, false); - auto m1 = sim1->Measure(Qs1[0]); - - CHECK(*m == *m1); + Qs.push_back(sim.AllocateQubit()); + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + auto m = sim.Measure(Qs[0]); + return m; + }; + + for (std::size_t trial = 0; trial < 5; trial++) { + sims[0] = std::make_unique(); + sims[1] = std::make_unique(); + + auto m0 = circuit(*(sims[0]), gens[0]); + auto m1 = circuit(*(sims[1]), gens[1]); + + CHECK(*m0 == *m1); + } +} + +TEST_CASE("Sample with a seeded device", "[Measures]") { + std::size_t shots = 100; + std::array, 2> sims; + std::vector> sample_vec(2, + std::vector(shots * 4)); + + std::vector> buffers{ + MemRefT{ + sample_vec[0].data(), sample_vec[0].data(), 0, {shots, 1}, {1, 1}}, + MemRefT{ + sample_vec[1].data(), sample_vec[1].data(), 0, {shots, 1}, {1, 1}}, + }; + std::vector> views{ + DataView(buffers[0].data_aligned, buffers[0].offset, + buffers[0].sizes, buffers[0].strides), + DataView(buffers[1].data_aligned, buffers[1].offset, + buffers[1].sizes, buffers[1].strides)}; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [shots](LKSimulator &sim, DataView &view, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + std::vector Qs; + Qs.reserve(1); + Qs.push_back(sim.AllocateQubit()); + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("RX", {0.5}, {Qs[0]}, false); + sim.Sample(view, shots); + }; + + for (std::size_t trial = 0; trial < 5; trial++) { + sims[0] = std::make_unique(); + sims[1] = std::make_unique(); + + for (std::size_t sim_idx = 0; sim_idx < sims.size(); sim_idx++) { + circuit(*(sims[sim_idx]), views[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < sample_vec[0].size(); i++) { + CHECK((sample_vec[0][i] == sample_vec[1][i])); + } } } 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 28449e501..ee8684e81 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp @@ -14,6 +14,7 @@ #pragma once #include #include +#include #include #include @@ -649,13 +650,16 @@ class Measurements final * Reference https://en.wikipedia.org/wiki/Inverse_transform_sampling * * @param num_samples Number of Samples + * @param seed Seed to generate the samples from * * @return std::vector to the samples. * Each sample has a length equal to the number of qubits. Each sample can * be accessed using the stride sample_id*num_qubits, where sample_id is a * number between 0 and num_samples-1. */ - auto generate_samples(std::size_t num_samples) -> std::vector { + auto generate_samples(std::size_t num_samples, + const std::optional &seed = std::nullopt) + -> std::vector { const std::size_t num_qubits = this->_statevector.getNumQubits(); const std::size_t N = this->_statevector.getLength(); Kokkos::View samples("num_samples", @@ -674,10 +678,12 @@ class Measurements final }); // Sampling using Random_XorShift64_Pool - Kokkos::Random_XorShift64_Pool<> rand_pool( - std::chrono::high_resolution_clock::now() - .time_since_epoch() - .count()); + auto rand_pool = seed.has_value() + ? Kokkos::Random_XorShift64_Pool<>(seed.value()) + : Kokkos::Random_XorShift64_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_qubit/measurements/MeasurementsLQubit.hpp b/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp index 4bf72e332..d57bd7063 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -573,14 +574,17 @@ class Measurements final * Reference: https://en.wikipedia.org/wiki/Alias_method * * @param num_samples The number of samples to generate. + * @param seed Seed to generate the samples from * @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(const std::size_t num_samples) { + std::vector + generate_samples(const std::size_t num_samples, + const std::optional &seed = std::nullopt) { const std::size_t num_qubits = this->_statevector.getNumQubits(); std::vector wires(num_qubits); std::iota(wires.begin(), wires.end(), 0); - return generate_samples(wires, num_samples); + return generate_samples(wires, num_samples, seed); } /** @@ -588,15 +592,21 @@ class Measurements final * * @param wires Sample are generated for the specified wires. * @param num_samples The number of samples to generate. + * @param seed Seed to generate the samples from * @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(const std::vector &wires, - const std::size_t num_samples) { + const std::size_t num_samples, + const std::optional &seed = std::nullopt) { const std::size_t n_wires = wires.size(); std::vector samples(num_samples * n_wires); - this->setRandomSeed(); + if (seed.has_value()) { + this->setSeed(seed.value()); + } else { + this->setRandomSeed(); + } DiscreteRandomVariable drv{this->rng, probs(wires)}; // The Python layer expects a 2D array with dimensions (n_samples x // n_wires) and hence the linear index is `s * n_wires + (n_wires - 1 -