From 29a986da9d173d45fca88d8ff0c0f0d7c90df1c5 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Mon, 5 Apr 2021 19:59:49 -0700 Subject: [PATCH] Improved randomness sources in parallel ops. --- .../core/ops/noise/tfq_noisy_expectation.cc | 37 ++++++++------- .../noise/tfq_noisy_sampled_expectation.cc | 45 +++++++++++-------- .../core/ops/noise/tfq_noisy_samples.cc | 37 ++++++++------- .../tfq_simulate_sampled_expectation_op.cc | 19 +++++++- .../core/ops/tfq_simulate_samples_op.cc | 21 ++++++++- tensorflow_quantum/core/src/util_qsim.h | 13 +++--- tensorflow_quantum/core/src/util_qsim_test.cc | 10 +++-- 7 files changed, 119 insertions(+), 63 deletions(-) diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc index f71ba7b5b..0aafdc11c 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_expectation.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include +#include #include #include "../qsim/lib/channel.h" @@ -169,6 +170,13 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::mt19937 gen(r_seed); + std::uniform_int_distribution<> distrib(1, 1 << 30); + // Simulate programs one by one. Parallelizing over state vectors // we no longer parallelize over circuits. Each time we encounter a // a larger circuit we will grow the Statevector as necessary. @@ -199,13 +207,9 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { while (1) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, sv, - unused_stats); + + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, unused_stats); // Use this trajectory as a source for all expectation calculations. for (int j = 0; j < pauli_sums[i].size(); j++) { @@ -266,6 +270,11 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { output_tensor->setZero(); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Status compute_status = Status::OK(); auto c_lock = tensorflow::mutex(); auto DoWork = [&](int start, int end) { @@ -277,6 +286,9 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + std::mt19937 gen(r_seed + start); + std::uniform_int_distribution<> distrib(1, 1 << 30); + for (int i = 0; i < ncircuits.size(); i++) { int nq = num_qubits[i]; int rep_offset = rep_offsets[start][i]; @@ -305,14 +317,9 @@ class TfqNoisyExpectationOp : public tensorflow::OpKernel { while (1) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, - sv, unused_stats); + + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, unused_stats); // Compute expectations across all ops using this trajectory. for (int j = 0; j < pauli_sums[i].size(); j++) { diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc index cbfca1dbb..85ac5ef1e 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_sampled_expectation.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include +#include #include #include "../qsim/lib/channel.h" @@ -170,6 +171,14 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + // time since epoch seeds random generator. + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::mt19937 gen(r_seed); + std::uniform_int_distribution<> distrib(1, 1 << 30); + // Simulate programs one by one. Parallelizing over state vectors // we no longer parallelize over circuits. Each time we encounter a // a larger circuit we will grow the Statevector as necessary. @@ -200,13 +209,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { while (1) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, sv, - unused_stats); + + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, unused_stats); // Use this trajectory as a source for all expectation calculations. for (int j = 0; j < pauli_sums[i].size(); j++) { @@ -214,9 +219,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { continue; } float exp_v = 0.0; - OP_REQUIRES_OK( - context, ComputeSampledExpectationQsim(pauli_sums[i][j], sim, ss, - sv, scratch, 1, &exp_v)); + OP_REQUIRES_OK(context, ComputeSampledExpectationQsim( + pauli_sums[i][j], sim, ss, sv, scratch, 1, + gen, &exp_v)); rolling_sums[j] += static_cast(exp_v); run_samples[j]++; } @@ -267,6 +272,11 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { output_tensor->setZero(); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Status compute_status = Status::OK(); auto c_lock = tensorflow::mutex(); auto DoWork = [&](int start, int end) { @@ -278,6 +288,10 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + // time since epoch seeds random generator. + std::mt19937 gen(r_seed + start); + std::uniform_int_distribution<> distrib(1, 1 << 30); + for (int i = 0; i < ncircuits.size(); i++) { int nq = num_qubits[i]; int rep_offset = rep_offsets[start][i]; @@ -306,14 +320,9 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { while (1) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, - sv, unused_stats); + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, unused_stats); // Compute expectations across all ops using this trajectory. for (int j = 0; j < pauli_sums[i].size(); j++) { @@ -325,7 +334,7 @@ class TfqNoisySampledExpectationOp : public tensorflow::OpKernel { NESTED_FN_STATUS_SYNC( compute_status, ComputeSampledExpectationQsim(pauli_sums[i][j], sim, ss, sv, - scratch, 1, &exp_v), + scratch, 1, gen, &exp_v), c_lock); rolling_sums[j] += static_cast(exp_v); run_samples[j]++; diff --git a/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc b/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc index dd2835d8c..06bb43be8 100644 --- a/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc +++ b/tensorflow_quantum/core/ops/noise/tfq_noisy_samples.cc @@ -15,6 +15,7 @@ limitations under the License. #include +#include #include #include "../qsim/lib/channel.h" @@ -148,6 +149,13 @@ class TfqNoisySamplesOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::mt19937 gen(r_seed); + std::uniform_int_distribution<> distrib(1, 1 << 30); + // Simulate programs one by one. Parallelizing over state vectors // we no longer parallelize over circuits. Each time we encounter a // a larger circuit we will grow the Statevector as nescessary. @@ -171,14 +179,9 @@ class TfqNoisySamplesOp : public tensorflow::OpKernel { for (int j = 0; j < num_samples; j++) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, sv, - gathered_samples); + + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, gathered_samples); uint64_t q_ind = 0; uint64_t mask = 1; bool val = 0; @@ -233,6 +236,11 @@ class TfqNoisySamplesOp : public tensorflow::OpKernel { } } + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + auto DoWork = [&](int start, int end) { // Begin simulation. const auto tfq_for = qsim::SequentialFor(1); @@ -242,6 +250,9 @@ class TfqNoisySamplesOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + std::mt19937 gen(r_seed + start); + std::uniform_int_distribution<> distrib(1, 1 << 30); + for (int i = 0; i < ncircuits.size(); i++) { int nq = num_qubits[i]; int j = start > 0 ? offset_prefix_sum[start - 1][i] : 0; @@ -263,14 +274,8 @@ class TfqNoisySamplesOp : public tensorflow::OpKernel { while (1) { ss.SetStateZero(sv); - // time since epoch seeds random generator. - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - - QTSimulator::RunOnce(param, ncircuits[i], r_seed, ss, sim, scratch, - sv, gathered_samples); + QTSimulator::RunOnce(param, ncircuits[i], distrib(gen), ss, sim, + scratch, sv, gathered_samples); uint64_t q_ind = 0; uint64_t mask = 1; diff --git a/tensorflow_quantum/core/ops/tfq_simulate_sampled_expectation_op.cc b/tensorflow_quantum/core/ops/tfq_simulate_sampled_expectation_op.cc index e1c78dffe..b41b09e98 100644 --- a/tensorflow_quantum/core/ops/tfq_simulate_sampled_expectation_op.cc +++ b/tensorflow_quantum/core/ops/tfq_simulate_sampled_expectation_op.cc @@ -14,6 +14,7 @@ limitations under the License. ==============================================================================*/ #include +#include #include #include "../qsim/lib/circuit.h" @@ -157,6 +158,12 @@ class TfqSimulateSampledExpectationOp : public tensorflow::OpKernel { auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::mt19937 gen(r_seed); + // Simulate programs one by one. Parallelizing over state vectors // we no longer parallelize over circuits. Each time we encounter a // a larger circuit we will grow the Statevector as necessary. @@ -185,7 +192,7 @@ class TfqSimulateSampledExpectationOp : public tensorflow::OpKernel { float exp_v = 0.0; OP_REQUIRES_OK(context, ComputeSampledExpectationQsim( pauli_sums[i][j], sim, ss, sv, scratch, - num_samples[i][j], &exp_v)); + num_samples[i][j], gen, &exp_v)); (*output_tensor)(i, j) = exp_v; } } @@ -204,6 +211,11 @@ class TfqSimulateSampledExpectationOp : public tensorflow::OpKernel { const int output_dim_op_size = output_tensor->dimension(1); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + Status compute_status = Status::OK(); auto c_lock = tensorflow::mutex(); auto DoWork = [&](int start, int end) { @@ -216,6 +228,9 @@ class TfqSimulateSampledExpectationOp : public tensorflow::OpKernel { StateSpace ss = StateSpace(tfq_for); auto sv = ss.Create(largest_nq); auto scratch = ss.Create(largest_nq); + + std::mt19937 gen(r_seed + start); + for (int i = start; i < end; i++) { cur_batch_index = i / output_dim_op_size; cur_op_index = i % output_dim_op_size; @@ -249,7 +264,7 @@ class TfqSimulateSampledExpectationOp : public tensorflow::OpKernel { compute_status, ComputeSampledExpectationQsim( pauli_sums[cur_batch_index][cur_op_index], sim, ss, sv, scratch, - num_samples[cur_batch_index][cur_op_index], &exp_v), + num_samples[cur_batch_index][cur_op_index], gen, &exp_v), c_lock); (*output_tensor)(cur_batch_index, cur_op_index) = exp_v; diff --git a/tensorflow_quantum/core/ops/tfq_simulate_samples_op.cc b/tensorflow_quantum/core/ops/tfq_simulate_samples_op.cc index 5fa634e51..f156be7fc 100644 --- a/tensorflow_quantum/core/ops/tfq_simulate_samples_op.cc +++ b/tensorflow_quantum/core/ops/tfq_simulate_samples_op.cc @@ -15,6 +15,7 @@ limitations under the License. #include +#include #include #include "../qsim/lib/circuit.h" @@ -143,6 +144,13 @@ class TfqSimulateSamplesOp : public tensorflow::OpKernel { StateSpace ss = StateSpace(tfq_for); auto sv = ss.Create(largest_nq); + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + std::mt19937 gen(r_seed); + std::uniform_int_distribution<> distrib(1, 1 << 30); + // Simulate programs one by one. Parallelizing over state vectors // we no longer parallelize over circuits. Each time we encounter a // a larger circuit we will grow the Statevector as nescessary. @@ -159,7 +167,7 @@ class TfqSimulateSamplesOp : public tensorflow::OpKernel { qsim::ApplyFusedGate(sim, fused_circuits[i][j], sv); } - auto samples = ss.Sample(sv, num_samples, rand() % 123456); + auto samples = ss.Sample(sv, num_samples, distrib(gen)); for (int j = 0; j < num_samples; j++) { uint64_t q_ind = 0; uint64_t mask = 1; @@ -190,11 +198,20 @@ class TfqSimulateSamplesOp : public tensorflow::OpKernel { using Simulator = qsim::Simulator; using StateSpace = Simulator::StateSpace; + unsigned long r_seed = + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + auto DoWork = [&](int start, int end) { int largest_nq = 1; Simulator sim = Simulator(tfq_for); StateSpace ss = StateSpace(tfq_for); auto sv = ss.Create(largest_nq); + + std::mt19937 gen(r_seed + start); + std::uniform_int_distribution<> distrib(1, 1 << 30); + for (int i = start; i < end; i++) { int nq = num_qubits[i]; @@ -208,7 +225,7 @@ class TfqSimulateSamplesOp : public tensorflow::OpKernel { qsim::ApplyFusedGate(sim, fused_circuits[i][j], sv); } - auto samples = ss.Sample(sv, num_samples, rand() % 123456); + auto samples = ss.Sample(sv, num_samples, distrib(gen)); for (int j = 0; j < num_samples; j++) { uint64_t q_ind = 0; uint64_t mask = 1; diff --git a/tensorflow_quantum/core/src/util_qsim.h b/tensorflow_quantum/core/src/util_qsim.h index 95f1ac7e2..90c33edc8 100644 --- a/tensorflow_quantum/core/src/util_qsim.h +++ b/tensorflow_quantum/core/src/util_qsim.h @@ -18,6 +18,7 @@ limitations under the License. #include #include +#include #include #include "../qsim/lib/circuit.h" @@ -189,7 +190,9 @@ template tensorflow::Status ComputeSampledExpectationQsim( const tfq::proto::PauliSum& p_sum, const SimT& sim, const StateSpaceT& ss, StateT& state, StateT& scratch, const int num_samples, - float* expectation_value) { + std::mt19937& random_source, float* expectation_value) { + std::uniform_int_distribution<> distrib(1, 1 << 30); + if (num_samples == 0) { return tensorflow::Status::OK(); } @@ -222,12 +225,8 @@ tensorflow::Status ComputeSampledExpectationQsim( if (!status.ok()) { return status; } - unsigned long r_seed = - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()) - .count(); - const unsigned int seed = static_cast(r_seed); - std::vector state_samples = ss.Sample(scratch, num_samples, seed); + std::vector state_samples = + ss.Sample(scratch, num_samples, distrib(random_source)); // Find qubits on which to measure parity std::vector parity_bits; diff --git a/tensorflow_quantum/core/src/util_qsim_test.cc b/tensorflow_quantum/core/src/util_qsim_test.cc index 64ad82621..1974165f6 100644 --- a/tensorflow_quantum/core/src/util_qsim_test.cc +++ b/tensorflow_quantum/core/src/util_qsim_test.cc @@ -15,6 +15,7 @@ limitations under the License. #include "tensorflow_quantum/core/src/util_qsim.h" +#include #include #include "../qsim/lib/circuit.h" @@ -88,8 +89,9 @@ TEST_P(TwoTermSampledExpectationFixture, CorrectnessTest) { // Compute expectation and compare to reference values. float exp_v = 0; + std::mt19937 gen(1234); Status s = tfq::ComputeSampledExpectationQsim(p_sum, sim, ss, sv, scratch, - 1000000, &exp_v); + 1000000, gen, &exp_v); EXPECT_NEAR(exp_v, std::get<1>(GetParam()), 1e-2); } @@ -191,8 +193,9 @@ TEST(UtilQsimTest, SampledEmptyTermCase) { // Compute expectation and compare to reference values. float exp_v = 0; + std::mt19937 gen(1234); Status s = tfq::ComputeSampledExpectationQsim(p_sum_empty, sim, ss, sv, - scratch, 100, &exp_v); + scratch, 100, gen, &exp_v); EXPECT_NEAR(exp_v, 0.1234, 1e-5); } @@ -274,8 +277,9 @@ TEST(UtilQsimTest, SampledCompoundCase) { p_term_scratch->set_coefficient_real(4.0); // Compute expectation and compare to reference values. float exp_v = 0; + std::mt19937 gen(1234); Status s = tfq::ComputeSampledExpectationQsim(p_sum, sim, ss, sv, scratch, - 10000000, &exp_v); + 10000000, gen, &exp_v); EXPECT_NEAR(exp_v, 4.1234, 1e-2); }