diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 29471bd59d..c6a668a442 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -47,6 +47,9 @@ * Update the `lightning.kokkos` CUDA backend for compatibility with Catalyst. [(#942)](https://github.com/PennyLaneAI/pennylane-lightning/pull/942) +* Generalize seeding mechanism for all measurements. + [(#1003)](https://github.com/PennyLaneAI/pennylane-lightning/pull/1003) + ### Documentation * Update Lightning-Tensor installation docs and usage suggestions. diff --git a/pennylane_lightning/core/_version.py b/pennylane_lightning/core/_version.py index 4830359ac4..17c98c7d19 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.40.0-dev23" +__version__ = "0.40.0-dev24" diff --git a/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp b/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp index 62fd82e1ab..6aaec8d676 100644 --- a/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp +++ b/pennylane_lightning/core/src/measurements/MeasurementsBase.hpp @@ -17,6 +17,7 @@ */ #pragma once +#include #include #include #include @@ -56,7 +57,9 @@ template class MeasurementsBase { #else const StateVectorT &_statevector; #endif - std::mt19937 rng; + std::optional _deviceseed{std::nullopt}; + + std::mt19937 _rng; public: #ifdef _ENABLE_PLGPU @@ -72,7 +75,14 @@ template class MeasurementsBase { * * @param seed Seed */ - void setSeed(const std::size_t seed) { rng.seed(seed); } + void setSeed(const std::optional &seed = std::nullopt) { + if (seed.has_value()) { + _rng.seed(seed.value()); + this->_deviceseed = seed; + } else { + setRandomSeed(); + } + } /** * @brief Randomly set the seed of the internal random generator @@ -80,7 +90,7 @@ template class MeasurementsBase { */ void setRandomSeed() { std::random_device rd; - setSeed(rd()); + _rng.seed(rd()); } /** @@ -288,6 +298,7 @@ template class MeasurementsBase { sv.updateData(data_storage.data(), data_storage.size()); obs.applyInPlaceShots(sv, eigenvalues, obs_wires); Derived measure(sv); + measure.setSeed(this->_deviceseed); if (num_shots > std::size_t{0}) { return measure.probs(obs_wires, num_shots); } @@ -296,6 +307,7 @@ template class MeasurementsBase { StateVectorT sv(_statevector); obs.applyInPlaceShots(sv, eigenvalues, obs_wires); Derived measure(sv); + measure.setSeed(this->_deviceseed); if (num_shots > std::size_t{0}) { return measure.probs(obs_wires, num_shots); } @@ -388,6 +400,7 @@ template class MeasurementsBase { */ auto sample(const std::size_t &num_shots) -> std::vector { Derived measure(_statevector); + measure.setSeed(this->_deviceseed); return measure.generate_samples(num_shots); } @@ -479,11 +492,13 @@ template class MeasurementsBase { StateVectorT sv(data_storage.data(), data_storage.size()); obs.applyInPlaceShots(sv, eigenValues, obs_wires); Derived measure(sv); + measure.setSeed(this->_deviceseed); samples = measure.generate_samples(num_shots); } else { StateVectorT sv(_statevector); obs.applyInPlaceShots(sv, eigenValues, obs_wires); Derived measure(sv); + measure.setSeed(this->_deviceseed); samples = measure.generate_samples(num_shots); } diff --git a/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp b/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp index f32a44a363..92d2e6e77f 100644 --- a/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp +++ b/pennylane_lightning/core/src/measurements/tests/Test_MeasurementsBase.cpp @@ -1257,6 +1257,7 @@ void testSamples(const std::optional &seed = std::nullopt) { // This object attaches to the statevector allowing several // measurements. Measurements Measurer(statevector); + Measurer.setSeed(seed); std::vector expected_probabilities = { 0.67078706, 0.03062806, 0.0870997, 0.00397696, @@ -1265,10 +1266,7 @@ void testSamples(const std::optional &seed = std::nullopt) { std::size_t num_qubits = 3; std::size_t N = std::pow(2, num_qubits); std::size_t num_samples = 100000; - auto &&samples = - seed.has_value() - ? Measurer.generate_samples(num_samples, seed.value()) - : Measurer.generate_samples(num_samples); + auto &&samples = Measurer.generate_samples(num_samples); std::vector counts(N, 0); std::vector samples_decimal(num_samples, 0); diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.cpp b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.cpp index 48a4ea6809..4adc9bcc4f 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.cpp @@ -256,6 +256,8 @@ auto LightningGPUSimulator::Expval(ObsIdType obsKey) -> double { Pennylane::LightningGPU::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return device_shots ? m.expval(*obs, device_shots, {}) : m.expval(*obs); } @@ -273,6 +275,8 @@ auto LightningGPUSimulator::Var(ObsIdType obsKey) -> double { Pennylane::LightningGPU::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return device_shots ? m.var(*obs, device_shots) : m.var(*obs); } @@ -294,6 +298,9 @@ void LightningGPUSimulator::State(DataView, 1> &state) { void LightningGPUSimulator::Probs(DataView &probs) { Pennylane::LightningGPU::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = device_shots ? m.probs(device_shots) : m.probs(); RT_FAIL_IF(probs.size() != dv_probs.size(), @@ -313,6 +320,9 @@ void LightningGPUSimulator::PartialProbs( auto dev_wires = getDeviceWires(wires); Pennylane::LightningGPU::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = device_shots ? m.probs(dev_wires, device_shots) : m.probs(dev_wires); @@ -327,9 +337,8 @@ std::vector LightningGPUSimulator::GenerateSamples(size_t shots) { Pennylane::LightningGPU::Measures::Measurements m{ *(this->device_sv)}; - if (this->gen) { - return m.generate_samples(shots, (*(this->gen))()); - } + m.setSeed(this->generateSeed()); + return m.generate_samples(shots); } diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.hpp b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.hpp index edf99c9f61..1c9238a5b2 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/LightningGPUSimulator.hpp @@ -95,6 +95,14 @@ class LightningGPUSimulator final : public Catalyst::Runtime::QuantumDevice { return res; } + inline auto generateSeed() -> std::optional { + if (this->gen != nullptr) { + return (*(this->gen))(); + } + + return std::nullopt; + } + auto GenerateSamples(size_t shots) -> std::vector; public: diff --git a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/tests/Test_LightningGPUMeasures.cpp b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/tests/Test_LightningGPUMeasures.cpp index 93ac7b3a2d..93d46226c1 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/tests/Test_LightningGPUMeasures.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/catalyst/tests/Test_LightningGPUMeasures.cpp @@ -1823,3 +1823,137 @@ TEST_CASE("Sample with a seeded device", "[Measures]") { } } } + +TEST_CASE("Probs with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::vector> probs(2, std::vector(16)); + + std::vector> views{DataView(probs[0]), + DataView(probs[1])}; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LGPUSimulator &sim, DataView &view, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + sim.Probs(view); + }; + + 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 < probs[0].size(); i++) { + CHECK((probs[0][i] == probs[1][i])); + } + } +} + +TEST_CASE("Var with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> vars; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LGPUSimulator &sim, std::vector &var, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + var.push_back(sim.Var(px)); + var.push_back(sim.Var(py)); + var.push_back(sim.Var(pz)); + }; + + 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]), vars[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < vars[0].size(); i++) { + CHECK((vars[0][i] == vars[1][i])); + } + } +} + +TEST_CASE("Expval with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> expvals; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LGPUSimulator &sim, std::vector &expval, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + expval.push_back(sim.Expval(px)); + expval.push_back(sim.Expval(py)); + expval.push_back(sim.Expval(pz)); + }; + + 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]), expvals[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < expvals[0].size(); i++) { + CHECK((expvals[0][i] == expvals[1][i])); + } + } +} 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 bcfdd3944c..8f4f5530d0 100644 --- a/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_gpu/measurements/MeasurementsGPU.hpp @@ -25,7 +25,6 @@ #include #include #include // custatevecApplyMatrix -#include #include #include #include @@ -215,9 +214,7 @@ 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, - const std::optional &seed = std::nullopt) - -> std::vector { + auto generate_samples(std::size_t num_samples) -> std::vector { std::vector rand_nums(num_samples); custatevecSamplerDescriptor_t sampler; @@ -236,15 +233,11 @@ class Measurements final } else { data_type = CUDA_C_32F; } + this->setSeed(this->_deviceseed); - 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); + rand_nums[n] = dis(this->_rng); } std::vector samples(num_samples * num_qubits, 0); std::unordered_map cache; 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 cfe3a9c5d0..447290bac1 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.cpp @@ -268,6 +268,8 @@ auto LightningKokkosSimulator::Expval(ObsIdType obsKey) -> double { Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return device_shots ? m.expval(*obs, device_shots, {}) : m.expval(*obs); } @@ -285,6 +287,8 @@ auto LightningKokkosSimulator::Var(ObsIdType obsKey) -> double { Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return device_shots ? m.var(*obs, device_shots) : m.var(*obs); } @@ -314,6 +318,9 @@ void LightningKokkosSimulator::State(DataView, 1> &state) { void LightningKokkosSimulator::Probs(DataView &probs) { Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = device_shots ? m.probs(device_shots) : m.probs(); RT_FAIL_IF(probs.size() != dv_probs.size(), @@ -333,6 +340,9 @@ void LightningKokkosSimulator::PartialProbs( auto dev_wires = getDeviceWires(wires); Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = device_shots ? m.probs(dev_wires, device_shots) : m.probs(dev_wires); @@ -347,11 +357,10 @@ std::vector LightningKokkosSimulator::GenerateSamples(size_t shots) { Pennylane::LightningKokkos::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + // PL-Lightning-Kokkos generates samples using the alias method. // Reference: https://en.wikipedia.org/wiki/Inverse_transform_sampling - if (this->gen) { - return m.generate_samples(shots, (*(this->gen))()); - } return m.generate_samples(shots); } 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 01110cb937..dd162569fe 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/catalyst/LightningKokkosSimulator.hpp @@ -95,6 +95,14 @@ class LightningKokkosSimulator final : public Catalyst::Runtime::QuantumDevice { return res; } + inline auto generateSeed() -> std::optional { + if (this->gen != nullptr) { + return (*(this->gen))(); + } + + return std::nullopt; + } + auto GenerateSamples(size_t shots) -> std::vector; public: 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 d32e6100ef..962b812236 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 @@ -1822,3 +1822,137 @@ TEST_CASE("Sample with a seeded device", "[Measures]") { } } } + +TEST_CASE("Probs with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::vector> probs(2, std::vector(16)); + + std::vector> views{DataView(probs[0]), + DataView(probs[1])}; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LKSimulator &sim, DataView &view, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + sim.Probs(view); + }; + + 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 < probs[0].size(); i++) { + CHECK((probs[0][i] == probs[1][i])); + } + } +} + +TEST_CASE("Var with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> vars; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LKSimulator &sim, std::vector &var, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + var.push_back(sim.Var(px)); + var.push_back(sim.Var(py)); + var.push_back(sim.Var(pz)); + }; + + 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]), vars[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < vars[0].size(); i++) { + CHECK((vars[0][i] == vars[1][i])); + } + } +} + +TEST_CASE("Expval with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> expvals; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LKSimulator &sim, std::vector &expval, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + expval.push_back(sim.Expval(px)); + expval.push_back(sim.Expval(py)); + expval.push_back(sim.Expval(pz)); + }; + + 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]), expvals[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < expvals[0].size(); i++) { + CHECK((expvals[0][i] == expvals[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 ee8684e814..61b3b29a60 100644 --- a/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_kokkos/measurements/MeasurementsKokkos.hpp @@ -14,7 +14,6 @@ #pragma once #include #include -#include #include #include @@ -650,16 +649,13 @@ 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, - const std::optional &seed = std::nullopt) - -> std::vector { + auto generate_samples(std::size_t num_samples) -> std::vector { const std::size_t num_qubits = this->_statevector.getNumQubits(); const std::size_t N = this->_statevector.getLength(); Kokkos::View samples("num_samples", @@ -678,12 +674,13 @@ class Measurements final }); // Sampling using Random_XorShift64_Pool - 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()); + auto rand_pool = + this->_deviceseed.has_value() + ? Kokkos::Random_XorShift64_Pool<>(this->_deviceseed.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/catalyst/LightningSimulator.cpp b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.cpp index 5b4141cd13..10ae70adf5 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.cpp @@ -251,6 +251,8 @@ auto LightningSimulator::Expval(ObsIdType obsKey) -> double { Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return (device_shots != 0U) ? m.expval(*obs, device_shots, {}) : m.expval(*obs); } @@ -268,6 +270,8 @@ auto LightningSimulator::Var(ObsIdType obsKey) -> double { Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + return (device_shots != 0U) ? m.var(*obs, device_shots) : m.var(*obs); } @@ -282,6 +286,9 @@ void LightningSimulator::State(DataView, 1> &state) { void LightningSimulator::Probs(DataView &probs) { Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = (device_shots != 0U) ? m.probs(device_shots) : m.probs(); RT_FAIL_IF(probs.size() != dv_probs.size(), @@ -301,6 +308,9 @@ void LightningSimulator::PartialProbs(DataView &probs, auto dev_wires = getDeviceWires(wires); Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + + m.setSeed(this->generateSeed()); + auto &&dv_probs = (device_shots != 0U) ? m.probs(dev_wires, device_shots) : m.probs(dev_wires); @@ -316,6 +326,8 @@ LightningSimulator::GenerateSamplesMetropolis(size_t shots) { Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + // PL-Lightning generates samples using the alias method. // Reference: https://en.wikipedia.org/wiki/Alias_method // Given the number of samples, returns 1-D vector of samples @@ -335,6 +347,8 @@ std::vector LightningSimulator::GenerateSamples(size_t shots) { Pennylane::LightningQubit::Measures::Measurements m{ *(this->device_sv)}; + m.setSeed(this->generateSeed()); + // PL-Lightning generates samples using the alias method. // Reference: https://en.wikipedia.org/wiki/Alias_method // Given the number of samples, returns 1-D vector of samples @@ -342,9 +356,6 @@ std::vector LightningSimulator::GenerateSamples(size_t shots) { // the number of qubits. // // Return Value Optimization (RVO) - if (this->gen != nullptr) { - return m.generate_samples(shots, (*(this->gen))()); - } return m.generate_samples(shots); } diff --git a/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.hpp b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.hpp index 28cbb19578..c4edb70dbb 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/LightningSimulator.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include "StateVectorLQubitManaged.hpp" @@ -87,6 +88,14 @@ class LightningSimulator final : public Catalyst::Runtime::QuantumDevice { return res; } + inline auto generateSeed() -> std::optional { + if (this->gen != nullptr) { + return (*(this->gen))(); + } + + return std::nullopt; + } + public: explicit LightningSimulator( const std::string &kwargs = "{}") // NOLINT(hicpp-member-init) diff --git a/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/tests/Test_LightningMeasures.cpp b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/tests/Test_LightningMeasures.cpp index 3189fbef7c..db077eec12 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/tests/Test_LightningMeasures.cpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/catalyst/tests/Test_LightningMeasures.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include +#include #include "CacheManager.hpp" #include "LightningSimulator.hpp" @@ -1831,3 +1832,137 @@ TEST_CASE("Sample with a seeded device", "[Measures]") { } } } + +TEST_CASE("Probs with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::vector> probs(2, std::vector(16)); + + std::vector> views{DataView(probs[0]), + DataView(probs[1])}; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LQSimulator &sim, DataView &view, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + sim.Probs(view); + }; + + 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 < probs[0].size(); i++) { + CHECK((probs[0][i] == probs[1][i])); + } + } +} + +TEST_CASE("Var with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> vars; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LQSimulator &sim, std::vector &var, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + var.push_back(sim.Var(px)); + var.push_back(sim.Var(py)); + var.push_back(sim.Var(pz)); + }; + + 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]), vars[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < vars[0].size(); i++) { + CHECK((vars[0][i] == vars[1][i])); + } + } +} + +TEST_CASE("Expval with a seeded device", "[Measures]") { + constexpr std::size_t shots = 1000; + std::array, 2> sims; + std::array, 2> expvals; + + std::vector gens{std::mt19937{37}, std::mt19937{37}}; + + auto circuit = [](LQSimulator &sim, std::vector &expval, + std::mt19937 &gen) { + sim.SetDevicePRNG(&gen); + sim.SetDeviceShots(shots); + // state-vector with #qubits = n + constexpr std::size_t n = 4; + std::vector Qs; + Qs.reserve(n); + for (std::size_t i = 0; i < n; i++) { + Qs.push_back(sim.AllocateQubit()); + } + sim.NamedOperation("Hadamard", {}, {Qs[0]}, false); + sim.NamedOperation("PauliY", {}, {Qs[1]}, false); + sim.NamedOperation("Hadamard", {}, {Qs[2]}, false); + sim.NamedOperation("PauliZ", {}, {Qs[3]}, false); + + ObsIdType px = sim.Observable(ObsId::PauliX, {}, {Qs[2]}); + ObsIdType py = sim.Observable(ObsId::PauliY, {}, {Qs[0]}); + ObsIdType pz = sim.Observable(ObsId::PauliZ, {}, {Qs[3]}); + + expval.push_back(sim.Expval(px)); + expval.push_back(sim.Expval(py)); + expval.push_back(sim.Expval(pz)); + }; + + 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]), expvals[sim_idx], gens[sim_idx]); + } + + for (std::size_t i = 0; i < expvals[0].size(); i++) { + CHECK((expvals[0][i] == expvals[1][i])); + } + } +} 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 d57bd70631..fa3cedf62e 100644 --- a/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp +++ b/pennylane_lightning/core/src/simulators/lightning_qubit/measurements/MeasurementsLQubit.hpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -503,13 +502,13 @@ class Measurements final // Burn In for (std::size_t i = 0; i < num_burnin; i++) { - idx = metropolis_step(this->_statevector, tk, this->rng, distrib, + idx = metropolis_step(this->_statevector, tk, this->_rng, distrib, idx); // Burn-in. } // Sample for (std::size_t i = 0; i < num_samples; i++) { - idx = metropolis_step(this->_statevector, tk, this->rng, distrib, + idx = metropolis_step(this->_statevector, tk, this->_rng, distrib, idx); if (cache.contains(idx)) { @@ -574,17 +573,14 @@ 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, - const std::optional &seed = std::nullopt) { + std::vector generate_samples(const std::size_t num_samples) { 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, seed); + return generate_samples(wires, num_samples); } /** @@ -592,22 +588,16 @@ 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::optional &seed = std::nullopt) { + const std::size_t num_samples) { const std::size_t n_wires = wires.size(); std::vector samples(num_samples * n_wires); - if (seed.has_value()) { - this->setSeed(seed.value()); - } else { - this->setRandomSeed(); - } - DiscreteRandomVariable drv{this->rng, probs(wires)}; + this->setSeed(this->_deviceseed); + 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 - // j)` `s` being the "slow" row index and `j` being the "fast" column