From 3a3e7198317e59a564359513587882b932d191a9 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 15:23:57 -0700 Subject: [PATCH 1/8] Refactor RNG ownership semantics - Change `stim::TableauSimulator`'s constructor to require a move-reference for its rng - Change `stim::FrameSimulator`'s constructor to require a move-reference for its rng - Change `stim::FrameSimulator`'s RNG from a reference to a normal object owned by the simulator - Replace SHARED_TEST_RNG method with INDEPENDENT_TEST_RNG method - Rewrite all tests to use INDEPENDENT_TEST_RNG, fixing potential overlaps in randomness - Also fix some `pybind11::args args` -> `const pybind11::args &args` warnings - Also fix some int vs size_t comparison warnings Fixes https://github.com/quantumlib/Stim/issues/353 --- src/stim/circuit/gate_data.test.cc | 10 +- src/stim/circuit/stabilizer_flow.test.cc | 3 +- src/stim/cmd/command_gen.test.cc | 3 +- src/stim/cmd/command_sample.cc | 3 +- src/stim/io/measure_record_reader.test.cc | 17 +- src/stim/mem/simd_bit_table.h | 3 + src/stim/mem/simd_bit_table.test.cc | 11 +- src/stim/mem/simd_bits.test.cc | 32 +- src/stim/mem/simd_bits_range_ref.inl | 4 +- src/stim/mem/simd_bits_range_ref.test.cc | 28 +- src/stim/mem/simd_util.test.cc | 10 +- src/stim/mem/simd_word.test.cc | 2 +- src/stim/probability_util.test.cc | 11 +- .../count_determined_measurements.inl | 3 +- src/stim/simulators/dem_sampler.test.cc | 4 +- src/stim/simulators/error_analyzer.test.cc | 2 +- src/stim/simulators/frame_simulator.h | 4 +- src/stim/simulators/frame_simulator.inl | 4 +- src/stim/simulators/frame_simulator.test.cc | 200 +++++++------ src/stim/simulators/frame_simulator_util.inl | 16 +- .../simulators/frame_simulator_util.test.cc | 30 +- .../measurements_to_detection_events.inl | 7 +- .../sparse_rev_frame_tracker.test.cc | 2 +- src/stim/simulators/tableau_simulator.h | 4 +- src/stim/simulators/tableau_simulator.inl | 4 +- .../simulators/tableau_simulator.pybind.cc | 70 ++--- src/stim/simulators/tableau_simulator.test.cc | 274 ++++++++++-------- src/stim/stabilizers/conversions.inl | 6 +- src/stim/stabilizers/conversions.test.cc | 20 +- src/stim/stabilizers/pauli_string.test.cc | 3 +- src/stim/stabilizers/tableau.test.cc | 54 ++-- src/stim/test_util.test.cc | 5 +- src/stim/test_util.test.h | 2 +- 33 files changed, 484 insertions(+), 367 deletions(-) diff --git a/src/stim/circuit/gate_data.test.cc b/src/stim/circuit/gate_data.test.cc index 5e7a80d09..fa2d590cc 100644 --- a/src/stim/circuit/gate_data.test.cc +++ b/src/stim/circuit/gate_data.test.cc @@ -75,8 +75,8 @@ std::pair>, std::vector>> circuit_outp if (circuit.count_measurements() > 1) { throw std::invalid_argument("count_measurements > 1"); } - TableauSimulator sim1(SHARED_TEST_RNG(), circuit.count_qubits(), -1); - TableauSimulator sim2(SHARED_TEST_RNG(), circuit.count_qubits(), +1); + TableauSimulator sim1(INDEPENDENT_TEST_RNG(), circuit.count_qubits(), -1); + TableauSimulator sim2(INDEPENDENT_TEST_RNG(), circuit.count_qubits(), +1); sim1.expand_do_circuit(circuit); sim2.expand_do_circuit(circuit); return {sim1.canonical_stabilizers(), sim2.canonical_stabilizers()}; @@ -163,7 +163,8 @@ TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_correct, { Circuit c; c.safe_append(g.id, targets, {}); - auto r = check_if_circuit_has_stabilizer_flows(256, SHARED_TEST_RNG(), c, flows); + auto rng = INDEPENDENT_TEST_RNG(); + auto r = check_if_circuit_has_stabilizer_flows(256, rng, c, flows); for (uint32_t fk = 0; fk < (uint32_t)flows.size(); fk++) { EXPECT_TRUE(r[fk]) << "gate " << g.name << " has an unsatisfied flow: " << flows[fk]; } @@ -171,6 +172,7 @@ TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_correct, { }) TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_also_correct_for_decomposed_circuit, { + auto rng =INDEPENDENT_TEST_RNG(); for (const auto &g : GATE_DATA.items) { auto flows = g.flows(); if (flows.empty()) { @@ -194,7 +196,7 @@ TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_also_correct_for_decompose } Circuit c(g.extra_data_func().h_s_cx_m_r_decomposition); - auto r = check_if_circuit_has_stabilizer_flows(256, SHARED_TEST_RNG(), c, flows); + auto r = check_if_circuit_has_stabilizer_flows(256, rng, c, flows); for (uint32_t fk = 0; fk < (uint32_t)flows.size(); fk++) { EXPECT_TRUE(r[fk]) << "gate " << g.name << " has a decomposition with an unsatisfied flow: " << flows[fk]; } diff --git a/src/stim/circuit/stabilizer_flow.test.cc b/src/stim/circuit/stabilizer_flow.test.cc index ca0c98b42..f48a5b1ca 100644 --- a/src/stim/circuit/stabilizer_flow.test.cc +++ b/src/stim/circuit/stabilizer_flow.test.cc @@ -23,9 +23,10 @@ using namespace stim; TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_stabilizer_flows, { + auto rng = INDEPENDENT_TEST_RNG(); auto results = check_if_circuit_has_stabilizer_flows( 256, - SHARED_TEST_RNG(), + rng, Circuit(R"CIRCUIT( R 4 CX 0 4 1 4 2 4 3 4 diff --git a/src/stim/cmd/command_gen.test.cc b/src/stim/cmd/command_gen.test.cc index 8e98145bf..0dd86ce18 100644 --- a/src/stim/cmd/command_gen.test.cc +++ b/src/stim/cmd/command_gen.test.cc @@ -45,7 +45,8 @@ TEST_EACH_WORD_SIZE_W(command_gen, no_noise_no_detections, { } CircuitGenParameters params(r, d, func.second.first); auto circuit = func.second.second(params).circuit; - auto [det_samples, obs_samples] = sample_batch_detection_events(circuit, 256, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto [det_samples, obs_samples] = sample_batch_detection_events(circuit, 256, rng); EXPECT_FALSE(det_samples.data.not_zero() || obs_samples.data.not_zero()) << "d=" << d << ", r=" << r << ", task=" << func.second.first << ", func=" << func.first; } diff --git a/src/stim/cmd/command_sample.cc b/src/stim/cmd/command_sample.cc index 1b873553a..4a9b52a23 100644 --- a/src/stim/cmd/command_sample.cc +++ b/src/stim/cmd/command_sample.cc @@ -51,7 +51,8 @@ int stim::command_sample(int argc, const char **argv) { if (num_shots == 1 && !skip_reference_sample) { TableauSimulator::sample_stream(in, out, out_format.id, false, rng); - } else if (num_shots > 0) { + } else { + assert(num_shots > 0); auto circuit = Circuit::from_file(in); simd_bits ref(0); if (!skip_reference_sample) { diff --git a/src/stim/io/measure_record_reader.test.cc b/src/stim/io/measure_record_reader.test.cc index 98f1254b0..a5e94f5a7 100644 --- a/src/stim/io/measure_record_reader.test.cc +++ b/src/stim/io/measure_record_reader.test.cc @@ -453,7 +453,8 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, read_records_into_RoundTrip, { size_t n_shots = 100; size_t n_results = 512 - 8; - auto shot_maj_data = simd_bit_table::random(n_shots, n_results, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto shot_maj_data = simd_bit_table::random(n_shots, n_results, rng); auto shot_min_data = shot_maj_data.transposed(); for (const auto &kv : format_name_to_enum_map()) { SampleFormat format = kv.second.id; @@ -556,13 +557,14 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, read_b8_detection_event_data_full_run }) TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record, { + auto rng = INDEPENDENT_TEST_RNG(); size_t n = 512 - 8; size_t no = 5; size_t nd = n - no; // Compute expected data. simd_bits test_data(n); - biased_randomize_bits(0.1, test_data.u64, test_data.u64 + test_data.num_u64_padded(), SHARED_TEST_RNG()); + biased_randomize_bits(0.1, test_data.u64, test_data.u64 + test_data.num_u64_padded(), rng); SparseShot sparse_test_data; sparse_test_data.obs_mask = simd_bits<64>(no); for (size_t k = 0; k < nd; k++) { @@ -660,9 +662,10 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record_all_zero }) TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record_ptb64_dense, { + auto rng = INDEPENDENT_TEST_RNG(); FILE *f = tmpfile(); - auto saved1 = simd_bits::random(64 * 71, SHARED_TEST_RNG()); - auto saved2 = simd_bits::random(64 * 71, SHARED_TEST_RNG()); + auto saved1 = simd_bits::random(64 * 71, rng); + auto saved2 = simd_bits::random(64 * 71, rng); for (size_t k = 0; k < 64 * 71 / 8; k++) { putc(saved1.u8[k], f); } @@ -689,12 +692,13 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record_ptb64_de }) TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record_ptb64_sparse, { + auto rng = INDEPENDENT_TEST_RNG(); FILE *tmp = tmpfile(); simd_bit_table ground_truth(71, 64 * 5); { MeasureRecordBatchWriter writer(tmp, 64 * 5, stim::SAMPLE_FORMAT_PTB64); for (size_t k = 0; k < 71; k++) { - ground_truth[k].randomize(64 * 5, SHARED_TEST_RNG()); + ground_truth[k].randomize(64 * 5, rng); writer.batch_write_bit(ground_truth[k]); } writer.write_end(); @@ -719,6 +723,7 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, start_and_read_entire_record_ptb64_sp }) TEST_EACH_WORD_SIZE_W(MeasureRecordReader, read_file_data_into_shot_table_vs_write_table, { + auto rng = INDEPENDENT_TEST_RNG(); for (const auto &format_data : format_name_to_enum_map()) { SampleFormat format = format_data.second.id; size_t num_shots = 500; @@ -729,7 +734,7 @@ TEST_EACH_WORD_SIZE_W(MeasureRecordReader, read_file_data_into_shot_table_vs_wri simd_bit_table expected(num_shots, bits_per_shot); for (size_t shot = 0; shot < num_shots; shot++) { - expected[shot].randomize(bits_per_shot, SHARED_TEST_RNG()); + expected[shot].randomize(bits_per_shot, rng); } simd_bit_table expected_transposed = expected.transposed(); diff --git a/src/stim/mem/simd_bit_table.h b/src/stim/mem/simd_bit_table.h index 0bb2969bf..e2ee5a6be 100644 --- a/src/stim/mem/simd_bit_table.h +++ b/src/stim/mem/simd_bit_table.h @@ -108,6 +108,9 @@ struct simd_bit_table { /// Returns a subset of the table. simd_bit_table slice_maj(size_t maj_start_bit, size_t maj_stop_bit) const; + /// Returns a copy of a column of the table. + simd_bits read_across_majors_at_minor_index(size_t major_start, size_t major_stop, size_t minor_index) const; + /// Concatenates the contents of the two tables, along the major axis. simd_bit_table concat_major(const simd_bit_table &second, size_t n_first, size_t n_second) const; /// Overwrites a range of the table with a range from another table with the same minor size. diff --git a/src/stim/mem/simd_bit_table.test.cc b/src/stim/mem/simd_bit_table.test.cc index 3abd0fbfd..947213a68 100644 --- a/src/stim/mem/simd_bit_table.test.cc +++ b/src/stim/mem/simd_bit_table.test.cc @@ -200,18 +200,20 @@ TEST_EACH_WORD_SIZE_W(simd_bit_table, transposed, { }) TEST_EACH_WORD_SIZE_W(simd_bit_table, random, { - auto t = simd_bit_table::random(100, 90, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = simd_bit_table::random(100, 90, rng); ASSERT_NE(t[99], simd_bits(90)); ASSERT_EQ(t[100], simd_bits(90)); t = t.transposed(); ASSERT_NE(t[89], simd_bits(100)); ASSERT_EQ(t[90], simd_bits(100)); ASSERT_NE( - simd_bit_table::random(10, 10, SHARED_TEST_RNG()), simd_bit_table::random(10, 10, SHARED_TEST_RNG())); + simd_bit_table::random(10, 10, rng), simd_bit_table::random(10, 10, rng)); }) TEST_EACH_WORD_SIZE_W(simd_bit_table, slice_maj, { - auto m = simd_bit_table::random(100, 64, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto m = simd_bit_table::random(100, 64, rng); auto s = m.slice_maj(5, 15); ASSERT_EQ(s[0], m[5]); ASSERT_EQ(s[9], m[14]); @@ -291,7 +293,8 @@ TEST(simd_bit_table, lg) { } TEST_EACH_WORD_SIZE_W(simd_bit_table, destructive_resize, { - simd_bit_table table = table.random(5, 7, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + simd_bit_table table = table.random(5, 7, rng); const uint8_t *prev_pointer = table.data.u8; table.destructive_resize(5, 7); ASSERT_EQ(table.data.u8, prev_pointer); diff --git a/src/stim/mem/simd_bits.test.cc b/src/stim/mem/simd_bits.test.cc index 6cdfcefed..62b9eb9c9 100644 --- a/src/stim/mem/simd_bits.test.cc +++ b/src/stim/mem/simd_bits.test.cc @@ -123,7 +123,8 @@ TEST_EACH_WORD_SIZE_W(simd_bits, str, { TEST_EACH_WORD_SIZE_W(simd_bits, randomize, { simd_bits d(1024); - d.randomize(64 + 57, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + d.randomize(64 + 57, rng); uint64_t mask = (1ULL << 57) - 1; // Randomized. ASSERT_NE(d.u64[0], 0); @@ -138,7 +139,7 @@ TEST_EACH_WORD_SIZE_W(simd_bits, randomize, { for (size_t k = 0; k < d.num_u64_padded(); k++) { d.u64[k] = UINT64_MAX; } - d.randomize(64 + 57, SHARED_TEST_RNG()); + d.randomize(64 + 57, rng); // Randomized. ASSERT_NE(d.u64[0], 0); ASSERT_NE(d.u64[0], SIZE_MAX); @@ -151,8 +152,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits, randomize, { }) TEST_EACH_WORD_SIZE_W(simd_bits, xor_assignment, { - simd_bits m0 = simd_bits::random(512, SHARED_TEST_RNG()); - simd_bits m1 = simd_bits::random(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + simd_bits m0 = simd_bits::random(512, rng); + simd_bits m1 = simd_bits::random(512, rng); simd_bits m2(512); m2 ^= m0; ASSERT_EQ(m0, m2); @@ -284,7 +286,7 @@ TEST_EACH_WORD_SIZE_W(simd_bits, right_shift_assignment, { }) TEST_EACH_WORD_SIZE_W(simd_bits, fuzz_right_shift_assignment, { - auto rng = SHARED_TEST_RNG(); + auto rng = INDEPENDENT_TEST_RNG(); for (int i = 0; i < 5; i++) { std::uniform_int_distribution dist_bits(1, 1200); int num_bits = dist_bits(rng); @@ -334,7 +336,7 @@ TEST_EACH_WORD_SIZE_W(simd_bits, left_shift_assignment, { }) TEST_EACH_WORD_SIZE_W(simd_bits, fuzz_left_shift_assignment, { - auto rng = SHARED_TEST_RNG(); + auto rng = INDEPENDENT_TEST_RNG(); for (int i = 0; i < 5; i++) { std::uniform_int_distribution dist_bits(1, 1200); int num_bits = dist_bits(rng); @@ -356,8 +358,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits, fuzz_left_shift_assignment, { TEST_EACH_WORD_SIZE_W(simd_bits, assignment, { simd_bits m0(512); simd_bits m1(512); - m0.randomize(512, SHARED_TEST_RNG()); - m1.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); + m1.randomize(512, rng); auto old_m1 = m1.u64[0]; ASSERT_NE(m0, m1); m0 = m1; @@ -389,8 +392,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits, swap_with, { simd_bits m1(512); simd_bits m2(512); simd_bits m3(512); - m0.randomize(512, SHARED_TEST_RNG()); - m1.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); + m1.randomize(512, rng); m2 = m0; m3 = m1; ASSERT_EQ(m0, m2); @@ -402,7 +406,8 @@ TEST_EACH_WORD_SIZE_W(simd_bits, swap_with, { TEST_EACH_WORD_SIZE_W(simd_bits, clear, { simd_bits m0(512); - m0.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); ASSERT_TRUE(m0.not_zero()); m0.clear(); ASSERT_TRUE(!m0.not_zero()); @@ -471,8 +476,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits, mask_assignment_or, { }) TEST_EACH_WORD_SIZE_W(simd_bits, truncated_overwrite_from, { - simd_bits dat = simd_bits::random(1024, SHARED_TEST_RNG()); - simd_bits mut = simd_bits::random(1024, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + simd_bits dat = simd_bits::random(1024, rng); + simd_bits mut = simd_bits::random(1024, rng); simd_bits old = mut; mut.truncated_overwrite_from(dat, 455); diff --git a/src/stim/mem/simd_bits_range_ref.inl b/src/stim/mem/simd_bits_range_ref.inl index 4ca657975..46fc514bd 100644 --- a/src/stim/mem/simd_bits_range_ref.inl +++ b/src/stim/mem/simd_bits_range_ref.inl @@ -103,7 +103,7 @@ simd_bits_range_ref simd_bits_range_ref::operator<<=(int offset) { } while (offset >= 64) { incoming_word = 0ULL; - for (int w = 0; w < num_u64_padded(); w++) { + for (size_t w = 0; w < num_u64_padded(); w++) { cur_word = u64[w]; u64[w] = incoming_word; incoming_word = cur_word; @@ -114,7 +114,7 @@ simd_bits_range_ref simd_bits_range_ref::operator<<=(int offset) { return *this; } incoming_word = 0ULL; - for (int w = 0; w < num_u64_padded(); w++) { + for (size_t w = 0; w < num_u64_padded(); w++) { cur_word = u64[w]; u64[w] <<= offset; u64[w] |= incoming_word; diff --git a/src/stim/mem/simd_bits_range_ref.test.cc b/src/stim/mem/simd_bits_range_ref.test.cc index f23f2ae99..235b85d69 100644 --- a/src/stim/mem/simd_bits_range_ref.test.cc +++ b/src/stim/mem/simd_bits_range_ref.test.cc @@ -85,7 +85,8 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, randomize, { alignas(64) std::array data{}; simd_bits_range_ref ref((bitword *)data.data(), sizeof(data) / sizeof(bitword)); - ref.randomize(64 + 57, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + ref.randomize(64 + 57, rng); uint64_t mask = (1ULL << 57) - 1; // Randomized. ASSERT_NE(ref.u64[0], 0); @@ -100,7 +101,7 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, randomize, { for (size_t k = 0; k < ref.num_u64_padded(); k++) { ref.u64[k] = UINT64_MAX; } - ref.randomize(64 + 57, SHARED_TEST_RNG()); + ref.randomize(64 + 57, rng); // Randomized. ASSERT_NE(ref.u64[0], 0); ASSERT_NE(ref.u64[0], SIZE_MAX); @@ -117,8 +118,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, xor_assignment, { simd_bits_range_ref m0((bitword *)&data[0], sizeof(data) / sizeof(bitword) / 3); simd_bits_range_ref m1((bitword *)&data[8], sizeof(data) / sizeof(bitword) / 3); simd_bits_range_ref m2((bitword *)&data[16], sizeof(data) / sizeof(bitword) / 3); - m0.randomize(512, SHARED_TEST_RNG()); - m1.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); + m1.randomize(512, rng); ASSERT_NE(m0, m1); ASSERT_NE(m0, m2); m2 ^= m0; @@ -133,8 +135,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, assignment, { alignas(64) std::array data{}; simd_bits_range_ref m0((bitword *)&data[0], sizeof(data) / sizeof(bitword) / 2); simd_bits_range_ref m1((bitword *)&data[8], sizeof(data) / sizeof(bitword) / 2); - m0.randomize(512, SHARED_TEST_RNG()); - m1.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); + m1.randomize(512, rng); auto old_m1 = m1.u64[0]; ASSERT_NE(m0, m1); m0 = m1; @@ -265,8 +268,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, swap_with, { simd_bits_range_ref m1((bitword *)&data[8], sizeof(data) / sizeof(bitword) / 4); simd_bits_range_ref m2((bitword *)&data[16], sizeof(data) / sizeof(bitword) / 4); simd_bits_range_ref m3((bitword *)&data[24], sizeof(data) / sizeof(bitword) / 4); - m0.randomize(512, SHARED_TEST_RNG()); - m1.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); + m1.randomize(512, rng); m2 = m0; m3 = m1; ASSERT_EQ(m0, m2); @@ -279,7 +283,8 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, swap_with, { TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, clear, { alignas(64) std::array data{}; simd_bits_range_ref m0((bitword *)&data[0], sizeof(data) / sizeof(bitword)); - m0.randomize(512, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + m0.randomize(512, rng); ASSERT_TRUE(m0.not_zero()); m0.clear(); ASSERT_TRUE(!m0.not_zero()); @@ -325,8 +330,9 @@ TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, for_each_set_bit, { }) TEST_EACH_WORD_SIZE_W(simd_bits_range_ref, truncated_overwrite_from, { - simd_bits dat = simd_bits::random(1024, SHARED_TEST_RNG()); - simd_bits mut = simd_bits::random(1024, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + simd_bits dat = simd_bits::random(1024, rng); + simd_bits mut = simd_bits::random(1024, rng); simd_bits old = mut; simd_bits_range_ref(mut).truncated_overwrite_from(dat, 455); diff --git a/src/stim/mem/simd_util.test.cc b/src/stim/mem/simd_util.test.cc index 51860d9cb..1411fc6e6 100644 --- a/src/stim/mem/simd_util.test.cc +++ b/src/stim/mem/simd_util.test.cc @@ -63,7 +63,7 @@ template std::string determine_if_function_performs_bit_permutation_helper( const std::function &)> &func, const std::array &bit_permutation) { size_t area = 1 << A; - auto data = simd_bits::random(area, SHARED_TEST_RNG()); + auto data = simd_bits::random(area, INDEPENDENT_TEST_RNG()); auto expected = simd_bits(area); for (size_t k_in = 0; k_in < area; k_in++) { @@ -103,7 +103,8 @@ template void EXPECT_FUNCTION_PERFORMS_ADDRESS_BIT_PERMUTATION( const std::function &)> &func, const std::array &bit_permutation) { size_t area = 1 << A; - auto data = simd_bits::random(area, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto data = simd_bits::random(area, rng); auto expected = simd_bits(area); for (size_t k_in = 0; k_in < area; k_in++) { @@ -144,7 +145,8 @@ void EXPECT_FUNCTION_PERFORMS_ADDRESS_BIT_PERMUTATION( TEST(simd_util, inplace_transpose_64x64) { constexpr size_t W = 64; - simd_bits data = simd_bits::random(64 * 64, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + simd_bits data = simd_bits::random(64 * 64, rng); simd_bits copy = data; inplace_transpose_64x64(copy.u64, 1); for (size_t i = 0; i < 64; i++) { @@ -432,7 +434,7 @@ TEST(simd_util, popcnt64) { bits.push_back(i < expected); } for (size_t reps = 0; reps < 100; reps++) { - std::shuffle(bits.begin(), bits.end(), SHARED_TEST_RNG()); + std::shuffle(bits.begin(), bits.end(), INDEPENDENT_TEST_RNG()); uint64_t v = 0; for (size_t i = 0; i < 64; i++) { v |= bits[i] << i; diff --git a/src/stim/mem/simd_word.test.cc b/src/stim/mem/simd_word.test.cc index 6fa3eeb22..5a09de2f6 100644 --- a/src/stim/mem/simd_word.test.cc +++ b/src/stim/mem/simd_word.test.cc @@ -41,7 +41,7 @@ TEST_EACH_WORD_SIZE_W(simd_word_pick, popcount, { bits.push_back(i < expected); } for (size_t reps = 0; reps < 100; reps++) { - std::shuffle(bits.begin(), bits.end(), SHARED_TEST_RNG()); + std::shuffle(bits.begin(), bits.end(), INDEPENDENT_TEST_RNG()); for (size_t i = 0; i < n; i++) { v.p[i >> 6] = 0; } diff --git a/src/stim/probability_util.test.cc b/src/stim/probability_util.test.cc index d54d36f1c..6d455b4aa 100644 --- a/src/stim/probability_util.test.cc +++ b/src/stim/probability_util.test.cc @@ -23,17 +23,19 @@ using namespace stim; TEST(probability_util, sample_hit_indices_corner_cases) { - ASSERT_EQ(sample_hit_indices(0, 100000, SHARED_TEST_RNG()), (std::vector{})); - ASSERT_EQ(sample_hit_indices(1, 10, SHARED_TEST_RNG()), (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); + auto rng = INDEPENDENT_TEST_RNG(); + ASSERT_EQ(sample_hit_indices(0, 100000, rng), (std::vector{})); + ASSERT_EQ(sample_hit_indices(1, 10, rng), (std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})); } TEST(probability_util, sample_hit_indices) { + auto rng = INDEPENDENT_TEST_RNG(); size_t num_buckets = 10000; size_t num_samples = 100000; double p = 0.001; std::vector buckets(num_buckets, 0); for (size_t k = 0; k < num_samples; k++) { - for (auto bucket : sample_hit_indices(p, num_buckets, SHARED_TEST_RNG())) { + for (auto bucket : sample_hit_indices(p, num_buckets, rng)) { buckets[bucket] += 1; } } @@ -48,11 +50,12 @@ TEST(probability_util, sample_hit_indices) { } TEST_EACH_WORD_SIZE_W(probability_util, biased_random, { + auto rng = INDEPENDENT_TEST_RNG(); std::vector probs{0, 0.01, 0.03, 0.1, 0.4, 0.49, 0.5, 0.6, 0.9, 0.99, 0.999, 1}; simd_bits data(1000000); size_t n = data.num_bits_padded(); for (auto p : probs) { - biased_randomize_bits(p, data.u64, data.u64 + data.num_u64_padded(), SHARED_TEST_RNG()); + biased_randomize_bits(p, data.u64, data.u64 + data.num_u64_padded(), rng); size_t t = 0; for (size_t k = 0; k < data.num_u64_padded(); k++) { t += popcnt64(data.u64[k]); diff --git a/src/stim/simulators/count_determined_measurements.inl b/src/stim/simulators/count_determined_measurements.inl index e38ba6253..d9c9bcd69 100644 --- a/src/stim/simulators/count_determined_measurements.inl +++ b/src/stim/simulators/count_determined_measurements.inl @@ -6,9 +6,8 @@ namespace stim { template uint64_t count_determined_measurements(const Circuit &circuit) { uint64_t result = 0; - std::mt19937_64 irrelevant_rng{0}; auto n = circuit.count_qubits(); - TableauSimulator sim(irrelevant_rng, n); + TableauSimulator sim(std::mt19937_64{0}, n); PauliString obs_buffer(n); circuit.for_each_operation([&](const CircuitInstruction &inst) { diff --git a/src/stim/simulators/dem_sampler.test.cc b/src/stim/simulators/dem_sampler.test.cc index 1a7163608..c1c2b649a 100644 --- a/src/stim/simulators/dem_sampler.test.cc +++ b/src/stim/simulators/dem_sampler.test.cc @@ -57,7 +57,7 @@ TEST_EACH_WORD_SIZE_W(DemSampler, resample_basic_probabilities, { error(0.75) D3 error(1) D4 ^ D5 )DEM"), - SHARED_TEST_RNG(), + INDEPENDENT_TEST_RNG(), 1000); for (size_t k = 0; k < 2; k++) { sampler.resample(false); @@ -82,7 +82,7 @@ TEST_EACH_WORD_SIZE_W(DemSampler, resample_combinations, { error(0.2) D1 D2 error(0.3) D2 D0 )DEM"), - SHARED_TEST_RNG(), + INDEPENDENT_TEST_RNG(), 1000); for (size_t k = 0; k < 2; k++) { sampler.resample(false); diff --git a/src/stim/simulators/error_analyzer.test.cc b/src/stim/simulators/error_analyzer.test.cc index a7a614af5..927fedae5 100644 --- a/src/stim/simulators/error_analyzer.test.cc +++ b/src/stim/simulators/error_analyzer.test.cc @@ -294,7 +294,7 @@ TEST_EACH_WORD_SIZE_W(ErrorAnalyzer, unitary_gates_match_frame_simulator, { CircuitStats stats; stats.num_qubits = 16; stats.num_measurements = 100; - FrameSimulator f(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 16, SHARED_TEST_RNG()); + FrameSimulator f(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 16, INDEPENDENT_TEST_RNG()); ErrorAnalyzer e(100, 1, 16, 100, false, false, false, 0.0, false, true); for (size_t q = 0; q < 16; q++) { if (q & 1) { diff --git a/src/stim/simulators/frame_simulator.h b/src/stim/simulators/frame_simulator.h index 0927240fc..04e32efdb 100644 --- a/src/stim/simulators/frame_simulator.h +++ b/src/stim/simulators/frame_simulator.h @@ -54,7 +54,7 @@ struct FrameSimulator { simd_bits tmp_storage; // Workspace used when sampling compound error processes. simd_bits last_correlated_error_occurred; // correlated error flag for each instance. simd_bit_table sweep_table; // Shot-to-shot configuration data. - std::mt19937_64 &rng; // Random number generator used for generating entropy. + std::mt19937_64 rng; // Random number generator used for generating entropy. // Determines whether e.g. 50% Z errors are multiplied into the frame when measuring in the Z basis. // This is necessary for correct sampling. @@ -71,7 +71,7 @@ struct FrameSimulator { /// of buffers. /// batch_size: How many shots to simulate simultaneously. /// rng: The random number generator to pull noise from. - FrameSimulator(CircuitStats circuit_stats, FrameSimulatorMode mode, size_t batch_size, std::mt19937_64 &rng); + FrameSimulator(CircuitStats circuit_stats, FrameSimulatorMode mode, size_t batch_size, std::mt19937_64 &&rng); FrameSimulator() = delete; PauliString get_frame(size_t sample_index) const; diff --git a/src/stim/simulators/frame_simulator.inl b/src/stim/simulators/frame_simulator.inl index 7f3c42bf1..67ac9f998 100644 --- a/src/stim/simulators/frame_simulator.inl +++ b/src/stim/simulators/frame_simulator.inl @@ -39,7 +39,7 @@ inline void for_each_target_pair(FrameSimulator &sim, const CircuitInstructio template FrameSimulator::FrameSimulator( - CircuitStats circuit_stats, FrameSimulatorMode mode, size_t batch_size, std::mt19937_64 &rng) + CircuitStats circuit_stats, FrameSimulatorMode mode, size_t batch_size, std::mt19937_64 &&rng) : num_qubits(0), keeping_detection_data(false), batch_size(0), @@ -52,7 +52,7 @@ FrameSimulator::FrameSimulator( tmp_storage(0), last_correlated_error_occurred(0), sweep_table(0, 0), - rng(rng) { + rng(std::move(rng)) { configure_for(circuit_stats, mode, batch_size); } diff --git a/src/stim/simulators/frame_simulator.test.cc b/src/stim/simulators/frame_simulator.test.cc index 214251a7b..57279cc07 100644 --- a/src/stim/simulators/frame_simulator.test.cc +++ b/src/stim/simulators/frame_simulator.test.cc @@ -28,7 +28,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, get_set_frame, { CircuitStats circuit_stats; circuit_stats.num_qubits = 6; circuit_stats.max_lookback = 999; - FrameSimulator sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 4, SHARED_TEST_RNG()); + FrameSimulator sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 4, INDEPENDENT_TEST_RNG()); ASSERT_EQ(sim.get_frame(0), PauliString::from_str("______")); ASSERT_EQ(sim.get_frame(1), PauliString::from_str("______")); ASSERT_EQ(sim.get_frame(2), PauliString::from_str("______")); @@ -46,7 +46,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, get_set_frame, { circuit_stats.num_qubits = 501; circuit_stats.max_lookback = 999; - FrameSimulator big_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1001, SHARED_TEST_RNG()); + FrameSimulator big_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1001, INDEPENDENT_TEST_RNG()); big_sim.set_frame(258, PauliString::from_func(false, 501, [](size_t k) { return "_X"[k == 303]; })); @@ -62,15 +62,16 @@ bool is_bulk_frame_operation_consistent_with_tableau(const Gate &gate) { circuit_stats.num_qubits = 500; circuit_stats.max_lookback = 10; size_t num_samples = 1000; - FrameSimulator sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1000, SHARED_TEST_RNG()); + FrameSimulator sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1000, INDEPENDENT_TEST_RNG()); size_t num_targets = tableau.num_qubits; assert(num_targets == 1 || num_targets == 2); std::vector targets{{101}, {403}, {202}, {100}}; while (targets.size() > num_targets) { targets.pop_back(); } + auto rng = INDEPENDENT_TEST_RNG(); for (size_t k = 7; k < num_samples; k += 101) { - auto test_value = PauliString::random(circuit_stats.num_qubits, SHARED_TEST_RNG()); + auto test_value = PauliString::random(circuit_stats.num_qubits, rng); PauliStringRef test_value_ref(test_value); sim.set_frame(k, test_value); sim.do_gate({gate.id, {}, targets}); @@ -103,7 +104,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, bulk_operations_consistent_with_tableau_da template bool is_output_possible_promising_no_bare_resets(const Circuit &circuit, const simd_bits_range_ref output) { - auto tableau_sim = TableauSimulator(SHARED_TEST_RNG(), circuit.count_qubits()); + auto tableau_sim = TableauSimulator(INDEPENDENT_TEST_RNG(), circuit.count_qubits()); size_t out_p = 0; bool pass = true; circuit.for_each_operation([&](const CircuitInstruction &op) { @@ -143,9 +144,10 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, test_util_is_output_possible, { template bool is_sim_frame_consistent_with_sim_tableau(const char *program_text) { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(program_text); auto reference_sample = TableauSimulator::reference_sample_circuit(circuit); - auto samples = sample_batch_measurements(circuit, reference_sample, 10, SHARED_TEST_RNG(), true); + auto samples = sample_batch_measurements(circuit, reference_sample, 10, rng, true); for (size_t k = 0; k < 10; k++) { simd_bits_range_ref sample = samples[k]; @@ -284,6 +286,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, consistency, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, sample_batch_measurements_writing_results_to_disk, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit( "X 0\n" "M 1\n" @@ -291,17 +294,17 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, sample_batch_measurements_writing_results_ "M 2\n" "M 3\n"); auto ref = TableauSimulator::reference_sample_circuit(circuit); - auto r = sample_batch_measurements(circuit, ref, 10, SHARED_TEST_RNG(), true); + auto r = sample_batch_measurements(circuit, ref, 10, rng, true); for (size_t k = 0; k < 10; k++) { ASSERT_EQ(r[k].u64[0], 2); } FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 5, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 5, tmp, SAMPLE_FORMAT_01, rng); ASSERT_EQ(rewind_read_close(tmp), "0100\n0100\n0100\n0100\n0100\n"); tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 5, tmp, SAMPLE_FORMAT_B8, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 5, tmp, SAMPLE_FORMAT_B8, rng); rewind(tmp); for (size_t k = 0; k < 5; k++) { ASSERT_EQ(getc(tmp), 2); @@ -309,7 +312,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, sample_batch_measurements_writing_results_ ASSERT_EQ(getc(tmp), EOF); tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 64, tmp, SAMPLE_FORMAT_PTB64, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 64, tmp, SAMPLE_FORMAT_PTB64, rng); rewind(tmp); for (size_t k = 0; k < 8; k++) { ASSERT_EQ(getc(tmp), 0); @@ -325,6 +328,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, sample_batch_measurements_writing_results_ }) TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_measurements, { + auto rng = INDEPENDENT_TEST_RNG(); Circuit circuit; for (uint32_t k = 0; k < 1250; k += 3) { circuit.safe_append_u("X", {k}); @@ -333,7 +337,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_measurements, { circuit.safe_append_u("M", {k}); } auto ref = TableauSimulator::reference_sample_circuit(circuit); - auto r = sample_batch_measurements(circuit, ref, 750, SHARED_TEST_RNG(), true); + auto r = sample_batch_measurements(circuit, ref, 750, rng, true); for (size_t i = 0; i < 750; i++) { for (size_t k = 0; k < 1250; k++) { ASSERT_EQ(r[i][k], k % 3 == 0) << k; @@ -341,7 +345,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_measurements, { } FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 750, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 750, tmp, SAMPLE_FORMAT_01, rng); rewind(tmp); for (size_t s = 0; s < 750; s++) { for (size_t k = 0; k < 1250; k++) { @@ -352,7 +356,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_measurements, { ASSERT_EQ(getc(tmp), EOF); tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 750, tmp, SAMPLE_FORMAT_B8, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 750, tmp, SAMPLE_FORMAT_B8, rng); rewind(tmp); for (size_t s = 0; s < 750; s++) { for (size_t k = 0; k < 1250; k += 8) { @@ -366,6 +370,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_measurements, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, run_length_measurement_formats, { + auto rng = INDEPENDENT_TEST_RNG(); Circuit circuit; circuit.safe_append_u("X", {100, 500, 501, 551, 1200}); for (uint32_t k = 0; k < 1250; k++) { @@ -374,17 +379,17 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, run_length_measurement_formats, { auto ref = TableauSimulator::reference_sample_circuit(circuit); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_HITS, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_HITS, rng); ASSERT_EQ(rewind_read_close(tmp), "100,500,501,551,1200\n100,500,501,551,1200\n100,500,501,551,1200\n"); tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_DETS, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_DETS, rng); ASSERT_EQ( rewind_read_close(tmp), "shot M100 M500 M501 M551 M1200\nshot M100 M500 M501 M551 M1200\nshot M100 M500 M501 M551 M1200\n"); tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_R8, SHARED_TEST_RNG()); + sample_batch_measurements_writing_results_to_disk(circuit, ref, 3, tmp, SAMPLE_FORMAT_R8, rng); rewind(tmp); for (size_t k = 0; k < 3; k++) { ASSERT_EQ(getc(tmp), 100); @@ -401,6 +406,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, run_length_measurement_formats, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_random_measurements, { + auto rng = INDEPENDENT_TEST_RNG(); Circuit circuit; for (uint32_t k = 0; k < 270; k++) { circuit.safe_append_u("H_XZ", {k}); @@ -409,13 +415,14 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, big_circuit_random_measurements, { circuit.safe_append_u("M", {k}); } auto ref = TableauSimulator::reference_sample_circuit(circuit); - auto r = sample_batch_measurements(circuit, ref, 1000, SHARED_TEST_RNG(), true); + auto r = sample_batch_measurements(circuit, ref, 1000, rng, true); for (size_t k = 0; k < 1000; k++) { ASSERT_TRUE(r[k].not_zero()) << k; } }) TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits ref(5); simd_bits expected(5); @@ -430,7 +437,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -447,7 +454,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -464,7 +471,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -481,7 +488,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -498,7 +505,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -515,7 +522,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -527,28 +534,27 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { ASSERT_EQ( sample_batch_measurements( Circuit(R"circuit( - CORRELATED_ERROR(1) X0 X1 - ELSE_CORRELATED_ERROR(1) X1 X2 - ELSE_CORRELATED_ERROR(1) X2 X3 - CORRELATED_ERROR(1) X3 X4 - M 0 1 2 3 4 - )circuit"), + CORRELATED_ERROR(1) X0 X1 + ELSE_CORRELATED_ERROR(1) X1 X2 + ELSE_CORRELATED_ERROR(1) X2 X3 + CORRELATED_ERROR(1) X3 X4 + M 0 1 2 3 4 + )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); int hits[3]{}; - std::mt19937_64 rng(0); size_t n = 10000; auto samples = sample_batch_measurements( Circuit(R"circuit( - CORRELATED_ERROR(0.5) X0 - ELSE_CORRELATED_ERROR(0.25) X1 - ELSE_CORRELATED_ERROR(0.75) X2 - M 0 1 2 - )circuit"), + CORRELATED_ERROR(0.5) X0 + ELSE_CORRELATED_ERROR(0.25) X1 + ELSE_CORRELATED_ERROR(0.75) X2 + M 0 1 2 + )circuit"), ref, n, rng, @@ -564,6 +570,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, correlated_error, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits ref(128); // Quantum controlling classical operation is not allowed. @@ -576,7 +583,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true); }, std::invalid_argument); @@ -589,7 +596,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true); }, std::invalid_argument); @@ -602,7 +609,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true); }, std::invalid_argument); @@ -615,7 +622,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true); }, std::invalid_argument); @@ -628,13 +635,14 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, quantum_cannot_control_classical, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true); }, std::invalid_argument); }) TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_can_control_quantum, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits ref(128); simd_bits expected(5); expected.clear(); @@ -650,7 +658,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_can_control_quantum, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); ASSERT_EQ( @@ -663,7 +671,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_can_control_quantum, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); ASSERT_EQ( @@ -676,7 +684,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_can_control_quantum, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); ASSERT_EQ( @@ -689,12 +697,13 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_can_control_quantum, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); }) TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits ref(128); simd_bits expected(5); @@ -708,7 +717,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -722,7 +731,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -739,7 +748,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -756,7 +765,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); auto r = sample_batch_measurements( @@ -768,7 +777,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1000, - SHARED_TEST_RNG(), + rng, true); size_t hits = 0; for (size_t k = 0; k < 1000; k++) { @@ -792,7 +801,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); @@ -809,14 +818,14 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { )circuit"), ref, 1, - SHARED_TEST_RNG(), + rng, true)[0], expected); }) TEST_EACH_WORD_SIZE_W(FrameSimulator, record_gets_trimmed, { Circuit c = Circuit("M 0 1 2 3 4 5 6 7 8 9"); - FrameSimulator sim(c.compute_stats(), FrameSimulatorMode::STREAM_MEASUREMENTS_TO_DISK, 768, SHARED_TEST_RNG()); + FrameSimulator sim(c.compute_stats(), FrameSimulatorMode::STREAM_MEASUREMENTS_TO_DISK, 768, INDEPENDENT_TEST_RNG()); MeasureRecordBatchWriter b(tmpfile(), 768, SAMPLE_FORMAT_B8); for (size_t k = 0; k < 1000; k++) { sim.do_MZ(c.operations[0]); @@ -826,6 +835,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, record_gets_trimmed, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_huge_case, { + auto rng = INDEPENDENT_TEST_RNG(); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( Circuit(R"CIRCUIT( @@ -838,7 +848,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_huge_case, { 256, tmp, SAMPLE_FORMAT_B8, - SHARED_TEST_RNG()); + rng); rewind(tmp); for (size_t k = 0; k < 256 * 100000 * 4 / 8; k++) { ASSERT_EQ(getc(tmp), 0x44); @@ -847,6 +857,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_huge_case, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_single_shot, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( REPEAT 10000 { X_ERROR(1) 0 @@ -856,7 +867,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_single_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 30000; k += 3) { @@ -868,6 +879,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_single_shot, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_triple_shot, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( REPEAT 10000 { X_ERROR(1) 0 @@ -877,7 +889,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_triple_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -892,6 +904,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_triple_shot, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( REPEAT 10000 { @@ -902,7 +915,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results, { )circuit"); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 30000; k += 3) { @@ -914,6 +927,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_many_shots, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( X_ERROR(1) 1 @@ -921,7 +935,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_many_shots, { )circuit"); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 2049, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + circuit, simd_bits(0), 2049, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); ASSERT_EQ(result.size(), 2049 * 4); @@ -934,6 +948,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_many_shots, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results_triple_shot, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( REPEAT 10000 { @@ -944,7 +959,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results_triple_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG()); + circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -959,6 +974,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results_triple_shot, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_y_without_reset_doesnt_reset, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RY 0 @@ -973,7 +989,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_y_without_reset_doesnt_reset, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_EQ(r[0].popcnt(), 0); ASSERT_EQ(r[1].popcnt(), 0); @@ -996,7 +1012,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_y_without_reset_doesnt_reset, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_EQ(r[0].popcnt(), 0); ASSERT_EQ(r[1].popcnt(), 0); @@ -1007,12 +1023,13 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_y_without_reset_doesnt_reset, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, resets_vs_measurements, { + auto rng = INDEPENDENT_TEST_RNG(); auto check = [&](const char *circuit, std::vector results) { simd_bits ref(results.size()); for (size_t k = 0; k < results.size(); k++) { ref[k] = results[k]; } - simd_bit_table t = sample_batch_measurements(Circuit(circuit), ref, 100, SHARED_TEST_RNG(), true); + simd_bit_table t = sample_batch_measurements(Circuit(circuit), ref, 100, rng, true); return !t.data.not_zero(); }; @@ -1156,6 +1173,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, resets_vs_measurements, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_x, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RX 0 @@ -1164,7 +1182,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_x, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1180,7 +1198,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_x, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1190,6 +1208,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_x, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_y, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RY 0 @@ -1198,7 +1217,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_y, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1214,7 +1233,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_y, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1224,6 +1243,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_y, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_z, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RZ 0 @@ -1232,7 +1252,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_z, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1248,7 +1268,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_z, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1258,6 +1278,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_z, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_x, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RX 0 @@ -1266,7 +1287,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_x, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1282,7 +1303,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_x, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1292,6 +1313,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_x, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_y, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RY 0 @@ -1300,7 +1322,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_y, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1316,7 +1338,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_y, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1326,6 +1348,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_y, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_z, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( RZ 0 @@ -1334,7 +1357,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_z, { )CIRCUIT"), simd_bits(0), 10000, - SHARED_TEST_RNG(), + rng, false); ASSERT_FALSE(r[1].not_zero()); auto m1 = r[0].popcnt(); @@ -1350,7 +1373,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_z, { )CIRCUIT"), simd_bits(0), 5000, - SHARED_TEST_RNG(), + rng, false); auto m2 = r[0].popcnt() + r[1].popcnt(); ASSERT_LT(m2, 10000 - 300); @@ -1360,6 +1383,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, noisy_measurement_reset_z, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_pauli_product_4body, { + auto rng = INDEPENDENT_TEST_RNG(); auto r = sample_batch_measurements( Circuit(R"CIRCUIT( X_ERROR(0.5) 0 1 2 3 @@ -1371,7 +1395,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_pauli_product_4body, { )CIRCUIT"), simd_bits(0), 10, - SHARED_TEST_RNG(), + rng, false); for (size_t k = 0; k < 10; k++) { auto x0123 = r[0][k]; @@ -1391,6 +1415,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, measure_pauli_product_4body, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, non_deterministic_pauli_product_detectors, { + auto rng = INDEPENDENT_TEST_RNG(); auto n = sample_batch_measurements( Circuit(R"CIRCUIT( MPP Z8*X9 @@ -1398,7 +1423,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, non_deterministic_pauli_product_detectors, )CIRCUIT"), simd_bits(0), 1000, - SHARED_TEST_RNG(), + rng, false)[0] .popcnt(); ASSERT_TRUE(400 < n && n < 600); @@ -1410,7 +1435,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, non_deterministic_pauli_product_detectors, )CIRCUIT"), simd_bits(0), 1000, - SHARED_TEST_RNG(), + rng, false)[0] .popcnt(); ASSERT_TRUE(400 < n && n < 600); @@ -1422,13 +1447,14 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, non_deterministic_pauli_product_detectors, )CIRCUIT"), simd_bits(0), 1000, - SHARED_TEST_RNG(), + rng, false)[0] .popcnt(); ASSERT_TRUE(400 < n && n < 600); }) TEST_EACH_WORD_SIZE_W(FrameSimulator, ignores_sweep_controls_when_given_no_sweep_data, { + auto rng = INDEPENDENT_TEST_RNG(); auto n = sample_batch_measurements( Circuit(R"CIRCUIT( CNOT sweep[0] 0 @@ -1437,7 +1463,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, ignores_sweep_controls_when_given_no_sweep )CIRCUIT"), simd_bits(0), 1000, - SHARED_TEST_RNG(), + rng, false)[0] .popcnt(); ASSERT_EQ(n, 0); @@ -1451,18 +1477,19 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, reconfigure_for, { )CIRCUIT"); FrameSimulator frame_sim( - circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, SHARED_TEST_RNG()); + circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, INDEPENDENT_TEST_RNG()); frame_sim.configure_for(circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 256); frame_sim.reset_all_and_run(circuit); ASSERT_EQ(frame_sim.det_record.storage[0].popcnt(), 256); }) TEST_EACH_WORD_SIZE_W(FrameSimulator, mpad, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"CIRCUIT( MPAD 0 1 )CIRCUIT"); auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, SHARED_TEST_RNG(), false); + circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[0][k], false); ASSERT_EQ(sample[1][k], true); @@ -1470,6 +1497,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mpad, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_basis, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"CIRCUIT( RX 0 1 MXX 0 1 @@ -1479,7 +1507,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_basis, { MZZ 0 1 )CIRCUIT"); auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, SHARED_TEST_RNG(), false); + circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[0][k], false); ASSERT_EQ(sample[1][k], false); @@ -1488,13 +1516,14 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_basis, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_inversion, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"CIRCUIT( MXX 0 1 0 !1 !0 1 !0 !1 MYY 0 1 0 !1 !0 1 !0 !1 MZZ 0 1 0 !1 !0 1 !0 !1 )CIRCUIT"); auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, SHARED_TEST_RNG(), false); + circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[1][k], !sample[0][k]); ASSERT_EQ(sample[2][k], !sample[0][k]); @@ -1510,9 +1539,10 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_inversion, { }) TEST_EACH_WORD_SIZE_W(FrameSimulator, runs_on_general_circuit, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = generate_test_circuit_with_all_operations(); auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, SHARED_TEST_RNG(), false); + circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); ASSERT_GT(sample.num_simd_words_minor, 0); ASSERT_GT(sample.num_simd_words_major, 0); }) @@ -1531,7 +1561,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, heralded_erase_detect_statistics, { size_t n; std::array bins{}; FrameSimulator sim( - circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1024, SHARED_TEST_RNG()); + circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1024, INDEPENDENT_TEST_RNG()); for (n = 0; n < 1024 * 256; n += 1024) { sim.reset_all_and_run(circuit); auto sample = sim.det_record.storage.transposed(); diff --git a/src/stim/simulators/frame_simulator_util.inl b/src/stim/simulators/frame_simulator_util.inl index d4366f0c2..3607ceb94 100644 --- a/src/stim/simulators/frame_simulator_util.inl +++ b/src/stim/simulators/frame_simulator_util.inl @@ -22,8 +22,9 @@ namespace stim { template std::pair, simd_bit_table> sample_batch_detection_events( const Circuit &circuit, size_t num_shots, std::mt19937_64 &rng) { - FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_shots, rng); + FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_shots, std::move(rng)); sim.reset_all_and_run(circuit); + rng = std::move(sim.rng); // Update input rng as if it was used directly, by moving the updated state out of the simulator. return std::pair, simd_bit_table>{ std::move(sim.det_record.storage), @@ -237,7 +238,7 @@ void sample_batch_detection_events_writing_results_to_disk( stats, streaming ? FrameSimulatorMode::STREAM_DETECTIONS_TO_DISK : FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, batch_size, - rng); + std::move(rng)); // Will copy rng state back out later. // Run the frame simulator until as many shots as requested have been written. simd_bit_table out_concat_buf(0, 0); @@ -275,6 +276,9 @@ void sample_batch_detection_events_writing_results_to_disk( } shots_left -= shots_performed; } + + // Update input rng as if it was used directly, by moving the updated state out of the simulator. + rng = std::move(frame_sim.rng); } template @@ -284,9 +288,10 @@ simd_bit_table sample_batch_measurements( size_t num_samples, std::mt19937_64 &rng, bool transposed) { - FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, num_samples, rng); + FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, num_samples, std::move(rng)); sim.reset_all_and_run(circuit); simd_bit_table result = std::move(sim.m_record.storage); + rng = std::move(sim.rng); // Update input rng as if it was used directly, by moving the updated state out of the simulator. if (reference_sample.not_zero()) { result = transposed_vs_ref(num_samples, result, reference_sample); @@ -337,7 +342,7 @@ void sample_batch_measurements_writing_results_to_disk( circuit.compute_stats(), streaming ? FrameSimulatorMode::STREAM_MEASUREMENTS_TO_DISK : FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, batch_size, - rng); + std::move(rng)); // Temporarily move rng into simulator. // Run the frame simulator until as many shots as requested have been written. size_t shots_left = num_shots; @@ -352,6 +357,9 @@ void sample_batch_measurements_writing_results_to_disk( } shots_left -= shots_performed; } + + // Update input rng as if it was used directly, by moving the updated state out of the simulator. + rng = std::move(frame_sim.rng); } } // namespace stim diff --git a/src/stim/simulators/frame_simulator_util.test.cc b/src/stim/simulators/frame_simulator_util.test.cc index e555a9008..651de8a79 100644 --- a/src/stim/simulators/frame_simulator_util.test.cc +++ b/src/stim/simulators/frame_simulator_util.test.cc @@ -24,7 +24,8 @@ using namespace stim; template simd_bit_table sample_test_detection_events(const Circuit &circuit, size_t num_shots) { - return sample_batch_detection_events(circuit, num_shots, SHARED_TEST_RNG()).first; + auto rng = INDEPENDENT_TEST_RNG(); + return sample_batch_detection_events(circuit, num_shots, rng).first; } TEST_EACH_WORD_SIZE_W(DetectionSimulator, sample_test_detection_events, { @@ -47,6 +48,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, bad_detector, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, sample_batch_detection_events_writing_results_to_disk, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( X_ERROR(1) 0 M 0 1 @@ -57,26 +59,27 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, sample_batch_detection_events_writing_ FILE *tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 2, false, false, tmp, SAMPLE_FORMAT_DETS, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 2, false, false, tmp, SAMPLE_FORMAT_DETS, rng, nullptr, SAMPLE_FORMAT_01); ASSERT_EQ(rewind_read_close(tmp), "shot D1\nshot D1\n"); tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 2, true, false, tmp, SAMPLE_FORMAT_DETS, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 2, true, false, tmp, SAMPLE_FORMAT_DETS, rng, nullptr, SAMPLE_FORMAT_01); ASSERT_EQ(rewind_read_close(tmp), "shot L4 D1\nshot L4 D1\n"); tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 2, false, true, tmp, SAMPLE_FORMAT_DETS, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 2, false, true, tmp, SAMPLE_FORMAT_DETS, rng, nullptr, SAMPLE_FORMAT_01); ASSERT_EQ(rewind_read_close(tmp), "shot D1 L4\nshot D1 L4\n"); tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 2, false, true, tmp, SAMPLE_FORMAT_HITS, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 2, false, true, tmp, SAMPLE_FORMAT_HITS, rng, nullptr, SAMPLE_FORMAT_01); ASSERT_EQ(rewind_read_close(tmp), "1,6\n1,6\n"); }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_many_shots, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( X_ERROR(1) 1 @@ -87,7 +90,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_many_shots, { )circuit"); FILE *tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 2048, false, false, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 2048, false, false, tmp, SAMPLE_FORMAT_01, rng, nullptr, SAMPLE_FORMAT_01); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 2048 * 4; k += 4) { @@ -99,6 +102,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_many_shots, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_single_shot, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( REPEAT 10000 { M 0 @@ -113,7 +117,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_single_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 1, false, true, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 1, false, true, tmp, SAMPLE_FORMAT_01, rng, nullptr, SAMPLE_FORMAT_01); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 30000; k += 3) { @@ -125,6 +129,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_single_shot, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_triple_shot, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( REPEAT 10000 { M 0 @@ -139,7 +144,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_triple_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 3, false, true, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 3, false, true, tmp, SAMPLE_FORMAT_01, rng, nullptr, SAMPLE_FORMAT_01); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -154,6 +159,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, block_results_triple_shot, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_results, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( REPEAT 10000 { @@ -171,7 +177,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_results, { RaiiTempNamedFile tmp; FILE *f = fopen(tmp.path.c_str(), "w"); sample_batch_detection_events_writing_results_to_disk( - circuit, 1, false, true, f, SAMPLE_FORMAT_01, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 1, false, true, f, SAMPLE_FORMAT_01, rng, nullptr, SAMPLE_FORMAT_01); fclose(f); auto result = tmp.read_contents(); @@ -184,6 +190,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_results, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_results_triple_shot, { + auto rng = INDEPENDENT_TEST_RNG(); DebugForceResultStreamingRaii force_streaming; auto circuit = Circuit(R"circuit( REPEAT 10000 { @@ -199,7 +206,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, stream_results_triple_shot, { )circuit"); FILE *tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 3, false, true, tmp, SAMPLE_FORMAT_01, SHARED_TEST_RNG(), nullptr, SAMPLE_FORMAT_01); + circuit, 3, false, true, tmp, SAMPLE_FORMAT_01, rng, nullptr, SAMPLE_FORMAT_01); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -418,6 +425,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, noisy_measure_reset_z, { }) TEST_EACH_WORD_SIZE_W(DetectionSimulator, obs_data, { + auto rng = INDEPENDENT_TEST_RNG(); auto circuit = Circuit(R"circuit( REPEAT 399 { X_ERROR(1) 0 @@ -439,7 +447,7 @@ TEST_EACH_WORD_SIZE_W(DetectionSimulator, obs_data, { FILE *det_tmp = tmpfile(); FILE *obs_tmp = tmpfile(); sample_batch_detection_events_writing_results_to_disk( - circuit, 1001, false, false, det_tmp, SAMPLE_FORMAT_B8, SHARED_TEST_RNG(), obs_tmp, SAMPLE_FORMAT_B8); + circuit, 1001, false, false, det_tmp, SAMPLE_FORMAT_B8, rng, obs_tmp, SAMPLE_FORMAT_B8); auto det_saved = rewind_read_close(det_tmp); auto obs_saved = rewind_read_close(obs_tmp); diff --git a/src/stim/simulators/measurements_to_detection_events.inl b/src/stim/simulators/measurements_to_detection_events.inl index a6d271407..535198f73 100644 --- a/src/stim/simulators/measurements_to_detection_events.inl +++ b/src/stim/simulators/measurements_to_detection_events.inl @@ -58,9 +58,7 @@ void measurements_to_detection_events_helper( // The frame simulator is used to account for flips in the measurement results that originate from the sweep data. // Eg. a `CNOT sweep[5] 0` can bit flip qubit 0, which can invert later measurement results, which will invert the // expected parity of detectors involving that measurement. This can vary from shot to shot. - std::mt19937_64 rng1(0); - std::mt19937_64 rng2(0); // Used to sanity check that rng1 isn't called. - FrameSimulator frame_sim(circuit_stats, FrameSimulatorMode::STREAM_DETECTIONS_TO_DISK, batch_size, rng1); + FrameSimulator frame_sim(circuit_stats, FrameSimulatorMode::STREAM_DETECTIONS_TO_DISK, batch_size, std::mt19937_64(0)); frame_sim.sweep_table = sweep_bits__minor_shot_index; frame_sim.guarantee_anticommutation_via_frame_randomization = false; @@ -121,7 +119,8 @@ void measurements_to_detection_events_helper( } // Safety check verifying no randomness was used by the frame simulator. - if (rng1() != rng2()) { + std::mt19937_64 fresh_rng(0); + if (frame_sim.rng() != fresh_rng() || frame_sim.rng() != fresh_rng() || frame_sim.rng() != fresh_rng()) { throw std::invalid_argument("Something is wrong. Converting measurements consumed entropy, but it shouldn't."); } } diff --git a/src/stim/simulators/sparse_rev_frame_tracker.test.cc b/src/stim/simulators/sparse_rev_frame_tracker.test.cc index 0ff2c3f83..c638a7d9a 100644 --- a/src/stim/simulators/sparse_rev_frame_tracker.test.cc +++ b/src/stim/simulators/sparse_rev_frame_tracker.test.cc @@ -142,7 +142,7 @@ static std::vector qubit_targets(const std::vector &target } TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, fuzz_all_unitary_gates_vs_tableau, { - auto &rng = SHARED_TEST_RNG(); + auto rng = INDEPENDENT_TEST_RNG(); for (const auto &gate : GATE_DATA.items) { if (gate.flags & GATE_IS_UNITARY) { size_t n = (gate.flags & GATE_TARGETS_PAIRS) ? 2 : 1; diff --git a/src/stim/simulators/tableau_simulator.h b/src/stim/simulators/tableau_simulator.h index 49fdc6380..b9e7ca123 100644 --- a/src/stim/simulators/tableau_simulator.h +++ b/src/stim/simulators/tableau_simulator.h @@ -51,11 +51,11 @@ struct TableauSimulator { /// sign_bias: 0 means collapse randomly, -1 means collapse towards True, +1 means collapse towards False. /// record: Measurement record configuration. explicit TableauSimulator( - std::mt19937_64 rng, size_t num_qubits = 0, int8_t sign_bias = 0, MeasureRecord record = MeasureRecord()); + std::mt19937_64 &&rng, size_t num_qubits = 0, int8_t sign_bias = 0, MeasureRecord record = MeasureRecord()); /// Args: /// other: TableauSimulator to copy state from. /// rng: The random number generator to use for random operations. - TableauSimulator(const TableauSimulator &other, std::mt19937_64 rng); + TableauSimulator(const TableauSimulator &other, std::mt19937_64 &&rng); /// Samples the given circuit in a deterministic fashion. /// diff --git a/src/stim/simulators/tableau_simulator.inl b/src/stim/simulators/tableau_simulator.inl index 3717a05ce..27e42117a 100644 --- a/src/stim/simulators/tableau_simulator.inl +++ b/src/stim/simulators/tableau_simulator.inl @@ -24,7 +24,7 @@ namespace stim { template -TableauSimulator::TableauSimulator(std::mt19937_64 rng, size_t num_qubits, int8_t sign_bias, MeasureRecord record) +TableauSimulator::TableauSimulator(std::mt19937_64 &&rng, size_t num_qubits, int8_t sign_bias, MeasureRecord record) : inv_state(Tableau::identity(num_qubits)), rng(std::move(rng)), sign_bias(sign_bias), @@ -33,7 +33,7 @@ TableauSimulator::TableauSimulator(std::mt19937_64 rng, size_t num_qubits, in } template -TableauSimulator::TableauSimulator(const TableauSimulator &other, std::mt19937_64 rng) +TableauSimulator::TableauSimulator(const TableauSimulator &other, std::mt19937_64 &&rng) : inv_state(other.inv_state), rng(std::move(rng)), sign_bias(other.sign_bias), diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc index 0d4610030..d77a5eff9 100644 --- a/src/stim/simulators/tableau_simulator.pybind.cc +++ b/src/stim/simulators/tableau_simulator.pybind.cc @@ -562,7 +562,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "h", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_H_XZ(build_single_qubit_gate_instruction_ensure_size(self, GateType::H, args)); }, clean_doc_string(R"DOC( @@ -682,7 +682,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "h_xz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_H_XZ(build_single_qubit_gate_instruction_ensure_size(self, GateType::H, args)); }, clean_doc_string(R"DOC( @@ -695,7 +695,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "c_xyz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_C_XYZ( build_single_qubit_gate_instruction_ensure_size(self, GateType::C_XYZ, args)); }, @@ -709,7 +709,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "c_zyx", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_C_ZYX( build_single_qubit_gate_instruction_ensure_size(self, GateType::C_ZYX, args)); }, @@ -723,7 +723,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "h_xy", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_H_XY( build_single_qubit_gate_instruction_ensure_size(self, GateType::H_XY, args)); }, @@ -737,7 +737,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "h_yz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_H_YZ( build_single_qubit_gate_instruction_ensure_size(self, GateType::H_YZ, args)); }, @@ -751,7 +751,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "x", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_X(build_single_qubit_gate_instruction_ensure_size(self, GateType::X, args)); }, clean_doc_string(R"DOC( @@ -764,7 +764,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "y", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_Y(build_single_qubit_gate_instruction_ensure_size(self, GateType::Y, args)); }, clean_doc_string(R"DOC( @@ -790,7 +790,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "s", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_Z(build_single_qubit_gate_instruction_ensure_size(self, GateType::S, args)); }, clean_doc_string(R"DOC( @@ -803,7 +803,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "s_dag", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_Z_DAG( build_single_qubit_gate_instruction_ensure_size(self, GateType::S_DAG, args)); }, @@ -817,7 +817,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "sqrt_x", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_X( build_single_qubit_gate_instruction_ensure_size(self, GateType::SQRT_X, args)); }, @@ -831,7 +831,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "sqrt_x_dag", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_X_DAG( build_single_qubit_gate_instruction_ensure_size(self, GateType::SQRT_X_DAG, args)); }, @@ -845,7 +845,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "sqrt_y", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_Y( build_single_qubit_gate_instruction_ensure_size(self, GateType::SQRT_Y, args)); }, @@ -859,7 +859,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "sqrt_y_dag", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SQRT_Y_DAG( build_single_qubit_gate_instruction_ensure_size(self, GateType::SQRT_Y_DAG, args)); }, @@ -873,7 +873,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "swap", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_SWAP(build_two_qubit_gate_instruction_ensure_size(self, GateType::SWAP, args)); }, clean_doc_string(R"DOC( @@ -888,7 +888,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "iswap", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ISWAP(build_two_qubit_gate_instruction_ensure_size(self, GateType::ISWAP, args)); }, clean_doc_string(R"DOC( @@ -903,7 +903,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "iswap_dag", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ISWAP_DAG( build_two_qubit_gate_instruction_ensure_size(self, GateType::ISWAP_DAG, args)); }, @@ -919,7 +919,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "cnot", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCX(build_two_qubit_gate_instruction_ensure_size(self, GateType::CX, args)); }, clean_doc_string(R"DOC( @@ -934,7 +934,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "zcx", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCX(build_two_qubit_gate_instruction_ensure_size(self, GateType::CX, args)); }, clean_doc_string(R"DOC( @@ -949,7 +949,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "cx", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCX(build_two_qubit_gate_instruction_ensure_size(self, GateType::CX, args)); }, clean_doc_string(R"DOC( @@ -964,7 +964,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "cz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCZ(build_two_qubit_gate_instruction_ensure_size(self, GateType::CZ, args)); }, clean_doc_string(R"DOC( @@ -979,7 +979,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "zcz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCZ(build_two_qubit_gate_instruction_ensure_size(self, GateType::CZ, args)); }, clean_doc_string(R"DOC( @@ -994,7 +994,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "cy", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCY(build_two_qubit_gate_instruction_ensure_size(self, GateType::CY, args)); }, clean_doc_string(R"DOC( @@ -1009,7 +1009,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "zcy", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_ZCY(build_two_qubit_gate_instruction_ensure_size(self, GateType::CY, args)); }, clean_doc_string(R"DOC( @@ -1024,7 +1024,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "xcx", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_XCX(build_two_qubit_gate_instruction_ensure_size(self, GateType::XCX, args)); }, clean_doc_string(R"DOC( @@ -1039,7 +1039,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "xcy", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_XCY(build_two_qubit_gate_instruction_ensure_size(self, GateType::XCY, args)); }, clean_doc_string(R"DOC( @@ -1054,7 +1054,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "xcz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_XCZ(build_two_qubit_gate_instruction_ensure_size(self, GateType::XCZ, args)); }, clean_doc_string(R"DOC( @@ -1069,7 +1069,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "ycx", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_YCX(build_two_qubit_gate_instruction_ensure_size(self, GateType::YCX, args)); }, clean_doc_string(R"DOC( @@ -1084,7 +1084,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "ycy", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_YCY(build_two_qubit_gate_instruction_ensure_size(self, GateType::YCY, args)); }, clean_doc_string(R"DOC( @@ -1099,7 +1099,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "ycz", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_YCZ(build_two_qubit_gate_instruction_ensure_size(self, GateType::YCZ, args)); }, clean_doc_string(R"DOC( @@ -1114,7 +1114,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "reset", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_RZ(build_single_qubit_gate_instruction_ensure_size(self, GateType::R, args)); }, clean_doc_string(R"DOC( @@ -1135,7 +1135,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "reset_x", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_RX(build_single_qubit_gate_instruction_ensure_size(self, GateType::RX, args)); }, clean_doc_string(R"DOC( @@ -1155,7 +1155,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "reset_y", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_RY(build_single_qubit_gate_instruction_ensure_size(self, GateType::RY, args)); }, clean_doc_string(R"DOC( @@ -1175,7 +1175,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "reset_z", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { self.do_RZ(build_single_qubit_gate_instruction_ensure_size(self, GateType::R, args)); }, clean_doc_string(R"DOC( @@ -1536,7 +1536,7 @@ void stim_pybind::pybind_tableau_simulator_methods( c.def( "measure_many", - [](TableauSimulator &self, pybind11::args args) { + [](TableauSimulator &self, const pybind11::args &args) { auto converted_args = build_single_qubit_gate_instruction_ensure_size(self, GateType::M, args); self.do_MZ(converted_args); diff --git a/src/stim/simulators/tableau_simulator.test.cc b/src/stim/simulators/tableau_simulator.test.cc index c3bc4211d..5e737150e 100644 --- a/src/stim/simulators/tableau_simulator.test.cc +++ b/src/stim/simulators/tableau_simulator.test.cc @@ -42,7 +42,7 @@ struct OpDat { }; TEST_EACH_WORD_SIZE_W(TableauSimulator, identity, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 1); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 1); ASSERT_EQ(s.measurement_record.storage, (std::vector{})); s.do_MZ({GateType::Z, {}, qubit_targets({0})}); ASSERT_EQ(s.measurement_record.storage, (std::vector{false})); @@ -51,7 +51,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, identity, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, bit_flip, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 1); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 1); s.do_H_XZ(OpDat(0)); s.do_SQRT_Z(OpDat(0)); s.do_SQRT_Z(OpDat(0)); @@ -63,7 +63,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, bit_flip, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, identity2, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 2); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 2); s.do_MZ(OpDat(0)); ASSERT_EQ(s.measurement_record.storage, (std::vector{false})); s.do_MZ(OpDat(1)); @@ -71,7 +71,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, identity2, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, bit_flip_2, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 2); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 2); s.do_H_XZ(OpDat(0)); s.do_SQRT_Z(OpDat(0)); s.do_SQRT_Z(OpDat(0)); @@ -83,7 +83,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, bit_flip_2, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, epr, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 2); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 2); s.do_H_XZ(OpDat(0)); s.do_ZCX(OpDat({0, 1})); ASSERT_EQ(s.is_deterministic_z(0), false); @@ -96,7 +96,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, epr, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, big_determinism, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 1000); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 1000); s.do_H_XZ(OpDat(0)); s.do_H_YZ(OpDat(1)); ASSERT_FALSE(s.is_deterministic_z(0)); @@ -114,7 +114,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, big_determinism, { TEST_EACH_WORD_SIZE_W(TableauSimulator, phase_kickback_consume_s_state, { for (size_t k = 0; k < 8; k++) { - auto s = TableauSimulator(SHARED_TEST_RNG(), 2); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 2); s.do_H_XZ(OpDat(1)); s.do_SQRT_Z(OpDat(1)); s.do_H_XZ(OpDat(0)); @@ -135,7 +135,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, phase_kickback_consume_s_state, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, phase_kickback_preserve_s_state, { - auto s = TableauSimulator(SHARED_TEST_RNG(), 2); + auto s = TableauSimulator(INDEPENDENT_TEST_RNG(), 2); // Prepare S state. s.do_H_XZ(OpDat(1)); @@ -164,7 +164,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, phase_kickback_preserve_s_state, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, kickback_vs_stabilizer, { - auto sim = TableauSimulator(SHARED_TEST_RNG(), 3); + auto sim = TableauSimulator(INDEPENDENT_TEST_RNG(), 3); sim.do_H_XZ(OpDat(2)); sim.do_ZCX(OpDat({2, 0})); sim.do_ZCX(OpDat({2, 1})); @@ -184,7 +184,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, kickback_vs_stabilizer, { TEST_EACH_WORD_SIZE_W(TableauSimulator, s_state_distillation_low_depth, { for (size_t reps = 0; reps < 10; reps++) { - auto sim = TableauSimulator(SHARED_TEST_RNG(), 9); + auto sim = TableauSimulator(INDEPENDENT_TEST_RNG(), 9); std::vector> stabilizers = { {0, 1, 2, 3}, @@ -255,7 +255,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, s_state_distillation_low_depth, { TEST_EACH_WORD_SIZE_W(TableauSimulator, s_state_distillation_low_space, { for (size_t rep = 0; rep < 10; rep++) { - auto sim = TableauSimulator(SHARED_TEST_RNG(), 5); + auto sim = TableauSimulator(INDEPENDENT_TEST_RNG(), 5); std::vector> phasors = { { @@ -307,8 +307,9 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, s_state_distillation_low_space, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, unitary_gates_consistent_with_tableau_data, { - auto t = Tableau::random(10, SHARED_TEST_RNG()); - TableauSimulator sim(SHARED_TEST_RNG(), 10); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(10, rng); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 10); for (const auto &gate : GATE_DATA.items) { if (!(gate.flags & GATE_IS_UNITARY)) { continue; @@ -328,8 +329,8 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, unitary_gates_consistent_with_tableau_da }) TEST_EACH_WORD_SIZE_W(TableauSimulator, certain_errors_consistent_with_gates, { - TableauSimulator sim1(SHARED_TEST_RNG(), 2); - TableauSimulator sim2(SHARED_TEST_RNG(), 2); + TableauSimulator sim1(INDEPENDENT_TEST_RNG(), 2); + TableauSimulator sim2(INDEPENDENT_TEST_RNG(), 2); GateTarget targets[]{GateTarget{0}}; double p0 = 0.0; double p1 = 1.0; @@ -356,18 +357,20 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, certain_errors_consistent_with_gates, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, simulate, { + auto rng = INDEPENDENT_TEST_RNG(); auto results = TableauSimulator::sample_circuit( Circuit("H 0\n" "CNOT 0 1\n" "M 0\n" "M 1\n" "M 2\n"), - SHARED_TEST_RNG()); + rng); ASSERT_EQ(results[0], results[1]); ASSERT_EQ(results[2], false); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, simulate_reset, { + auto rng = INDEPENDENT_TEST_RNG(); auto results = TableauSimulator::sample_circuit( Circuit("X 0\n" "M 0\n" @@ -375,14 +378,15 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, simulate_reset, { "M 0\n" "R 0\n" "M 0\n"), - SHARED_TEST_RNG()); + rng); ASSERT_EQ(results[0], true); ASSERT_EQ(results[1], false); ASSERT_EQ(results[2], false); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, to_vector_sim, { - TableauSimulator sim_tab(SHARED_TEST_RNG(), 2); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim_tab(INDEPENDENT_TEST_RNG(), 2); VectorSimulator sim_vec(2); ASSERT_TRUE(sim_tab.to_vector_sim().approximate_equals(sim_vec, true)); @@ -402,7 +406,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_vector_sim, { sim_vec.apply("ZCX", 0, 1); ASSERT_TRUE(sim_tab.to_vector_sim().approximate_equals(sim_vec, true)); - sim_tab.inv_state = Tableau::random(10, SHARED_TEST_RNG()); + sim_tab.inv_state = Tableau::random(10, rng); sim_vec = sim_tab.to_vector_sim(); ASSERT_TRUE(sim_tab.to_vector_sim().approximate_equals(sim_vec, true)); @@ -412,13 +416,13 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_vector_sim, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, to_state_vector, { - auto v = TableauSimulator(SHARED_TEST_RNG(), 0).to_state_vector(true); + auto v = TableauSimulator(INDEPENDENT_TEST_RNG(), 0).to_state_vector(true); ASSERT_EQ(v.size(), 1); auto r = v[0].real(); auto i = v[0].imag(); ASSERT_LT(r * r + i * i - 1, 1e-4); - TableauSimulator sim_tab(SHARED_TEST_RNG(), 3); + TableauSimulator sim_tab(INDEPENDENT_TEST_RNG(), 3); auto sim_vec = sim_tab.to_vector_sim(); VectorSimulator sim_vec2(3); sim_vec2.state = sim_tab.to_state_vector(true); @@ -431,7 +435,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_state_vector_endian, { sim_vec0.apply("H", 0); sim_vec2.apply("H", 2); - TableauSimulator sim_tab(SHARED_TEST_RNG(), 3); + TableauSimulator sim_tab(INDEPENDENT_TEST_RNG(), 3); sim_tab.do_H_XZ(OpDat(2)); VectorSimulator cmp(3); @@ -442,7 +446,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_state_vector_endian, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, to_state_vector_canonical, { - TableauSimulator sim_tab(SHARED_TEST_RNG(), 3); + TableauSimulator sim_tab(INDEPENDENT_TEST_RNG(), 3); sim_tab.do_H_XZ(OpDat(2)); std::vector expected; @@ -464,7 +468,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, to_state_vector_canonical, { template bool vec_sim_corroborates_measurement_process( const Tableau &state, const std::vector &measurement_targets) { - TableauSimulator sim_tab(SHARED_TEST_RNG(), 2); + TableauSimulator sim_tab(INDEPENDENT_TEST_RNG(), 2); sim_tab.inv_state = state; auto vec_sim = sim_tab.to_vector_sim(); sim_tab.do_MZ(OpDat(measurement_targets)); @@ -483,20 +487,21 @@ bool vec_sim_corroborates_measurement_process( } TEST_EACH_WORD_SIZE_W(TableauSimulator, measurement_vs_vector_sim, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t k = 0; k < 10; k++) { - auto state = Tableau::random(2, SHARED_TEST_RNG()); + auto state = Tableau::random(2, rng); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {1})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0, 1})); } for (size_t k = 0; k < 10; k++) { - auto state = Tableau::random(4, SHARED_TEST_RNG()); + auto state = Tableau::random(4, rng); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0, 1})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {2, 1})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0, 1, 2, 3})); } { - auto state = Tableau::random(12, SHARED_TEST_RNG()); + auto state = Tableau::random(12, rng); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0, 1, 2, 3})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {0, 10, 11})); ASSERT_TRUE(vec_sim_corroborates_measurement_process(state, {11, 5, 7})); @@ -505,6 +510,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measurement_vs_vector_sim, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits expected(5); expected.clear(); @@ -516,7 +522,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(0) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -530,7 +536,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(0) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -544,7 +550,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(0) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -558,7 +564,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(1) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -572,7 +578,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(0) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -586,7 +592,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { ELSE_CORRELATED_ERROR(1) X2 X3 M 0 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -603,12 +609,11 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { CORRELATED_ERROR(1) X3 X4 M 0 1 2 3 4 )circuit"), - SHARED_TEST_RNG()), + rng), expected); int hits[3]{}; size_t n = 10000; - std::mt19937_64 rng(0); for (size_t k = 0; k < n; k++) { auto sample = TableauSimulator::sample_circuit( Circuit(R"circuit( @@ -628,6 +633,8 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, correlated_error, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { + auto rng = INDEPENDENT_TEST_RNG(); + // Quantum controlling classical operation is not allowed. ASSERT_THROW( { @@ -636,7 +643,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { M 0 CNOT 1 rec[-1] )circuit"), - SHARED_TEST_RNG()); + rng); }, std::invalid_argument); ASSERT_THROW( @@ -646,7 +653,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { M 0 CY 1 rec[-1] )circuit"), - SHARED_TEST_RNG()); + rng); }, std::invalid_argument); ASSERT_THROW( @@ -656,7 +663,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { M 0 YCZ rec[-1] 1 )circuit"), - SHARED_TEST_RNG()); + rng); }, std::invalid_argument); ASSERT_THROW( @@ -666,7 +673,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { M 0 XCZ rec[-1] 1 )circuit"), - SHARED_TEST_RNG()); + rng); }, std::invalid_argument); ASSERT_THROW( @@ -676,12 +683,13 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, quantum_cannot_control_classical, { M 0 SWAP 1 rec[-1] )circuit"), - SHARED_TEST_RNG()); + rng); }, std::invalid_argument); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_can_control_quantum, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits expected(5); expected.clear(); expected[0] = true; @@ -693,7 +701,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_can_control_quantum, { CX rec[-1] 1 M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); ASSERT_EQ( TableauSimulator::sample_circuit( @@ -702,7 +710,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_can_control_quantum, { CY rec[-1] 1 M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); ASSERT_EQ( TableauSimulator::sample_circuit( @@ -711,7 +719,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_can_control_quantum, { XCZ 1 rec[-1] M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); ASSERT_EQ( TableauSimulator::sample_circuit( @@ -720,11 +728,12 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_can_control_quantum, { YCZ 1 rec[-1] M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_control_cases, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits expected(5); expected.clear(); expected[0] = true; @@ -738,7 +747,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_control_cases, { H 1 M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -751,7 +760,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_control_cases, { CY rec[-1] 1 M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -764,7 +773,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_control_cases, { CX rec[-1] 1 M 1 )circuit"), - SHARED_TEST_RNG()), + rng), expected); expected.clear(); @@ -784,19 +793,20 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, classical_control_cases, { CX rec[-1] 2 M 1 2 3 )circuit"), - SHARED_TEST_RNG()), + rng), expected); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, mr_repeated_target, { + auto rng = INDEPENDENT_TEST_RNG(); simd_bits expected(2); expected[0] = true; - auto r = TableauSimulator::sample_circuit(Circuit("X 0\nMR 0 0"), SHARED_TEST_RNG()); + auto r = TableauSimulator::sample_circuit(Circuit("X 0\nMR 0 0"), rng); ASSERT_EQ(r, expected); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, peek_bloch, { - TableauSimulator sim(SHARED_TEST_RNG(), 3); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 3); ASSERT_EQ(sim.peek_bloch(0), PauliString::from_str("+Z")); ASSERT_EQ(sim.peek_bloch(1), PauliString::from_str("+Z")); ASSERT_EQ(sim.peek_bloch(2), PauliString::from_str("+Z")); @@ -854,9 +864,10 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, peek_bloch, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, paulis, { - TableauSimulator sim1(SHARED_TEST_RNG(), 500); - TableauSimulator sim2(SHARED_TEST_RNG(), 500); - sim1.inv_state = Tableau::random(500, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim1(INDEPENDENT_TEST_RNG(), 500); + TableauSimulator sim2(INDEPENDENT_TEST_RNG(), 500); + sim1.inv_state = Tableau::random(500, rng); sim2.inv_state = sim1.inv_state; sim1.paulis(PauliString(500)); @@ -874,9 +885,10 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, paulis, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits, { - TableauSimulator sim1(SHARED_TEST_RNG(), 10); - TableauSimulator sim2(SHARED_TEST_RNG(), 10); - sim1.inv_state = Tableau::random(10, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim1(INDEPENDENT_TEST_RNG(), 10); + TableauSimulator sim2(INDEPENDENT_TEST_RNG(), 10); + sim1.inv_state = Tableau::random(10, rng); sim2.inv_state = sim1.inv_state; sim1.set_num_qubits(20); @@ -897,8 +909,9 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits_reduce_random, { - TableauSimulator sim(SHARED_TEST_RNG(), 10); - sim.inv_state = Tableau::random(10, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 10); + sim.inv_state = Tableau::random(10, rng); sim.set_num_qubits(5); ASSERT_EQ(sim.inv_state.num_qubits, 5); ASSERT_TRUE(sim.inv_state.satisfies_invariants()); @@ -906,7 +919,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits_reduce_random, { template void scramble_stabilizers(TableauSimulator &s) { - auto &rng = SHARED_TEST_RNG(); + auto rng = INDEPENDENT_TEST_RNG(); TableauTransposedRaii tmp(s.inv_state); for (size_t i = 0; i < s.inv_state.num_qubits; i++) { for (size_t j = i + 1; j < s.inv_state.num_qubits; j++) { @@ -927,7 +940,7 @@ void scramble_stabilizers(TableauSimulator &s) { } TEST_EACH_WORD_SIZE_W(TableauSimulator, canonical_stabilizers, { - TableauSimulator sim(SHARED_TEST_RNG(), 2); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 2); sim.do_H_XZ(OpDat(0)); sim.do_ZCX(OpDat({0, 1})); ASSERT_EQ( @@ -969,8 +982,9 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, canonical_stabilizers, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, canonical_stabilizers_random, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); - sim.inv_state = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); + sim.inv_state = Tableau::random(4, rng); auto s1 = sim.canonical_stabilizers(); scramble_stabilizers(sim); auto s2 = sim.canonical_stabilizers(); @@ -978,9 +992,9 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, canonical_stabilizers_random, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits_reduce_preserves_scrambled_stabilizers, { - auto &rng = SHARED_TEST_RNG(); - TableauSimulator sim(rng, 4); - sim.inv_state = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); + sim.inv_state = Tableau::random(4, rng); auto s1 = sim.canonical_stabilizers(); sim.inv_state.expand(8, 1.0); scramble_stabilizers(sim); @@ -990,7 +1004,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, set_num_qubits_reduce_preserves_scramble }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_z, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); sim.do_H_XZ(OpDat({0, 2})); sim.do_ZCX(OpDat({0, 1, 2, 3})); auto k1 = sim.measure_kickback_z(GateTarget::qubit(1)); @@ -1009,7 +1023,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_z, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_x, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); sim.do_H_XZ(OpDat({0, 2})); sim.do_ZCX(OpDat({0, 1, 2, 3})); auto k1 = sim.measure_kickback_x(GateTarget::qubit(1)); @@ -1028,7 +1042,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_x, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_y, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); sim.do_H_XZ(OpDat({0, 2})); sim.do_ZCX(OpDat({0, 1, 2, 3})); auto k1 = sim.measure_kickback_y(GateTarget::qubit(1)); @@ -1047,8 +1061,9 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_y, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_isolates, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); - sim.inv_state = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); + sim.inv_state = Tableau::random(4, rng); for (size_t k = 0; k < 4; k++) { auto result = sim.measure_kickback_z(GateTarget::qubit(k)); for (size_t j = 0; j < result.second.num_qubits && j < k; j++) { @@ -1059,9 +1074,10 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_kickback_isolates, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, collapse_isolate_completely, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t k = 0; k < 10; k++) { - TableauSimulator sim(SHARED_TEST_RNG(), 6); - sim.inv_state = Tableau::random(6, SHARED_TEST_RNG()); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 6); + sim.inv_state = Tableau::random(6, rng); { TableauTransposedRaii tmp(sim.inv_state); sim.collapse_isolate_qubit_z(2, tmp); @@ -1076,7 +1092,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, collapse_isolate_completely, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_pure, { - TableauSimulator t(SHARED_TEST_RNG(), 1); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 1); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Z")); t.do_RY(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Y")); @@ -1089,35 +1105,36 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_pure, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_random, { - TableauSimulator t(SHARED_TEST_RNG(), 5); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 5); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_RX(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+X")); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_RY(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Y")); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_RZ(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Z")); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_MRX(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+X")); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_MRY(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Y")); - t.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + t.inv_state = Tableau::random(5, rng); t.do_MRZ(OpDat(0)); ASSERT_EQ(t.peek_bloch(0), PauliString::from_str("+Z")); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_x_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_RX(OpDat(0)); @@ -1129,7 +1146,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_x_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_y_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_RY(OpDat(0)); @@ -1141,7 +1158,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_y_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_z_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_RZ(OpDat(0)); @@ -1153,7 +1170,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_z_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_x_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MX(OpDat(0)); @@ -1167,7 +1184,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_x_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_y_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MY(OpDat(0)); @@ -1181,7 +1198,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_y_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_z_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MZ(OpDat(0)); @@ -1195,7 +1212,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_z_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_x_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MRX(OpDat(0)); @@ -1208,7 +1225,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_x_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_y_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MRY(OpDat(0)); @@ -1221,7 +1238,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_y_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_z_entangled, { - TableauSimulator t(SHARED_TEST_RNG(), 2); + TableauSimulator t(INDEPENDENT_TEST_RNG(), 2); t.do_H_XZ(OpDat(0)); t.do_ZCX(OpDat({0, 1})); t.do_MRZ(OpDat(0)); @@ -1234,13 +1251,14 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_reset_z_entangled, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, reset_vs_measurements, { + auto rng = INDEPENDENT_TEST_RNG(); auto check = [&](const char *circuit, std::vector results) { simd_bits ref(results.size()); for (size_t k = 0; k < results.size(); k++) { ref[k] = results[k]; } for (size_t reps = 0; reps < 5; reps++) { - simd_bits t = TableauSimulator::sample_circuit(Circuit(circuit), SHARED_TEST_RNG()); + simd_bits t = TableauSimulator::sample_circuit(Circuit(circuit), rng); if (t != ref) { return false; } @@ -1406,7 +1424,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, sample_stream_mutates_rng_state, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_x, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RX 0 REPEAT 10000 { @@ -1435,7 +1453,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_x, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_y, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RY 0 REPEAT 10000 { @@ -1464,7 +1482,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_y, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_z, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RZ 0 REPEAT 10000 { @@ -1493,7 +1511,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measurement_z, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_x, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RX 0 REPEAT 10000 { @@ -1522,7 +1540,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_x, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_y, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RY 0 1 REPEAT 5000 { @@ -1551,7 +1569,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_y, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_z, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( RZ 0 1 REPEAT 5000 { @@ -1580,13 +1598,13 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, noisy_measure_reset_z, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_bad, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); ASSERT_THROW({ t.expand_do_circuit("MPP X0*X0"); }, std::invalid_argument); ASSERT_THROW({ t.expand_do_circuit("MPP X0*Z0"); }, std::invalid_argument); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_1, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( REPEAT 100 { RX 0 @@ -1601,7 +1619,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_1, { TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_4body, { for (size_t k = 0; k < 10; k++) { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( MPP X0*X1*X2*X3 MX 0 1 2 3 4 5 @@ -1626,7 +1644,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_4body, { TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_epr, { for (size_t k = 0; k < 10; k++) { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( MPP X0*X1 Z0*Z1 Y0*Y1 CNOT 0 1 @@ -1646,7 +1664,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_epr, { TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_inversions, { for (size_t k = 0; k < 10; k++) { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( MPP !X0*!X1 !X0*X1 X0*!X1 X0*X1 X0 X1 !X0 !X1 )CIRCUIT"); @@ -1668,7 +1686,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_inversions, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_noisy, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( H 0 CNOT 0 1 @@ -1684,7 +1702,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_product_noisy, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, ignores_sweep_controls, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( X 0 CNOT sweep[0] 0 @@ -1694,7 +1712,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, ignores_sweep_controls, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, peek_observable_expectation, { - TableauSimulator t(SHARED_TEST_RNG()); + TableauSimulator t(INDEPENDENT_TEST_RNG()); t.expand_do_circuit(R"CIRCUIT( H 0 CNOT 0 1 @@ -1718,7 +1736,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, peek_observable_expectation, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_x, { - TableauSimulator sim(SHARED_TEST_RNG(), 2); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 2); // Postselect from +X. sim.do_RX(OpDat(0)); @@ -1798,7 +1816,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_x, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_y, { - TableauSimulator sim(SHARED_TEST_RNG(), 2); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 2); // Postselect from +X. sim.do_RX(OpDat(0)); @@ -1878,7 +1896,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_y, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_z, { - TableauSimulator sim(SHARED_TEST_RNG(), 2); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 2); // Postselect from +X. sim.do_RX(OpDat(0)); @@ -1958,7 +1976,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_z, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, peek_x, { - TableauSimulator sim(SHARED_TEST_RNG(), 3); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 3); ASSERT_EQ(sim.peek_x(0), 0); ASSERT_EQ(sim.peek_y(0), 0); ASSERT_EQ(sim.peek_z(0), +1); @@ -2081,7 +2099,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, apply_tableau, { auto h = GATE_DATA.at("H").tableau(); auto cxyz = GATE_DATA.at("C_XYZ").tableau(); - TableauSimulator sim(SHARED_TEST_RNG(), 4); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); sim.apply_tableau(h, {1}); ASSERT_EQ(sim.peek_bloch(1), PauliString::from_str("+X")); sim.apply_tableau(s, {1}); @@ -2102,7 +2120,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, apply_tableau, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_string, { - TableauSimulator sim(SHARED_TEST_RNG(), 4); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 4); sim.do_H_XZ(OpDat(0)); sim.do_ZCX(OpDat({0, 1})); sim.do_X(OpDat(0)); @@ -2151,13 +2169,13 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, measure_pauli_string, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, amortized_resizing, { - TableauSimulator sim(SHARED_TEST_RNG(), 5120); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 5120); sim.ensure_large_enough_for_qubits(5121); ASSERT_GT(sim.inv_state.xs.xt.num_minor_bits_padded(), 5600); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, mpad, { - TableauSimulator sim(SHARED_TEST_RNG(), 5); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 5); ASSERT_EQ(sim.inv_state, Tableau(5)); ASSERT_EQ(sim.measurement_record.storage, (std::vector{})); @@ -2177,8 +2195,8 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, mpad, { template void expect_same_final_state(const Tableau &start, const Circuit &c1, const Circuit &c2, bool unsigned_stabilizers) { size_t n = start.num_qubits; - TableauSimulator sim1(SHARED_TEST_RNG(), n); - TableauSimulator sim2(SHARED_TEST_RNG(), n); + TableauSimulator sim1(INDEPENDENT_TEST_RNG(), n); + TableauSimulator sim2(INDEPENDENT_TEST_RNG(), n); sim1.inv_state = start; sim2.inv_state = start; sim1.expand_do_circuit(c1); @@ -2197,22 +2215,24 @@ void expect_same_final_state(const Tableau &start, const Circuit &c1, const C } TEST_EACH_WORD_SIZE_W(TableauSimulator, mxx_myy_mzz_vs_mpp_unsigned, { + auto rng = INDEPENDENT_TEST_RNG(); expect_same_final_state( - Tableau::random(5, SHARED_TEST_RNG()), Circuit("MXX 1 3 1 2 3 4"), Circuit("MPP X1*X3 X1*X2 X3*X4"), true); + Tableau::random(5, rng), Circuit("MXX 1 3 1 2 3 4"), Circuit("MPP X1*X3 X1*X2 X3*X4"), true); expect_same_final_state( - Tableau::random(5, SHARED_TEST_RNG()), Circuit("MYY 1 3 1 2 3 4"), Circuit("MPP Y1*Y3 Y1*Y2 Y3*Y4"), true); + Tableau::random(5, rng), Circuit("MYY 1 3 1 2 3 4"), Circuit("MPP Y1*Y3 Y1*Y2 Y3*Y4"), true); expect_same_final_state( - Tableau::random(5, SHARED_TEST_RNG()), Circuit("MZZ 1 3 1 2 3 4"), Circuit("MPP Z1*Z3 Z1*Z2 Z3*Z4"), true); + Tableau::random(5, rng), Circuit("MZZ 1 3 1 2 3 4"), Circuit("MPP Z1*Z3 Z1*Z2 Z3*Z4"), true); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, mxx, { - TableauSimulator sim(SHARED_TEST_RNG(), 5); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 5); sim.expand_do_circuit(Circuit("RX 0 1")); sim.expand_do_circuit(Circuit("MXX 0 1")); ASSERT_EQ(sim.measurement_record.storage, (std::vector{false})); sim.measurement_record.storage.clear(); - sim.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + sim.inv_state = Tableau::random(5, rng); sim.expand_do_circuit(Circuit("MXX 1 3")); bool x13 = sim.measurement_record.storage.back(); sim.measurement_record.storage.clear(); @@ -2242,13 +2262,14 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, mxx, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, myy, { - TableauSimulator sim(SHARED_TEST_RNG(), 5); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 5); sim.expand_do_circuit(Circuit("RY 0 1")); sim.expand_do_circuit(Circuit("MYY 0 1")); ASSERT_EQ(sim.measurement_record.storage, (std::vector{false})); sim.measurement_record.storage.clear(); - sim.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + sim.inv_state = Tableau::random(5, rng); sim.expand_do_circuit(Circuit("MYY 1 3")); bool x13 = sim.measurement_record.storage.back(); sim.measurement_record.storage.clear(); @@ -2278,13 +2299,14 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, myy, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, mzz, { - TableauSimulator sim(SHARED_TEST_RNG(), 5); + auto rng = INDEPENDENT_TEST_RNG(); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 5); sim.expand_do_circuit(Circuit("RZ 0 1")); sim.expand_do_circuit(Circuit("MZZ 0 1")); ASSERT_EQ(sim.measurement_record.storage, (std::vector{false})); sim.measurement_record.storage.clear(); - sim.inv_state = Tableau::random(5, SHARED_TEST_RNG()); + sim.inv_state = Tableau::random(5, rng); sim.expand_do_circuit(Circuit("MZZ 1 3")); bool x13 = sim.measurement_record.storage.back(); sim.measurement_record.storage.clear(); @@ -2315,13 +2337,13 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, mzz, { TEST_EACH_WORD_SIZE_W(TableauSimulator, runs_on_general_circuit, { auto circuit = generate_test_circuit_with_all_operations(); - TableauSimulator sim(SHARED_TEST_RNG(), 1); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 1); sim.expand_do_circuit(circuit); ASSERT_GT(sim.inv_state.xs.num_qubits, 1); }) TEST_EACH_WORD_SIZE_W(TableauSimulator, heralded_erase, { - TableauSimulator sim(SHARED_TEST_RNG(), 1); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 1); sim.expand_do_circuit(Circuit(R"CIRCUIT( HERALDED_ERASE(0) 0 1 2 3 10 11 12 13 )CIRCUIT")); @@ -2337,7 +2359,7 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, heralded_erase, { }) TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_observable, { - TableauSimulator sim(SHARED_TEST_RNG(), 0); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), 0); sim.postselect_observable(PauliString("ZZ"), false); sim.postselect_observable(PauliString("XX"), false); diff --git a/src/stim/stabilizers/conversions.inl b/src/stim/stabilizers/conversions.inl index e116c7f69..8f589b5fa 100644 --- a/src/stim/stabilizers/conversions.inl +++ b/src/stim/stabilizers/conversions.inl @@ -147,8 +147,7 @@ template Tableau circuit_to_tableau( const Circuit &circuit, bool ignore_noise, bool ignore_measurement, bool ignore_reset) { Tableau result(circuit.count_qubits()); - std::mt19937_64 unused_rng(0); - TableauSimulator sim(unused_rng, circuit.count_qubits()); + TableauSimulator sim(std::mt19937_64(0), circuit.count_qubits()); circuit.for_each_operation([&](const CircuitInstruction &op) { const auto &flags = GATE_DATA.items[op.gate_type].flags; @@ -188,8 +187,7 @@ Tableau circuit_to_tableau( template std::vector> circuit_to_output_state_vector(const Circuit &circuit, bool little_endian) { Tableau result(circuit.count_qubits()); - std::mt19937_64 unused_rng(0); - TableauSimulator sim(unused_rng, circuit.count_qubits()); + TableauSimulator sim(std::mt19937_64(0), circuit.count_qubits()); circuit.for_each_operation([&](const CircuitInstruction &op) { const auto &flags = GATE_DATA.items[op.gate_type].flags; diff --git a/src/stim/stabilizers/conversions.test.cc b/src/stim/stabilizers/conversions.test.cc index 7c6fb4ef7..2c103398b 100644 --- a/src/stim/stabilizers/conversions.test.cc +++ b/src/stim/stabilizers/conversions.test.cc @@ -215,11 +215,12 @@ TEST_EACH_WORD_SIZE_W(conversions, stabilizer_state_vector_to_circuit_basic, { }) TEST_EACH_WORD_SIZE_W(conversions, stabilizer_state_vector_to_circuit_fuzz_round_trip, { + auto rng = INDEPENDENT_TEST_RNG(); for (const auto &little_endian : std::vector{false, true}) { for (size_t n = 0; n < 10; n++) { // Pick a random stabilizer state. - TableauSimulator sim(SHARED_TEST_RNG(), n); - sim.inv_state = Tableau::random(n, SHARED_TEST_RNG()); + TableauSimulator sim(INDEPENDENT_TEST_RNG(), n); + sim.inv_state = Tableau::random(n, rng); auto desired_vec = sim.to_state_vector(little_endian); // Round trip through a circuit. @@ -430,8 +431,9 @@ TEST_EACH_WORD_SIZE_W(conversions, circuit_to_output_state_vector, { }) TEST_EACH_WORD_SIZE_W(conversions, tableau_to_circuit_fuzz_vs_circuit_to_tableau, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t n = 0; n < 10; n++) { - auto desired = Tableau::random(n, SHARED_TEST_RNG()); + auto desired = Tableau::random(n, rng); Circuit circuit = tableau_to_circuit(desired, "elimination"); auto actual = circuit_to_tableau(circuit, false, false, false); ASSERT_EQ(actual, desired); @@ -517,9 +519,10 @@ TEST_EACH_WORD_SIZE_W(conversions, unitary_vs_tableau_basic, { }) TEST_EACH_WORD_SIZE_W(conversions, unitary_to_tableau_fuzz_vs_tableau_to_unitary, { + auto rng = INDEPENDENT_TEST_RNG(); for (bool little_endian : std::vector{false, true}) { for (size_t n = 0; n < 6; n++) { - auto desired = Tableau::random(n, SHARED_TEST_RNG()); + auto desired = Tableau::random(n, rng); auto unitary = tableau_to_unitary(desired, little_endian); auto actual = unitary_to_tableau(unitary, little_endian); ASSERT_EQ(actual, desired) << "little_endian=" << little_endian << ", n=" << n; @@ -564,8 +567,9 @@ TEST_EACH_WORD_SIZE_W(conversions, unitary_to_tableau_fail, { }) TEST_EACH_WORD_SIZE_W(conversions, stabilizers_to_tableau_fuzz, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t n = 0; n < 10; n++) { - auto t = Tableau::random(n, SHARED_TEST_RNG()); + auto t = Tableau::random(n, rng); std::vector> expected_stabilizers; for (size_t k = 0; k < n; k++) { expected_stabilizers.push_back(t.zs[k]); @@ -580,9 +584,10 @@ TEST_EACH_WORD_SIZE_W(conversions, stabilizers_to_tableau_fuzz, { }) TEST_EACH_WORD_SIZE_W(conversions, stabilizers_to_tableau_partial_fuzz, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t n = 0; n < 10; n++) { for (size_t skipped = 1; skipped < n && skipped < 4; skipped++) { - auto t = Tableau::random(n, SHARED_TEST_RNG()); + auto t = Tableau::random(n, rng); std::vector> expected_stabilizers; for (size_t k = 0; k < n - skipped; k++) { expected_stabilizers.push_back(t.zs[k]); @@ -603,8 +608,9 @@ TEST_EACH_WORD_SIZE_W(conversions, stabilizers_to_tableau_partial_fuzz, { }) TEST_EACH_WORD_SIZE_W(conversions, stabilizers_to_tableau_overconstrained, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t n = 4; n < 10; n++) { - auto t = Tableau::random(n, SHARED_TEST_RNG()); + auto t = Tableau::random(n, rng); std::vector> expected_stabilizers; expected_stabilizers.push_back(PauliString(n)); expected_stabilizers.push_back(PauliString(n)); diff --git a/src/stim/stabilizers/pauli_string.test.cc b/src/stim/stabilizers/pauli_string.test.cc index 57655fda4..b713aa7e4 100644 --- a/src/stim/stabilizers/pauli_string.test.cc +++ b/src/stim/stabilizers/pauli_string.test.cc @@ -215,8 +215,9 @@ TEST_EACH_WORD_SIZE_W(pauli_string, move_copy_assignment, { }) TEST_EACH_WORD_SIZE_W(pauli_string, foreign_memory, { + auto rng = INDEPENDENT_TEST_RNG(); size_t bits = 2048; - auto buffer = simd_bits::random(bits, SHARED_TEST_RNG()); + auto buffer = simd_bits::random(bits, rng); bool signs = false; size_t num_qubits = W * 2 - 12; diff --git a/src/stim/stabilizers/tableau.test.cc b/src/stim/stabilizers/tableau.test.cc index ce5a81766..b3c804ddf 100644 --- a/src/stim/stabilizers/tableau.test.cc +++ b/src/stim/stabilizers/tableau.test.cc @@ -349,6 +349,7 @@ bool are_tableau_mutations_equivalent( size_t n, const std::function &t, const std::vector &)> &mutation1, const std::function &t, const std::vector &)> &mutation2) { + auto rng = INDEPENDENT_TEST_RNG(); auto test_tableau_dual = Tableau::identity(2 * n); std::vector targets1; std::vector targets2; @@ -363,8 +364,8 @@ bool are_tableau_mutations_equivalent( std::vector> tableaus{ test_tableau_dual, - Tableau::random(n + 10, SHARED_TEST_RNG()), - Tableau::random(n + 30, SHARED_TEST_RNG()), + Tableau::random(n + 10, rng), + Tableau::random(n + 30, rng), }; std::vector> cases{targets1, targets2, targets3}; for (const auto &t : tableaus) { @@ -451,20 +452,21 @@ TEST_EACH_WORD_SIZE_W(tableau, from_pauli_string, { }) TEST_EACH_WORD_SIZE_W(tableau, random, { + auto rng = INDEPENDENT_TEST_RNG(); for (size_t k = 0; k < 20; k++) { - auto t = Tableau::random(1, SHARED_TEST_RNG()); + auto t = Tableau::random(1, rng); ASSERT_TRUE(t.satisfies_invariants()) << t; } for (size_t k = 0; k < 20; k++) { - auto t = Tableau::random(2, SHARED_TEST_RNG()); + auto t = Tableau::random(2, rng); ASSERT_TRUE(t.satisfies_invariants()) << t; } for (size_t k = 0; k < 20; k++) { - auto t = Tableau::random(3, SHARED_TEST_RNG()); + auto t = Tableau::random(3, rng); ASSERT_TRUE(t.satisfies_invariants()) << t; } for (size_t k = 0; k < 20; k++) { - auto t = Tableau::random(30, SHARED_TEST_RNG()); + auto t = Tableau::random(30, rng); ASSERT_TRUE(t.satisfies_invariants()); } }) @@ -627,7 +629,8 @@ TEST_EACH_WORD_SIZE_W(tableau, specialized_operation, { }) TEST_EACH_WORD_SIZE_W(tableau, expand, { - auto t = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(4, rng); auto t2 = t; for (size_t n = 8; n < 500; n += 255) { t2.expand(n, 1.0); @@ -663,7 +666,8 @@ TEST_EACH_WORD_SIZE_W(tableau, expand, { }) TEST_EACH_WORD_SIZE_W(tableau, expand_pad, { - auto t = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(4, rng); auto t2 = t; size_t n = 8; while (n < 10000) { @@ -708,7 +712,8 @@ TEST_EACH_WORD_SIZE_W(tableau, expand_pad, { }) TEST_EACH_WORD_SIZE_W(tableau, expand_pad_equals, { - auto t = Tableau::random(15, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(15, rng); auto t2 = t; t.expand(500, 1.0); t2.expand(500, 2.0); @@ -716,13 +721,14 @@ TEST_EACH_WORD_SIZE_W(tableau, expand_pad_equals, { }) TEST_EACH_WORD_SIZE_W(tableau, transposed_access, { + auto rng = INDEPENDENT_TEST_RNG(); size_t n = 1000; Tableau t(n); auto m = t.xs.xt.data.num_bits_padded(); - t.xs.xt.data.randomize(m, SHARED_TEST_RNG()); - t.xs.zt.data.randomize(m, SHARED_TEST_RNG()); - t.zs.xt.data.randomize(m, SHARED_TEST_RNG()); - t.zs.zt.data.randomize(m, SHARED_TEST_RNG()); + t.xs.xt.data.randomize(m, rng); + t.xs.zt.data.randomize(m, rng); + t.zs.xt.data.randomize(m, rng); + t.zs.zt.data.randomize(m, rng); for (size_t inp_qubit = 0; inp_qubit < 1000; inp_qubit += 99) { for (size_t out_qubit = 0; out_qubit < 1000; out_qubit += 99) { bool bxx = t.xs.xt[inp_qubit][out_qubit]; @@ -747,6 +753,7 @@ TEST_EACH_WORD_SIZE_W(tableau, transposed_access, { }) TEST_EACH_WORD_SIZE_W(tableau, inverse, { + auto rng = INDEPENDENT_TEST_RNG(); Tableau t1(1); ASSERT_EQ(t1, t1.inverse()); t1.prepend_X(0); @@ -756,10 +763,10 @@ TEST_EACH_WORD_SIZE_W(tableau, inverse, { ASSERT_EQ(t2, GATE_DATA.at("X").tableau()); for (size_t k = 5; k < 20; k += 7) { - t1 = Tableau::random(k, SHARED_TEST_RNG()); + t1 = Tableau::random(k, rng); t2 = t1.inverse(); ASSERT_TRUE(t2.satisfies_invariants()); - auto p = PauliString::random(k, SHARED_TEST_RNG()); + auto p = PauliString::random(k, rng); auto p2 = t1(t2(p)); auto x1 = p.xs.str(); auto x2 = p2.xs.str(); @@ -771,7 +778,8 @@ TEST_EACH_WORD_SIZE_W(tableau, inverse, { }) TEST_EACH_WORD_SIZE_W(tableau, prepend_pauli_product, { - auto t = Tableau::random(6, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(6, rng); auto ref = t; t.prepend_pauli_product(PauliString::from_str("_XYZ__")); ref.prepend_X(1); @@ -858,7 +866,8 @@ TEST_EACH_WORD_SIZE_W(tableau, raised_to, { }) TEST_EACH_WORD_SIZE_W(tableau, transposed_xz_input, { - auto t = Tableau::random(4, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(4, rng); PauliString x0(0); PauliString x1(0); { @@ -875,8 +884,9 @@ TEST_EACH_WORD_SIZE_W(tableau, transposed_xz_input, { }) TEST_EACH_WORD_SIZE_W(tableau, direct_sum, { - auto t1 = Tableau::random(260, SHARED_TEST_RNG()); - auto t2 = Tableau::random(270, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t1 = Tableau::random(260, rng); + auto t2 = Tableau::random(270, rng); auto t3 = t1; t3 += t2; ASSERT_EQ(t3, t1 + t2); @@ -899,7 +909,8 @@ TEST_EACH_WORD_SIZE_W(tableau, direct_sum, { }) TEST_EACH_WORD_SIZE_W(tableau, pauli_access_methods, { - auto t = Tableau::random(3, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(3, rng); auto t_inv = t.inverse(); for (size_t i = 0; i < 3; i++) { auto x = t.xs[i]; @@ -966,7 +977,8 @@ TEST_EACH_WORD_SIZE_W(tableau, pauli_access_methods, { }) TEST_EACH_WORD_SIZE_W(tableau, inverse_pauli_string_acces_methods, { - auto t = Tableau::random(5, SHARED_TEST_RNG()); + auto rng = INDEPENDENT_TEST_RNG(); + auto t = Tableau::random(5, rng); auto t_inv = t.inverse(); auto y0 = t_inv.eval_y_obs(0); auto y1 = t_inv.eval_y_obs(1); diff --git a/src/stim/test_util.test.cc b/src/stim/test_util.test.cc index a60914d18..68e23963d 100644 --- a/src/stim/test_util.test.cc +++ b/src/stim/test_util.test.cc @@ -72,12 +72,13 @@ void expect_string_is_identical_to_saved_file(const std::string &actual, const s } } -std::mt19937_64 &SHARED_TEST_RNG() { +std::mt19937_64 INDEPENDENT_TEST_RNG() { if (!shared_test_rng_initialized) { shared_test_rng = externally_seeded_rng(); shared_test_rng_initialized = true; } - return shared_test_rng; + std::seed_seq seq{shared_test_rng(), shared_test_rng(), shared_test_rng(), shared_test_rng()}; + return std::mt19937_64(seq); } std::string rewind_read_close(FILE *f) { diff --git a/src/stim/test_util.test.h b/src/stim/test_util.test.h index 8bfb3bf63..3c71cd4e7 100644 --- a/src/stim/test_util.test.h +++ b/src/stim/test_util.test.h @@ -21,7 +21,7 @@ #include "gtest/gtest.h" -std::mt19937_64 &SHARED_TEST_RNG(); +std::mt19937_64 INDEPENDENT_TEST_RNG(); std::string rewind_read_close(FILE *f); From 90a85fb8c891bfc3d5369e8836e573e0ac1e4621 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 15:53:08 -0700 Subject: [PATCH 2/8] sync pybind, perf --- src/stim/dem/detector_error_model.pybind.cc | 2 +- src/stim/py/base.pybind.cc | 6 +++--- src/stim/py/base.pybind.h | 2 +- src/stim/py/compiled_detector_sampler.pybind.cc | 7 +++---- src/stim/py/compiled_detector_sampler.pybind.h | 3 +-- .../py/compiled_measurement_sampler.pybind.cc | 9 ++++----- src/stim/py/compiled_measurement_sampler.pybind.h | 4 ++-- src/stim/simulators/frame_simulator.perf.cc | 15 +++++---------- src/stim/simulators/tableau_simulator.perf.cc | 3 +-- src/stim/simulators/tableau_simulator.pybind.cc | 4 ++-- src/stim/stabilizers/pauli_string.pybind.cc | 2 +- src/stim/stabilizers/tableau.pybind.cc | 6 +++--- 12 files changed, 27 insertions(+), 36 deletions(-) diff --git a/src/stim/dem/detector_error_model.pybind.cc b/src/stim/dem/detector_error_model.pybind.cc index 65dee9125..cf546a7c1 100644 --- a/src/stim/dem/detector_error_model.pybind.cc +++ b/src/stim/dem/detector_error_model.pybind.cc @@ -997,7 +997,7 @@ void stim_pybind::pybind_detector_error_model_methods( c.def( "compile_sampler", [](const DetectorErrorModel &self, const pybind11::object &seed) -> DemSampler { - return DemSampler(self, *make_py_seeded_rng(seed), 1024); + return DemSampler(self, make_py_seeded_rng(seed), 1024); }, pybind11::kw_only(), pybind11::arg("seed") = pybind11::none(), diff --git a/src/stim/py/base.pybind.cc b/src/stim/py/base.pybind.cc index bfc95d634..4bbb363dc 100644 --- a/src/stim/py/base.pybind.cc +++ b/src/stim/py/base.pybind.cc @@ -20,14 +20,14 @@ using namespace stim; -std::shared_ptr stim_pybind::make_py_seeded_rng(const pybind11::object &seed) { +std::mt19937_64 stim_pybind::make_py_seeded_rng(const pybind11::object &seed) { if (seed.is_none()) { - return std::make_shared(externally_seeded_rng()); + return externally_seeded_rng(); } try { uint64_t s = pybind11::cast(seed) ^ INTENTIONAL_VERSION_SEED_INCOMPATIBILITY; - return std::make_shared(s); + return std::mt19937_64(s); } catch (const pybind11::cast_error &) { throw std::invalid_argument("Expected seed to be None or a 64 bit unsigned integer."); } diff --git a/src/stim/py/base.pybind.h b/src/stim/py/base.pybind.h index 712deea30..0697275eb 100644 --- a/src/stim/py/base.pybind.h +++ b/src/stim/py/base.pybind.h @@ -28,7 +28,7 @@ namespace stim_pybind { -std::shared_ptr make_py_seeded_rng(const pybind11::object &seed); +std::mt19937_64 make_py_seeded_rng(const pybind11::object &seed); stim::SampleFormat format_to_enum(const std::string &format); bool normalize_index_or_slice( const pybind11::object &index_or_slice, diff --git a/src/stim/py/compiled_detector_sampler.pybind.cc b/src/stim/py/compiled_detector_sampler.pybind.cc index e68e8ed4e..ed27754b5 100644 --- a/src/stim/py/compiled_detector_sampler.pybind.cc +++ b/src/stim/py/compiled_detector_sampler.pybind.cc @@ -25,11 +25,10 @@ using namespace stim; using namespace stim_pybind; -CompiledDetectorSampler::CompiledDetectorSampler(Circuit init_circuit, std::shared_ptr init_prng) +CompiledDetectorSampler::CompiledDetectorSampler(Circuit init_circuit, std::mt19937_64&& rng) : circuit_stats(init_circuit.compute_stats()), circuit(std::move(init_circuit)), - prng(init_prng), - frame_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, *prng) { + frame_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, std::move(rng)) { } pybind11::object CompiledDetectorSampler::sample_to_numpy( @@ -84,7 +83,7 @@ void CompiledDetectorSampler::sample_write( append_observables, out.f, f, - *prng, + frame_sim.rng, obs_out.f, parsed_obs_out_format); } diff --git a/src/stim/py/compiled_detector_sampler.pybind.h b/src/stim/py/compiled_detector_sampler.pybind.h index 24ace6ca9..c5853022e 100644 --- a/src/stim/py/compiled_detector_sampler.pybind.h +++ b/src/stim/py/compiled_detector_sampler.pybind.h @@ -28,13 +28,12 @@ namespace stim_pybind { struct CompiledDetectorSampler { stim::CircuitStats circuit_stats; stim::Circuit circuit; - std::shared_ptr prng; stim::FrameSimulator frame_sim; CompiledDetectorSampler() = delete; CompiledDetectorSampler(const CompiledDetectorSampler &) = delete; CompiledDetectorSampler(CompiledDetectorSampler &&) = default; - CompiledDetectorSampler(stim::Circuit circuit, std::shared_ptr prng); + CompiledDetectorSampler(stim::Circuit circuit, std::mt19937_64 &&rng); pybind11::object sample_to_numpy( size_t num_shots, bool prepend_observables, diff --git a/src/stim/py/compiled_measurement_sampler.pybind.cc b/src/stim/py/compiled_measurement_sampler.pybind.cc index fae638d68..2e85703a4 100644 --- a/src/stim/py/compiled_measurement_sampler.pybind.cc +++ b/src/stim/py/compiled_measurement_sampler.pybind.cc @@ -17,7 +17,6 @@ #include "stim/circuit/circuit.pybind.h" #include "stim/py/base.pybind.h" #include "stim/py/numpy.pybind.h" -#include "stim/simulators/frame_simulator.h" #include "stim/simulators/frame_simulator_util.h" #include "stim/simulators/tableau_simulator.h" @@ -28,12 +27,12 @@ CompiledMeasurementSampler::CompiledMeasurementSampler( simd_bits ref_sample, Circuit circuit, bool skip_reference_sample, - std::shared_ptr prng) - : ref_sample(ref_sample), circuit(circuit), skip_reference_sample(skip_reference_sample), prng(prng) { + std::mt19937_64 &&rng) + : ref_sample(ref_sample), circuit(circuit), skip_reference_sample(skip_reference_sample), rng(std::move(rng)) { } pybind11::object CompiledMeasurementSampler::sample_to_numpy(size_t num_shots, bool bit_packed) { - simd_bit_table sample = sample_batch_measurements(circuit, ref_sample, num_shots, *prng, true); + simd_bit_table sample = sample_batch_measurements(circuit, ref_sample, num_shots, rng, true); size_t bits_per_sample = circuit.count_measurements(); return simd_bit_table_to_numpy(sample, num_shots, bits_per_sample, bit_packed); } @@ -45,7 +44,7 @@ void CompiledMeasurementSampler::sample_write( if (out == nullptr) { throw std::invalid_argument("Failed to open '" + filepath + "' to write."); } - sample_batch_measurements_writing_results_to_disk(circuit, ref_sample, num_samples, out, f, *prng); + sample_batch_measurements_writing_results_to_disk(circuit, ref_sample, num_samples, out, f, rng); fclose(out); } diff --git a/src/stim/py/compiled_measurement_sampler.pybind.h b/src/stim/py/compiled_measurement_sampler.pybind.h index 079d24e3d..ca3f45c92 100644 --- a/src/stim/py/compiled_measurement_sampler.pybind.h +++ b/src/stim/py/compiled_measurement_sampler.pybind.h @@ -28,7 +28,7 @@ struct CompiledMeasurementSampler { const stim::simd_bits ref_sample; const stim::Circuit circuit; const bool skip_reference_sample; - std::shared_ptr prng; + std::mt19937_64 rng; CompiledMeasurementSampler() = delete; CompiledMeasurementSampler(const CompiledMeasurementSampler &) = delete; CompiledMeasurementSampler(CompiledMeasurementSampler &&) = default; @@ -36,7 +36,7 @@ struct CompiledMeasurementSampler { stim::simd_bits ref_sample, stim::Circuit circuit, bool skip_reference_sample, - std::shared_ptr prng); + std::mt19937_64 &&rng); pybind11::object sample_to_numpy(size_t num_shots, bool bit_packed); void sample_write(size_t num_samples, const std::string &filepath, const std::string &format); std::string repr() const; diff --git a/src/stim/simulators/frame_simulator.perf.cc b/src/stim/simulators/frame_simulator.perf.cc index 7fbe84a00..6dab1eaf4 100644 --- a/src/stim/simulators/frame_simulator.perf.cc +++ b/src/stim/simulators/frame_simulator.perf.cc @@ -25,8 +25,7 @@ BENCHMARK(FrameSimulator_depolarize1_100Kqubits_1Ksamples_per1000) { stats.num_qubits = 100 * 1000; size_t num_samples = 1000; double probability = 0.001; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, rng); + FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -45,8 +44,7 @@ BENCHMARK(FrameSimulator_depolarize2_100Kqubits_1Ksamples_per1000) { stats.num_qubits = 100 * 1000; size_t num_samples = 1000; double probability = 0.001; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, rng); + FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -65,8 +63,7 @@ BENCHMARK(FrameSimulator_hadamard_100Kqubits_1Ksamples) { CircuitStats stats; stats.num_qubits = 100 * 1000; size_t num_samples = 1000; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, rng); + FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -85,8 +82,7 @@ BENCHMARK(FrameSimulator_CX_100Kqubits_1Ksamples) { CircuitStats stats; stats.num_qubits = 100 * 1000; size_t num_samples = 1000; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, rng); + FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -108,9 +104,8 @@ BENCHMARK(FrameSimulator_surface_code_rotated_memory_z_d11_r100_batch1024) { params.after_clifford_depolarization = 0.001; auto circuit = generate_surface_code_circuit(params).circuit; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) FrameSimulator sim( - circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, 1024, rng); + circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, 1024, std::mt19937_64(0)); benchmark_go([&]() { sim.reset_all_and_run(circuit); diff --git a/src/stim/simulators/tableau_simulator.perf.cc b/src/stim/simulators/tableau_simulator.perf.cc index d672b6cc0..790f73411 100644 --- a/src/stim/simulators/tableau_simulator.perf.cc +++ b/src/stim/simulators/tableau_simulator.perf.cc @@ -20,8 +20,7 @@ using namespace stim; BENCHMARK(TableauSimulator_CX_10Kqubits) { size_t num_qubits = 10 * 1000; - std::mt19937_64 rng(0); // NOLINT(cert-msc51-cpp) - TableauSimulator sim(rng, num_qubits); + TableauSimulator sim(std::mt19937_64(0), num_qubits); std::vector targets; for (uint32_t k = 0; k < (uint32_t)num_qubits; k++) { diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc index d77a5eff9..ed1199962 100644 --- a/src/stim/simulators/tableau_simulator.pybind.cc +++ b/src/stim/simulators/tableau_simulator.pybind.cc @@ -50,7 +50,7 @@ void do_obj(TableauSimulator &self, const pybind11::object &obj) { template TableauSimulator create_tableau_simulator(const pybind11::object &seed) { - return TableauSimulator(*make_py_seeded_rng(seed)); + return TableauSimulator(make_py_seeded_rng(seed)); } template @@ -1798,7 +1798,7 @@ void stim_pybind::pybind_tableau_simulator_methods( } if (!copy_rng || !seed.is_none()) { - TableauSimulator copy_with_new_rng(self, *make_py_seeded_rng(seed)); + TableauSimulator copy_with_new_rng(self, make_py_seeded_rng(seed)); return copy_with_new_rng; } diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index 2d8316f2c..bcf6148ea 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -603,7 +603,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla [](size_t num_qubits, bool allow_imaginary) { auto rng = make_py_seeded_rng(pybind11::none()); return PyPauliString( - PauliString::random(num_qubits, *rng), allow_imaginary ? ((*rng)() & 1) : false); + PauliString::random(num_qubits, rng), allow_imaginary ? (rng() & 1) : false); }, pybind11::arg("num_qubits"), pybind11::kw_only(), diff --git a/src/stim/stabilizers/tableau.pybind.cc b/src/stim/stabilizers/tableau.pybind.cc index 64cc5d338..ce5eb5d8f 100644 --- a/src/stim/stabilizers/tableau.pybind.cc +++ b/src/stim/stabilizers/tableau.pybind.cc @@ -179,7 +179,8 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_::random(num_qubits, *make_py_seeded_rng(pybind11::none())); + auto rng = make_py_seeded_rng(pybind11::none()); + return Tableau::random(num_qubits, rng); }, pybind11::arg("num_qubits"), clean_doc_string(R"DOC( @@ -2121,8 +2122,7 @@ void stim_pybind::pybind_tableau_methods(pybind11::module &m, pybind11::class_ sim(unused_rng, self.num_qubits); + TableauSimulator sim(std::mt19937_64{0}, self.num_qubits); sim.inv_state = self.inverse(false); auto complex_vec = sim.to_state_vector(little_endian); From 37b8c6f8cf58b5c6bd184275a33147df0ccb6823 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 15:53:46 -0700 Subject: [PATCH 3/8] autoformat --- src/stim/circuit/gate_data.test.cc | 2 +- src/stim/mem/simd_bit_table.test.cc | 3 +- .../py/compiled_detector_sampler.pybind.cc | 2 +- .../py/compiled_measurement_sampler.pybind.cc | 5 +-- src/stim/simulators/frame_simulator.h | 2 +- src/stim/simulators/frame_simulator.perf.cc | 12 ++++-- src/stim/simulators/frame_simulator.test.cc | 37 +++++++++---------- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/stim/circuit/gate_data.test.cc b/src/stim/circuit/gate_data.test.cc index fa2d590cc..c1e38bf37 100644 --- a/src/stim/circuit/gate_data.test.cc +++ b/src/stim/circuit/gate_data.test.cc @@ -172,7 +172,7 @@ TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_correct, { }) TEST_EACH_WORD_SIZE_W(gate_data, stabilizer_flows_are_also_correct_for_decomposed_circuit, { - auto rng =INDEPENDENT_TEST_RNG(); + auto rng = INDEPENDENT_TEST_RNG(); for (const auto &g : GATE_DATA.items) { auto flows = g.flows(); if (flows.empty()) { diff --git a/src/stim/mem/simd_bit_table.test.cc b/src/stim/mem/simd_bit_table.test.cc index 947213a68..5e6537a12 100644 --- a/src/stim/mem/simd_bit_table.test.cc +++ b/src/stim/mem/simd_bit_table.test.cc @@ -207,8 +207,7 @@ TEST_EACH_WORD_SIZE_W(simd_bit_table, random, { t = t.transposed(); ASSERT_NE(t[89], simd_bits(100)); ASSERT_EQ(t[90], simd_bits(100)); - ASSERT_NE( - simd_bit_table::random(10, 10, rng), simd_bit_table::random(10, 10, rng)); + ASSERT_NE(simd_bit_table::random(10, 10, rng), simd_bit_table::random(10, 10, rng)); }) TEST_EACH_WORD_SIZE_W(simd_bit_table, slice_maj, { diff --git a/src/stim/py/compiled_detector_sampler.pybind.cc b/src/stim/py/compiled_detector_sampler.pybind.cc index ed27754b5..5b5480270 100644 --- a/src/stim/py/compiled_detector_sampler.pybind.cc +++ b/src/stim/py/compiled_detector_sampler.pybind.cc @@ -25,7 +25,7 @@ using namespace stim; using namespace stim_pybind; -CompiledDetectorSampler::CompiledDetectorSampler(Circuit init_circuit, std::mt19937_64&& rng) +CompiledDetectorSampler::CompiledDetectorSampler(Circuit init_circuit, std::mt19937_64 &&rng) : circuit_stats(init_circuit.compute_stats()), circuit(std::move(init_circuit)), frame_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, std::move(rng)) { diff --git a/src/stim/py/compiled_measurement_sampler.pybind.cc b/src/stim/py/compiled_measurement_sampler.pybind.cc index 2e85703a4..2588062c0 100644 --- a/src/stim/py/compiled_measurement_sampler.pybind.cc +++ b/src/stim/py/compiled_measurement_sampler.pybind.cc @@ -24,10 +24,7 @@ using namespace stim; using namespace stim_pybind; CompiledMeasurementSampler::CompiledMeasurementSampler( - simd_bits ref_sample, - Circuit circuit, - bool skip_reference_sample, - std::mt19937_64 &&rng) + simd_bits ref_sample, Circuit circuit, bool skip_reference_sample, std::mt19937_64 &&rng) : ref_sample(ref_sample), circuit(circuit), skip_reference_sample(skip_reference_sample), rng(std::move(rng)) { } diff --git a/src/stim/simulators/frame_simulator.h b/src/stim/simulators/frame_simulator.h index 04e32efdb..64b7d1304 100644 --- a/src/stim/simulators/frame_simulator.h +++ b/src/stim/simulators/frame_simulator.h @@ -54,7 +54,7 @@ struct FrameSimulator { simd_bits tmp_storage; // Workspace used when sampling compound error processes. simd_bits last_correlated_error_occurred; // correlated error flag for each instance. simd_bit_table sweep_table; // Shot-to-shot configuration data. - std::mt19937_64 rng; // Random number generator used for generating entropy. + std::mt19937_64 rng; // Random number generator used for generating entropy. // Determines whether e.g. 50% Z errors are multiplied into the frame when measuring in the Z basis. // This is necessary for correct sampling. diff --git a/src/stim/simulators/frame_simulator.perf.cc b/src/stim/simulators/frame_simulator.perf.cc index 6dab1eaf4..fde548c81 100644 --- a/src/stim/simulators/frame_simulator.perf.cc +++ b/src/stim/simulators/frame_simulator.perf.cc @@ -25,7 +25,8 @@ BENCHMARK(FrameSimulator_depolarize1_100Kqubits_1Ksamples_per1000) { stats.num_qubits = 100 * 1000; size_t num_samples = 1000; double probability = 0.001; - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); + FrameSimulator sim( + stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -44,7 +45,8 @@ BENCHMARK(FrameSimulator_depolarize2_100Kqubits_1Ksamples_per1000) { stats.num_qubits = 100 * 1000; size_t num_samples = 1000; double probability = 0.001; - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); + FrameSimulator sim( + stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -63,7 +65,8 @@ BENCHMARK(FrameSimulator_hadamard_100Kqubits_1Ksamples) { CircuitStats stats; stats.num_qubits = 100 * 1000; size_t num_samples = 1000; - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); + FrameSimulator sim( + stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { @@ -82,7 +85,8 @@ BENCHMARK(FrameSimulator_CX_100Kqubits_1Ksamples) { CircuitStats stats; stats.num_qubits = 100 * 1000; size_t num_samples = 1000; - FrameSimulator sim(stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); + FrameSimulator sim( + stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_samples, std::mt19937_64(0)); std::vector targets; for (uint32_t k = 0; k < stats.num_qubits; k++) { diff --git a/src/stim/simulators/frame_simulator.test.cc b/src/stim/simulators/frame_simulator.test.cc index 57279cc07..9b2e04fb1 100644 --- a/src/stim/simulators/frame_simulator.test.cc +++ b/src/stim/simulators/frame_simulator.test.cc @@ -46,7 +46,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, get_set_frame, { circuit_stats.num_qubits = 501; circuit_stats.max_lookback = 999; - FrameSimulator big_sim(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1001, INDEPENDENT_TEST_RNG()); + FrameSimulator big_sim( + circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1001, INDEPENDENT_TEST_RNG()); big_sim.set_frame(258, PauliString::from_func(false, 501, [](size_t k) { return "_X"[k == 303]; })); @@ -825,7 +826,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, classical_controls, { TEST_EACH_WORD_SIZE_W(FrameSimulator, record_gets_trimmed, { Circuit c = Circuit("M 0 1 2 3 4 5 6 7 8 9"); - FrameSimulator sim(c.compute_stats(), FrameSimulatorMode::STREAM_MEASUREMENTS_TO_DISK, 768, INDEPENDENT_TEST_RNG()); + FrameSimulator sim( + c.compute_stats(), FrameSimulatorMode::STREAM_MEASUREMENTS_TO_DISK, 768, INDEPENDENT_TEST_RNG()); MeasureRecordBatchWriter b(tmpfile(), 768, SAMPLE_FORMAT_B8); for (size_t k = 0; k < 1000; k++) { sim.do_MZ(c.operations[0]); @@ -866,8 +868,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_single_shot, { } )circuit"); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); + sample_batch_measurements_writing_results_to_disk(circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 30000; k += 3) { @@ -888,8 +889,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, block_results_triple_shot, { } )circuit"); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); + sample_batch_measurements_writing_results_to_disk(circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -914,8 +914,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results, { } )circuit"); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); + sample_batch_measurements_writing_results_to_disk(circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t k = 0; k < 30000; k += 3) { @@ -934,8 +933,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_many_shots, { M 0 1 2 )circuit"); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 2049, tmp, SAMPLE_FORMAT_01, rng); + sample_batch_measurements_writing_results_to_disk(circuit, simd_bits(0), 2049, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); ASSERT_EQ(result.size(), 2049 * 4); @@ -958,8 +956,7 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, stream_results_triple_shot, { } )circuit"); FILE *tmp = tmpfile(); - sample_batch_measurements_writing_results_to_disk( - circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); + sample_batch_measurements_writing_results_to_disk(circuit, simd_bits(0), 3, tmp, SAMPLE_FORMAT_01, rng); auto result = rewind_read_close(tmp); for (size_t rep = 0; rep < 3; rep++) { @@ -1488,8 +1485,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mpad, { auto circuit = Circuit(R"CIRCUIT( MPAD 0 1 )CIRCUIT"); - auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); + auto sample = + sample_batch_measurements(circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[0][k], false); ASSERT_EQ(sample[1][k], true); @@ -1506,8 +1503,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_basis, { RZ 0 1 MZZ 0 1 )CIRCUIT"); - auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); + auto sample = + sample_batch_measurements(circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[0][k], false); ASSERT_EQ(sample[1][k], false); @@ -1522,8 +1519,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_inversion, { MYY 0 1 0 !1 !0 1 !0 !1 MZZ 0 1 0 !1 !0 1 !0 !1 )CIRCUIT"); - auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); + auto sample = + sample_batch_measurements(circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); for (size_t k = 0; k < 100; k++) { ASSERT_EQ(sample[1][k], !sample[0][k]); ASSERT_EQ(sample[2][k], !sample[0][k]); @@ -1541,8 +1538,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, mxxyyzz_inversion, { TEST_EACH_WORD_SIZE_W(FrameSimulator, runs_on_general_circuit, { auto rng = INDEPENDENT_TEST_RNG(); auto circuit = generate_test_circuit_with_all_operations(); - auto sample = sample_batch_measurements( - circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); + auto sample = + sample_batch_measurements(circuit, TableauSimulator::reference_sample_circuit(circuit), 100, rng, false); ASSERT_GT(sample.num_simd_words_minor, 0); ASSERT_GT(sample.num_simd_words_major, 0); }) From a840b1bde0832dd9dd8b884ecff0845e744845b6 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 16:07:42 -0700 Subject: [PATCH 4/8] sync js --- glue/javascript/common.js.cc | 11 ----------- glue/javascript/common.js.h | 2 -- glue/javascript/pauli_string.js.cc | 3 ++- glue/javascript/tableau.js.cc | 3 ++- glue/javascript/tableau_simulator.js.cc | 2 +- 5 files changed, 5 insertions(+), 16 deletions(-) diff --git a/glue/javascript/common.js.cc b/glue/javascript/common.js.cc index 7b13631e8..084ed9098 100644 --- a/glue/javascript/common.js.cc +++ b/glue/javascript/common.js.cc @@ -2,17 +2,6 @@ using namespace stim; -static bool shared_rng_initialized; -static std::mt19937_64 shared_rng; - -std::mt19937_64 &JS_BIND_SHARED_RNG() { - if (!shared_rng_initialized) { - shared_rng = externally_seeded_rng(); - shared_rng_initialized = true; - } - return shared_rng; -} - uint32_t js_val_to_uint32_t(const emscripten::val &val) { double v = val.as(); double f = floor(v); diff --git a/glue/javascript/common.js.h b/glue/javascript/common.js.h index b8b825bd2..bf2710717 100644 --- a/glue/javascript/common.js.h +++ b/glue/javascript/common.js.h @@ -5,8 +5,6 @@ #include "stim/probability_util.h" -std::mt19937_64 &JS_BIND_SHARED_RNG(); - template emscripten::val vec_to_js_array(const std::vector &items) { emscripten::val result = emscripten::val::array(); diff --git a/glue/javascript/pauli_string.js.cc b/glue/javascript/pauli_string.js.cc index fe5620cd7..f700eafe4 100644 --- a/glue/javascript/pauli_string.js.cc +++ b/glue/javascript/pauli_string.js.cc @@ -21,7 +21,8 @@ ExposedPauliString::ExposedPauliString(const emscripten::val &arg) : pauli_strin } ExposedPauliString ExposedPauliString::random(size_t n) { - return ExposedPauliString(PauliString::random(n, JS_BIND_SHARED_RNG())); + auto rng = externally_seeded_rng(); + return ExposedPauliString(PauliString::random(n, rng)); } ExposedPauliString ExposedPauliString::times(const ExposedPauliString &other) const { diff --git a/glue/javascript/tableau.js.cc b/glue/javascript/tableau.js.cc index c29ade7d6..e31a2cbb6 100644 --- a/glue/javascript/tableau.js.cc +++ b/glue/javascript/tableau.js.cc @@ -14,7 +14,8 @@ ExposedTableau::ExposedTableau(int n) : tableau(n) { } ExposedTableau ExposedTableau::random(int n) { - return ExposedTableau(Tableau::random(n, JS_BIND_SHARED_RNG())); + auto rng = externally_seeded_rng(); + return ExposedTableau(Tableau::random(n, rng)); } ExposedTableau ExposedTableau::from_named_gate(const std::string &name) { diff --git a/glue/javascript/tableau_simulator.js.cc b/glue/javascript/tableau_simulator.js.cc index c4c087d1d..7fb6d8e19 100644 --- a/glue/javascript/tableau_simulator.js.cc +++ b/glue/javascript/tableau_simulator.js.cc @@ -58,7 +58,7 @@ static JsCircuitInstruction args_to_target_pairs(TableauSimulator Date: Sat, 19 Aug 2023 20:12:46 -0700 Subject: [PATCH 5/8] - Add `stim.FlipSimulator` - Add `stim.FlipSimulator.__init__` - Add `stim.FlipSimulator.batch_size` - Add `stim.FlipSimulator.do` - Add `stim.FlipSimulator.do_circuit` - Add `stim.FlipSimulator.do_instruction` - Add `stim.FlipSimulator.get_detector_flips` - Add `stim.FlipSimulator.get_observable_flips` - Add `stim.FlipSimulator.get_measurement_flips` - Add `stim.FlipSimulator.num_detectors` - Add `stim.FlipSimulator.num_measurements` - Add `stim.FlipSimulator.num_observables` - Add `stim.FlipSimulator.num_qubits` - Add `stim.FlipSimulator.peek_current_pauli_errors` - Add `frame_simulator.pybind.{h,cc}` - Add `stim::simd_bit_table::read_across_majors_at_minor_index` - Add `stim::simd_bit_table::copy_into_different_size_table` - Add `stim::simd_bit_table::resize` - Add `stim.FlipSimulator` - Split `stim::FrameSimulator::reset_all_and_run` into two methods - Add `FrameSimulatorMode::STORE_EVERYTHING_TO_MEMORY` - Add `FrameSimulator::ensure_safe_to_do_circuit_with_stats` - Add `FrameSimulator::safe_do_instruction` - Add `FrameSimulator::safe_do_circuit` - Move `stim::CircuitStats` from circuit file to circuit instruction file - Add `stim::CircuitInstruction::compute_stats` - Add `stim::CircuitInstruction::add_stats_to` --- doc/python_api_reference_vDev.md | 507 ++++++++++++ doc/stim.pyi | 395 +++++++++ file_lists/python_api_files | 1 + glue/python/src/stim/__init__.pyi | 395 +++++++++ src/stim/circuit/circuit.cc | 52 +- src/stim/circuit/circuit.h | 11 - src/stim/circuit/circuit_instruction.cc | 61 ++ src/stim/circuit/circuit_instruction.h | 28 + src/stim/mem/simd_bit_table.h | 10 + src/stim/mem/simd_bit_table.inl | 42 + src/stim/mem/simd_bit_table.test.cc | 106 ++- .../py/compiled_detector_sampler.pybind.cc | 3 +- src/stim/py/stim.pybind.cc | 3 + src/stim/simulators/frame_simulator.h | 20 +- src/stim/simulators/frame_simulator.inl | 67 +- src/stim/simulators/frame_simulator.perf.cc | 3 +- src/stim/simulators/frame_simulator.pybind.cc | 757 ++++++++++++++++++ src/stim/simulators/frame_simulator.pybind.h | 17 + src/stim/simulators/frame_simulator.test.cc | 6 +- .../simulators/frame_simulator_pybind_test.py | 114 +++ src/stim/simulators/frame_simulator_util.inl | 12 +- 21 files changed, 2526 insertions(+), 84 deletions(-) create mode 100644 src/stim/simulators/frame_simulator.pybind.cc create mode 100644 src/stim/simulators/frame_simulator.pybind.h create mode 100644 src/stim/simulators/frame_simulator_pybind_test.py diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index bf19a5642..f719ffb5a 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -167,6 +167,20 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.ExplainedError.__init__`](#stim.ExplainedError.__init__) - [`stim.ExplainedError.circuit_error_locations`](#stim.ExplainedError.circuit_error_locations) - [`stim.ExplainedError.dem_error_terms`](#stim.ExplainedError.dem_error_terms) +- [`stim.FlipSimulator`](#stim.FlipSimulator) + - [`stim.FlipSimulator.__init__`](#stim.FlipSimulator.__init__) + - [`stim.FlipSimulator.batch_size`](#stim.FlipSimulator.batch_size) + - [`stim.FlipSimulator.do`](#stim.FlipSimulator.do) + - [`stim.FlipSimulator.do_circuit`](#stim.FlipSimulator.do_circuit) + - [`stim.FlipSimulator.do_instruction`](#stim.FlipSimulator.do_instruction) + - [`stim.FlipSimulator.get_detector_flips`](#stim.FlipSimulator.get_detector_flips) + - [`stim.FlipSimulator.get_measurement_flips`](#stim.FlipSimulator.get_measurement_flips) + - [`stim.FlipSimulator.get_observable_flips`](#stim.FlipSimulator.get_observable_flips) + - [`stim.FlipSimulator.num_detectors`](#stim.FlipSimulator.num_detectors) + - [`stim.FlipSimulator.num_measurements`](#stim.FlipSimulator.num_measurements) + - [`stim.FlipSimulator.num_observables`](#stim.FlipSimulator.num_observables) + - [`stim.FlipSimulator.num_qubits`](#stim.FlipSimulator.num_qubits) + - [`stim.FlipSimulator.peek_current_pauli_errors`](#stim.FlipSimulator.peek_current_pauli_errors) - [`stim.FlippedMeasurement`](#stim.FlippedMeasurement) - [`stim.FlippedMeasurement.__init__`](#stim.FlippedMeasurement.__init__) - [`stim.FlippedMeasurement.observable`](#stim.FlippedMeasurement.observable) @@ -5729,6 +5743,499 @@ def dem_error_terms( """ ``` + +```python +# stim.FlipSimulator + +# (at top-level in the stim module) +class FlipSimulator: + """A simulator that tracks whether things are flipped, instead of what they are. + + Tracking flips is significantly cheaper than tracking actual values, requiring + O(1) work per gate (compared to O(n) for unitary operations and O(n^2) for + collapsing operations in the tableau simulator, where n is the qubit count). + + Supports interactive usage, where gates and measurements are applied on demand. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator() + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.__init__ + +# (in class stim.FlipSimulator) +def __init__( + self, + *, + batch_size: int, + disable_stabilizer_randomization: bool = False, + num_qubits: int = 0, + seed: Optional[int] = None, +) -> None: + """Initializes a stim.FlipSimulator. + + Args: + batch_size: For speed, the flip simulator simulates many instances in + parallel. This argument determines the number of parallel instances. + + It's recommended to use a multiple of 256, because internally the state + of the instances is striped across SSE (128 bit) or AVX (256 bit) + words with one bit in the word belonging to each instance. The result is + that, even if you only ask for 1 instance, probably the same amount of + work is being done as if you'd asked for 256 instances. The extra + results just aren't being used, creating waste. + + disable_stabilizer_randomization: Determines whether or not the flip + simulator uses stabilizer randomization. Defaults to False (stabilizer + randomization used). Set to True to disable stabilizer randomization. + + Stabilizer randomization means that, when a qubit is initialized or + measured in the Z basis, a Z error is added to the qubit with 50% + probability. More generally, anytime a stabilizer is introduced into + the system by any means, an error equal to that stabilizer is applied + with 50% probability. This ensures that observables anticommuting with + stabilizers of the system must be maximally uncertain. In other words, + this feature enforces Heisenberg's uncertainty principle. + + This is a safety feature that you should not turn off unless you have a + reason to do so. Stabilizer randomization is turned on by default + because it catches mistakes. For example, suppose you are trying to + create a stabilizer code but you accidentally have the code measure two + anticommuting stabilizers. With stabilizer randomization turned off, it + will look like this code works. With stabilizer randomization turned on, + the two measurements will correctly randomize each other revealing that + the code doesn't work. + + In some use cases, stabilizer randomization is a hindrance instead of + helpful. For example, if you are using the flip simulator to understand + how an error propagates through the system, the stabilizer randomization + will be introducing error terms that you don't want. + + num_qubits: Sets the initial number of qubits tracked by the simulation. + The simulator will still automatically resize as needed when qubits + beyond this limit are touched. + + This parameter exists as a way to hint at the desired size of the + simulator's state for performance, and to ensure methods that + peek at the size have the expected size from the start instead of + only after the relevant qubits have been touched. + + seed: PARTIALLY determines simulation results by deterministically seeding + the random number generator. + + Must be None or an integer in range(2**64). + + Defaults to None. When None, the prng is seeded from system entropy. + + When set to an integer, making the exact same series calls on the exact + same machine with the exact same version of Stim will produce the exact + same simulation results. + + CAUTION: simulation results *WILL NOT* be consistent between versions of + Stim. This restriction is present to make it possible to have future + optimizations to the random sampling, and is enforced by introducing + intentional differences in the seeding strategy from version to version. + + CAUTION: simulation results *MAY NOT* be consistent across machines that + differ in the width of supported SIMD instructions. For example, using + the same seed on a machine that supports AVX instructions and one that + only supports SSE instructions may produce different simulation results. + + CAUTION: simulation results *MAY NOT* be consistent if you vary how the + circuit is executed. For example, reordering whether a reset on one + qubit happens before or after a reset on another qubit can result in + different measurement results being observed starting from the same + seed. + + Returns: + An initialized stim.FlipSimulator. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator(seed=0) + >>> s2 = stim.FlipSimulator(seed=0) + >>> s.x_error(0, p=0.1) + >>> s2.x_error(0, p=0.1) + >>> s.measure(0) == s2.measure(0) + True + """ +``` + + +```python +# stim.FlipSimulator.batch_size + +# (in class stim.FlipSimulator) +@property +def batch_size( + self, +) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ +``` + + +```python +# stim.FlipSimulator.do + +# (in class stim.FlipSimulator) +def do( + self, + obj: Union[stim.Circuit, stim.CircuitInstruction, stim.CircuitRepeatBlock], +) -> None: + """Applies a circuit or circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + obj: The circuit or instruction to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.do_circuit + +# (in class stim.FlipSimulator) +def do_circuit( + self, + circuit: stim.Circuit, +) -> None: + """Applies a all the instructions in a circuit to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.do_instruction + +# (in class stim.FlipSimulator) +def do_instruction( + self, + instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], +) -> None: + """Applies a circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.get_detector_flips + +# (in class stim.FlipSimulator) +def get_detector_flips( + self, + *, + detector_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, +) -> np.ndarray: + """Retrieves detector flip data from the simulator's detection event record. + + Args: + record_index: Identifies a detector to read results from. + Setting this to None (default) returns results from all detectors. + Otherwise this should be an integer in range(0, self.num_detectors). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_detectors, self.batch_size), where the first index is + the detector_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying detector_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a detector_index (or a 0d array, a boolean, if detector_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.get_measurement_flips + +# (in class stim.FlipSimulator) +def get_measurement_flips( + self, + *, + record_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, +) -> np.ndarray: + """Retrieves measurement flip data from the simulator's measurement record. + + Args: + record_index: Identifies a measurement to read results from. + Setting this to None (default) returns results from all measurements. + Setting this to a non-negative integer indexes measurements by the order + they occurred. For example, record index 0 is the first measurement. + Setting this to a negative integer indexes measurements by recency. + For example, recording index -1 is the most recent measurement. + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be set to an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_measurements, self.batch_size), where the first index is + the measurement_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying record_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a measurement_index (or a 0d array, a boolean, if record_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.get_observable_flips + +# (in class stim.FlipSimulator) +def get_observable_flips( + self, + *, + observable_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, +) -> np.ndarray: + """Retrieves observable flip data from the simulator's detection event record. + + Args: + record_index: Identifies a observable to read results from. + Setting this to None (default) returns results from all observables. + Otherwise this should be an integer in range(0, self.num_observables). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_observables, self.batch_size), where the first index is + the observable_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying observable_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a observable_index (or a 0d array, a boolean, if observable_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ +``` + + +```python +# stim.FlipSimulator.num_detectors + +# (in class stim.FlipSimulator) +@property +def num_detectors( + self, +) -> int: + """Returns the number of detectors that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_detectors + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 0 + ... DETECTOR rec[-1] rec[-2] + ... ''')) + >>> sim.num_detectors + 1 + """ +``` + + +```python +# stim.FlipSimulator.num_measurements + +# (in class stim.FlipSimulator) +@property +def num_measurements( + self, +) -> int: + """Returns the number of measurements that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_measurements + 0 + >>> sim.measure(5) + >>> sim.num_measurements + 1 + """ +``` + + +```python +# stim.FlipSimulator.num_observables + +# (in class stim.FlipSimulator) +@property +def num_observables( + self, +) -> int: + """Returns the number of observables currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_observables + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 + ... OBSERVABLE_INCLUDE(4) rec[-1] + ... ''')) + >>> sim.num_observables + 5 + """ +``` + + +```python +# stim.FlipSimulator.num_qubits + +# (in class stim.FlipSimulator) +@property +def num_qubits( + self, +) -> int: + """Returns the number of qubits currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_qubits + 0 + >>> sim.h(5) + >>> sim.num_qubits + 6 + """ +``` + + +```python +# stim.FlipSimulator.peek_current_pauli_errors + +# (in class stim.FlipSimulator) +def peek_current_errors( + self, +) -> np.ndarray: + """Creates a numpy array describing the current pauli errors. + + Returns: + A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + + Each entry in the array is the Pauli error on one qubit in one instance, + using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 + then there's a Y error on the qubit with index 5 in the shot with index 3. + + Examples: + >>> import stim + >>> assert False + """ +``` + ```python # stim.FlippedMeasurement diff --git a/doc/stim.pyi b/doc/stim.pyi index 170c5b615..f0865de7e 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4317,6 +4317,401 @@ class ExplainedError: ) -> List[stim.DemTargetWithCoords]: """The detectors and observables flipped by this error mechanism. """ +class FlipSimulator: + """A simulator that tracks whether things are flipped, instead of what they are. + + Tracking flips is significantly cheaper than tracking actual values, requiring + O(1) work per gate (compared to O(n) for unitary operations and O(n^2) for + collapsing operations in the tableau simulator, where n is the qubit count). + + Supports interactive usage, where gates and measurements are applied on demand. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator() + >>> assert False + """ + def __init__( + self, + *, + batch_size: int, + disable_stabilizer_randomization: bool = False, + num_qubits: int = 0, + seed: Optional[int] = None, + ) -> None: + """Initializes a stim.FlipSimulator. + + Args: + batch_size: For speed, the flip simulator simulates many instances in + parallel. This argument determines the number of parallel instances. + + It's recommended to use a multiple of 256, because internally the state + of the instances is striped across SSE (128 bit) or AVX (256 bit) + words with one bit in the word belonging to each instance. The result is + that, even if you only ask for 1 instance, probably the same amount of + work is being done as if you'd asked for 256 instances. The extra + results just aren't being used, creating waste. + + disable_stabilizer_randomization: Determines whether or not the flip + simulator uses stabilizer randomization. Defaults to False (stabilizer + randomization used). Set to True to disable stabilizer randomization. + + Stabilizer randomization means that, when a qubit is initialized or + measured in the Z basis, a Z error is added to the qubit with 50% + probability. More generally, anytime a stabilizer is introduced into + the system by any means, an error equal to that stabilizer is applied + with 50% probability. This ensures that observables anticommuting with + stabilizers of the system must be maximally uncertain. In other words, + this feature enforces Heisenberg's uncertainty principle. + + This is a safety feature that you should not turn off unless you have a + reason to do so. Stabilizer randomization is turned on by default + because it catches mistakes. For example, suppose you are trying to + create a stabilizer code but you accidentally have the code measure two + anticommuting stabilizers. With stabilizer randomization turned off, it + will look like this code works. With stabilizer randomization turned on, + the two measurements will correctly randomize each other revealing that + the code doesn't work. + + In some use cases, stabilizer randomization is a hindrance instead of + helpful. For example, if you are using the flip simulator to understand + how an error propagates through the system, the stabilizer randomization + will be introducing error terms that you don't want. + + num_qubits: Sets the initial number of qubits tracked by the simulation. + The simulator will still automatically resize as needed when qubits + beyond this limit are touched. + + This parameter exists as a way to hint at the desired size of the + simulator's state for performance, and to ensure methods that + peek at the size have the expected size from the start instead of + only after the relevant qubits have been touched. + + seed: PARTIALLY determines simulation results by deterministically seeding + the random number generator. + + Must be None or an integer in range(2**64). + + Defaults to None. When None, the prng is seeded from system entropy. + + When set to an integer, making the exact same series calls on the exact + same machine with the exact same version of Stim will produce the exact + same simulation results. + + CAUTION: simulation results *WILL NOT* be consistent between versions of + Stim. This restriction is present to make it possible to have future + optimizations to the random sampling, and is enforced by introducing + intentional differences in the seeding strategy from version to version. + + CAUTION: simulation results *MAY NOT* be consistent across machines that + differ in the width of supported SIMD instructions. For example, using + the same seed on a machine that supports AVX instructions and one that + only supports SSE instructions may produce different simulation results. + + CAUTION: simulation results *MAY NOT* be consistent if you vary how the + circuit is executed. For example, reordering whether a reset on one + qubit happens before or after a reset on another qubit can result in + different measurement results being observed starting from the same + seed. + + Returns: + An initialized stim.FlipSimulator. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator(seed=0) + >>> s2 = stim.FlipSimulator(seed=0) + >>> s.x_error(0, p=0.1) + >>> s2.x_error(0, p=0.1) + >>> s.measure(0) == s2.measure(0) + True + """ + @property + def batch_size( + self, + ) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ + def do( + self, + obj: Union[stim.Circuit, stim.CircuitInstruction, stim.CircuitRepeatBlock], + ) -> None: + """Applies a circuit or circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + obj: The circuit or instruction to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def do_circuit( + self, + circuit: stim.Circuit, + ) -> None: + """Applies a all the instructions in a circuit to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def do_instruction( + self, + instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], + ) -> None: + """Applies a circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def get_detector_flips( + self, + *, + detector_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves detector flip data from the simulator's detection event record. + + Args: + record_index: Identifies a detector to read results from. + Setting this to None (default) returns results from all detectors. + Otherwise this should be an integer in range(0, self.num_detectors). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_detectors, self.batch_size), where the first index is + the detector_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying detector_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a detector_index (or a 0d array, a boolean, if detector_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + def get_measurement_flips( + self, + *, + record_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves measurement flip data from the simulator's measurement record. + + Args: + record_index: Identifies a measurement to read results from. + Setting this to None (default) returns results from all measurements. + Setting this to a non-negative integer indexes measurements by the order + they occurred. For example, record index 0 is the first measurement. + Setting this to a negative integer indexes measurements by recency. + For example, recording index -1 is the most recent measurement. + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be set to an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_measurements, self.batch_size), where the first index is + the measurement_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying record_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a measurement_index (or a 0d array, a boolean, if record_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + def get_observable_flips( + self, + *, + observable_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves observable flip data from the simulator's detection event record. + + Args: + record_index: Identifies a observable to read results from. + Setting this to None (default) returns results from all observables. + Otherwise this should be an integer in range(0, self.num_observables). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_observables, self.batch_size), where the first index is + the observable_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying observable_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a observable_index (or a 0d array, a boolean, if observable_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + @property + def num_detectors( + self, + ) -> int: + """Returns the number of detectors that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_detectors + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 0 + ... DETECTOR rec[-1] rec[-2] + ... ''')) + >>> sim.num_detectors + 1 + """ + @property + def num_measurements( + self, + ) -> int: + """Returns the number of measurements that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_measurements + 0 + >>> sim.measure(5) + >>> sim.num_measurements + 1 + """ + @property + def num_observables( + self, + ) -> int: + """Returns the number of observables currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_observables + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 + ... OBSERVABLE_INCLUDE(4) rec[-1] + ... ''')) + >>> sim.num_observables + 5 + """ + @property + def num_qubits( + self, + ) -> int: + """Returns the number of qubits currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_qubits + 0 + >>> sim.h(5) + >>> sim.num_qubits + 6 + """ + def peek_current_errors( + self, + ) -> np.ndarray: + """Creates a numpy array describing the current pauli errors. + + Returns: + A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + + Each entry in the array is the Pauli error on one qubit in one instance, + using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 + then there's a Y error on the qubit with index 5 in the shot with index 3. + + Examples: + >>> import stim + >>> assert False + """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/file_lists/python_api_files b/file_lists/python_api_files index c58470782..c8e36a645 100644 --- a/file_lists/python_api_files +++ b/file_lists/python_api_files @@ -16,6 +16,7 @@ src/stim/py/march.pybind.cc src/stim/py/numpy.pybind.cc src/stim/py/stim.pybind.cc src/stim/simulators/dem_sampler.pybind.cc +src/stim/simulators/frame_simulator.pybind.cc src/stim/simulators/matched_error.pybind.cc src/stim/simulators/measurements_to_detection_events.pybind.cc src/stim/simulators/tableau_simulator.pybind.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 170c5b615..f0865de7e 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4317,6 +4317,401 @@ class ExplainedError: ) -> List[stim.DemTargetWithCoords]: """The detectors and observables flipped by this error mechanism. """ +class FlipSimulator: + """A simulator that tracks whether things are flipped, instead of what they are. + + Tracking flips is significantly cheaper than tracking actual values, requiring + O(1) work per gate (compared to O(n) for unitary operations and O(n^2) for + collapsing operations in the tableau simulator, where n is the qubit count). + + Supports interactive usage, where gates and measurements are applied on demand. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator() + >>> assert False + """ + def __init__( + self, + *, + batch_size: int, + disable_stabilizer_randomization: bool = False, + num_qubits: int = 0, + seed: Optional[int] = None, + ) -> None: + """Initializes a stim.FlipSimulator. + + Args: + batch_size: For speed, the flip simulator simulates many instances in + parallel. This argument determines the number of parallel instances. + + It's recommended to use a multiple of 256, because internally the state + of the instances is striped across SSE (128 bit) or AVX (256 bit) + words with one bit in the word belonging to each instance. The result is + that, even if you only ask for 1 instance, probably the same amount of + work is being done as if you'd asked for 256 instances. The extra + results just aren't being used, creating waste. + + disable_stabilizer_randomization: Determines whether or not the flip + simulator uses stabilizer randomization. Defaults to False (stabilizer + randomization used). Set to True to disable stabilizer randomization. + + Stabilizer randomization means that, when a qubit is initialized or + measured in the Z basis, a Z error is added to the qubit with 50% + probability. More generally, anytime a stabilizer is introduced into + the system by any means, an error equal to that stabilizer is applied + with 50% probability. This ensures that observables anticommuting with + stabilizers of the system must be maximally uncertain. In other words, + this feature enforces Heisenberg's uncertainty principle. + + This is a safety feature that you should not turn off unless you have a + reason to do so. Stabilizer randomization is turned on by default + because it catches mistakes. For example, suppose you are trying to + create a stabilizer code but you accidentally have the code measure two + anticommuting stabilizers. With stabilizer randomization turned off, it + will look like this code works. With stabilizer randomization turned on, + the two measurements will correctly randomize each other revealing that + the code doesn't work. + + In some use cases, stabilizer randomization is a hindrance instead of + helpful. For example, if you are using the flip simulator to understand + how an error propagates through the system, the stabilizer randomization + will be introducing error terms that you don't want. + + num_qubits: Sets the initial number of qubits tracked by the simulation. + The simulator will still automatically resize as needed when qubits + beyond this limit are touched. + + This parameter exists as a way to hint at the desired size of the + simulator's state for performance, and to ensure methods that + peek at the size have the expected size from the start instead of + only after the relevant qubits have been touched. + + seed: PARTIALLY determines simulation results by deterministically seeding + the random number generator. + + Must be None or an integer in range(2**64). + + Defaults to None. When None, the prng is seeded from system entropy. + + When set to an integer, making the exact same series calls on the exact + same machine with the exact same version of Stim will produce the exact + same simulation results. + + CAUTION: simulation results *WILL NOT* be consistent between versions of + Stim. This restriction is present to make it possible to have future + optimizations to the random sampling, and is enforced by introducing + intentional differences in the seeding strategy from version to version. + + CAUTION: simulation results *MAY NOT* be consistent across machines that + differ in the width of supported SIMD instructions. For example, using + the same seed on a machine that supports AVX instructions and one that + only supports SSE instructions may produce different simulation results. + + CAUTION: simulation results *MAY NOT* be consistent if you vary how the + circuit is executed. For example, reordering whether a reset on one + qubit happens before or after a reset on another qubit can result in + different measurement results being observed starting from the same + seed. + + Returns: + An initialized stim.FlipSimulator. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator(seed=0) + >>> s2 = stim.FlipSimulator(seed=0) + >>> s.x_error(0, p=0.1) + >>> s2.x_error(0, p=0.1) + >>> s.measure(0) == s2.measure(0) + True + """ + @property + def batch_size( + self, + ) -> int: + """Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim.batch_size + 42 + """ + def do( + self, + obj: Union[stim.Circuit, stim.CircuitInstruction, stim.CircuitRepeatBlock], + ) -> None: + """Applies a circuit or circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + obj: The circuit or instruction to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def do_circuit( + self, + circuit: stim.Circuit, + ) -> None: + """Applies a all the instructions in a circuit to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def do_instruction( + self, + instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], + ) -> None: + """Applies a circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + """ + def get_detector_flips( + self, + *, + detector_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves detector flip data from the simulator's detection event record. + + Args: + record_index: Identifies a detector to read results from. + Setting this to None (default) returns results from all detectors. + Otherwise this should be an integer in range(0, self.num_detectors). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_detectors, self.batch_size), where the first index is + the detector_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying detector_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a detector_index (or a 0d array, a boolean, if detector_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + def get_measurement_flips( + self, + *, + record_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves measurement flip data from the simulator's measurement record. + + Args: + record_index: Identifies a measurement to read results from. + Setting this to None (default) returns results from all measurements. + Setting this to a non-negative integer indexes measurements by the order + they occurred. For example, record index 0 is the first measurement. + Setting this to a negative integer indexes measurements by recency. + For example, recording index -1 is the most recent measurement. + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be set to an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_measurements, self.batch_size), where the first index is + the measurement_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying record_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a measurement_index (or a 0d array, a boolean, if record_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + def get_observable_flips( + self, + *, + observable_index: Optional[int] = None, + instance_index: Optional[int] = None, + bit_packed: bool = False, + ) -> np.ndarray: + """Retrieves observable flip data from the simulator's detection event record. + + Args: + record_index: Identifies a observable to read results from. + Setting this to None (default) returns results from all observables. + Otherwise this should be an integer in range(0, self.num_observables). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_observables, self.batch_size), where the first index is + the observable_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying observable_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a observable_index (or a 0d array, a boolean, if observable_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + """ + @property + def num_detectors( + self, + ) -> int: + """Returns the number of detectors that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_detectors + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 0 + ... DETECTOR rec[-1] rec[-2] + ... ''')) + >>> sim.num_detectors + 1 + """ + @property + def num_measurements( + self, + ) -> int: + """Returns the number of measurements that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_measurements + 0 + >>> sim.measure(5) + >>> sim.num_measurements + 1 + """ + @property + def num_observables( + self, + ) -> int: + """Returns the number of observables currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_observables + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 + ... OBSERVABLE_INCLUDE(4) rec[-1] + ... ''')) + >>> sim.num_observables + 5 + """ + @property + def num_qubits( + self, + ) -> int: + """Returns the number of qubits currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_qubits + 0 + >>> sim.h(5) + >>> sim.num_qubits + 6 + """ + def peek_current_errors( + self, + ) -> np.ndarray: + """Creates a numpy array describing the current pauli errors. + + Returns: + A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + + Each entry in the array is the Pauli error on one qubit in one instance, + using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 + then there's a Y error on the qubit with index 5 in the shot with index 3. + + Examples: + >>> import stim + >>> assert False + """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/src/stim/circuit/circuit.cc b/src/stim/circuit/circuit.cc index 299bcf6aa..d88454eee 100644 --- a/src/stim/circuit/circuit.cc +++ b/src/stim/circuit/circuit.cc @@ -706,59 +706,9 @@ size_t Circuit::count_sweep_bits() const { CircuitStats Circuit::compute_stats() const { CircuitStats total; - for (const auto &op : operations) { - if (op.gate_type == REPEAT) { - // Recurse into blocks. - auto sub = op.repeat_block_body(*this).compute_stats(); - auto reps = op.repeat_block_rep_count(); - total.num_observables = std::max(total.num_observables, sub.num_observables); - total.num_qubits = std::max(total.num_qubits, sub.num_qubits); - total.max_lookback = std::max(total.max_lookback, sub.max_lookback); - total.num_sweep_bits = std::max(total.num_sweep_bits, sub.num_sweep_bits); - total.num_detectors = add_saturate(total.num_detectors, mul_saturate(sub.num_detectors, reps)); - total.num_measurements = add_saturate(total.num_measurements, mul_saturate(sub.num_measurements, reps)); - total.num_ticks = add_saturate(total.num_ticks, mul_saturate(sub.num_ticks, reps)); - continue; - } - - for (auto t : op.targets) { - auto v = t.data & TARGET_VALUE_MASK; - // Qubit counting. - if (!(t.data & (TARGET_RECORD_BIT | TARGET_SWEEP_BIT))) { - total.num_qubits = std::max(total.num_qubits, v + 1); - } - // Lookback counting. - if (t.data & TARGET_RECORD_BIT) { - total.max_lookback = std::max(total.max_lookback, v); - } - // Sweep bit counting. - if (t.data & TARGET_SWEEP_BIT) { - total.num_sweep_bits = std::max(total.num_sweep_bits, v + 1); - } - } - - // Measurement counting. - total.num_measurements += op.count_measurement_results(); - - switch (op.gate_type) { - case GateType::DETECTOR: - // Detector counting. - total.num_detectors += total.num_detectors < UINT64_MAX; - break; - case GateType::OBSERVABLE_INCLUDE: - // Observable counting. - total.num_observables = std::max(total.num_observables, (uint64_t)op.args[0] + 1); - break; - case GateType::TICK: - // Tick counting. - total.num_ticks++; - break; - default: - break; - } + op.add_stats_to(total, this); } - return total; } diff --git a/src/stim/circuit/circuit.h b/src/stim/circuit/circuit.h index fc39c15ff..9f14f7b39 100644 --- a/src/stim/circuit/circuit.h +++ b/src/stim/circuit/circuit.h @@ -38,17 +38,6 @@ namespace stim { uint64_t add_saturate(uint64_t a, uint64_t b); uint64_t mul_saturate(uint64_t a, uint64_t b); -/// Stores a variety of circuit quantities relevant for sizing memory. -struct CircuitStats { - uint64_t num_detectors = 0; - uint64_t num_observables = 0; - uint64_t num_measurements = 0; - uint32_t num_qubits = 0; - uint32_t num_ticks = 0; - uint32_t max_lookback = 0; - uint32_t num_sweep_bits = 0; -}; - /// A description of a quantum computation. struct Circuit { /// Backing data stores for variable-sized target data referenced by operations. diff --git a/src/stim/circuit/circuit_instruction.cc b/src/stim/circuit/circuit_instruction.cc index e45ffe5a8..321d7bdc4 100644 --- a/src/stim/circuit/circuit_instruction.cc +++ b/src/stim/circuit/circuit_instruction.cc @@ -36,6 +36,67 @@ Circuit &CircuitInstruction::repeat_block_body(Circuit &host) const { return host.blocks[b]; } +CircuitStats CircuitInstruction::compute_stats(const Circuit *host) const { + CircuitStats out; + add_stats_to(out, host); + return out; +} + +void CircuitInstruction::add_stats_to(CircuitStats &out, const Circuit *host) const { + if (gate_type == REPEAT) { + if (host == nullptr) { + throw std::invalid_argument("gate_type == REPEAT && host == nullptr"); + } + // Recurse into blocks. + auto sub = repeat_block_body(*host).compute_stats(); + auto reps = repeat_block_rep_count(); + out.num_observables = std::max(out.num_observables, sub.num_observables); + out.num_qubits = std::max(out.num_qubits, sub.num_qubits); + out.max_lookback = std::max(out.max_lookback, sub.max_lookback); + out.num_sweep_bits = std::max(out.num_sweep_bits, sub.num_sweep_bits); + out.num_detectors = add_saturate(out.num_detectors, mul_saturate(sub.num_detectors, reps)); + out.num_measurements = add_saturate(out.num_measurements, mul_saturate(sub.num_measurements, reps)); + out.num_ticks = add_saturate(out.num_ticks, mul_saturate(sub.num_ticks, reps)); + return; + } + + for (auto t : targets) { + auto v = t.data & TARGET_VALUE_MASK; + // Qubit counting. + if (!(t.data & (TARGET_RECORD_BIT | TARGET_SWEEP_BIT))) { + out.num_qubits = std::max(out.num_qubits, v + 1); + } + // Lookback counting. + if (t.data & TARGET_RECORD_BIT) { + out.max_lookback = std::max(out.max_lookback, v); + } + // Sweep bit counting. + if (t.data & TARGET_SWEEP_BIT) { + out.num_sweep_bits = std::max(out.num_sweep_bits, v + 1); + } + } + + // Measurement counting. + out.num_measurements += count_measurement_results(); + + switch (gate_type) { + case GateType::DETECTOR: + // Detector counting. + out.num_detectors += out.num_detectors < UINT64_MAX; + break; + case GateType::OBSERVABLE_INCLUDE: + // Observable counting. + out.num_observables = std::max(out.num_observables, (uint64_t)args[0] + 1); + break; + case GateType::TICK: + // Tick counting. + out.num_ticks++; + break; + default: + break; + } +} + const Circuit &CircuitInstruction::repeat_block_body(const Circuit &host) const { assert(targets.size() == 3); auto b = targets[0].data; diff --git a/src/stim/circuit/circuit_instruction.h b/src/stim/circuit/circuit_instruction.h index 50aad726d..f32534d9c 100644 --- a/src/stim/circuit/circuit_instruction.h +++ b/src/stim/circuit/circuit_instruction.h @@ -24,6 +24,29 @@ namespace stim { struct Circuit; +/// Stores a variety of circuit quantities relevant for sizing memory. +struct CircuitStats { + uint64_t num_detectors = 0; + uint64_t num_observables = 0; + uint64_t num_measurements = 0; + uint32_t num_qubits = 0; + uint32_t num_ticks = 0; + uint32_t max_lookback = 0; + uint32_t num_sweep_bits = 0; + + inline CircuitStats repeated(uint64_t repetitions) const { + return CircuitStats{ + num_detectors * repetitions, + num_observables, + num_measurements * repetitions, + num_qubits, + (uint32_t)(num_ticks * repetitions), + max_lookback, + num_sweep_bits, + }; + } +}; + /// The data that describes how a gate is being applied to qubits (or other targets). /// /// A gate applied to targets. @@ -49,6 +72,11 @@ struct CircuitInstruction { CircuitInstruction() = delete; CircuitInstruction(GateType gate_type, SpanRef args, SpanRef targets); + /// Computes number of qubits, number of measurements, etc. + CircuitStats compute_stats(const Circuit *host) const; + /// Computes number of qubits, number of measurements, etc and adds them into a target. + void add_stats_to(CircuitStats &out, const Circuit *host) const; + /// Determines if two operations can be combined into one operation (with combined targeting data). /// /// For example, `H 1` then `H 2 1` is equivalent to `H 1 2 1` so those instructions are fusable. diff --git a/src/stim/mem/simd_bit_table.h b/src/stim/mem/simd_bit_table.h index e2ee5a6be..b51ad5260 100644 --- a/src/stim/mem/simd_bit_table.h +++ b/src/stim/mem/simd_bit_table.h @@ -69,6 +69,16 @@ struct simd_bit_table { /// Resizes the table. Doesn't clear to zero. Does nothing if already the target size. void destructive_resize(size_t new_min_bits_major, size_t new_min_bits_minor); + /// Copies the table into another table. + /// + /// It's safe for the other table to have a different size. + /// When the other table has a different size, only the data at locations common to both + /// tables are copied over. + void copy_into_different_size_table(simd_bit_table &other) const; + + /// Resizes the table, keeping any data common to the old and new size and otherwise zeroing data. + void resize(size_t new_min_bits_major, size_t new_min_bits_minor); + /// Equality. bool operator==(const simd_bit_table &other) const; /// Inequality. diff --git a/src/stim/mem/simd_bit_table.inl b/src/stim/mem/simd_bit_table.inl index eb0287650..79c74cf81 100644 --- a/src/stim/mem/simd_bit_table.inl +++ b/src/stim/mem/simd_bit_table.inl @@ -107,6 +107,36 @@ void simd_bit_table::destructive_resize(size_t new_min_bits_major, size_t new data.destructive_resize(num_simd_words_minor * num_simd_words_major * W * W); } +template +void simd_bit_table::copy_into_different_size_table(simd_bit_table &other) const { + size_t ni = num_simd_words_minor; + size_t na = num_simd_words_major; + size_t mi = other.num_simd_words_minor; + size_t ma = other.num_simd_words_major; + size_t num_min_bytes = std::min(ni, mi) * (W / 8); + size_t num_maj = std::min(na, ma) * W; + + if (ni == mi) { + memcpy(other.data.ptr_simd, data.ptr_simd, num_min_bytes * num_maj); + } else { + for (size_t maj = 0; maj < num_maj; maj++) { + memcpy(other[maj].ptr_simd, (*this)[maj].ptr_simd, num_min_bytes); + } + } +} + +template +void simd_bit_table::resize(size_t new_min_bits_major, size_t new_min_bits_minor) { + auto new_num_simd_words_minor = min_bits_to_num_simd_words(new_min_bits_minor); + auto new_num_simd_words_major = min_bits_to_num_simd_words(new_min_bits_major); + if (new_num_simd_words_major == num_simd_words_major && new_num_simd_words_minor == num_simd_words_minor) { + return; + } + auto new_table = simd_bit_table(new_min_bits_major, new_min_bits_minor); + copy_into_different_size_table(new_table); + *this = std::move(new_table); +} + template void simd_bit_table::do_square_transpose() { assert(num_simd_words_minor == num_simd_words_major); @@ -138,6 +168,18 @@ simd_bit_table simd_bit_table::transposed() const { return result; } +template +simd_bits simd_bit_table::read_across_majors_at_minor_index(size_t major_start, size_t major_stop, size_t minor_index) const { + assert(major_stop >= major_start); + assert(major_stop <= num_major_bits_padded()); + assert(minor_index < num_minor_bits_padded()); + simd_bits result(major_stop - major_start); + for (size_t maj = major_start; maj < major_stop; maj++) { + result[maj - major_start] = (*this)[maj][minor_index]; + } + return result; +} + template simd_bit_table simd_bit_table::slice_maj(size_t maj_start_bit, size_t maj_stop_bit) const { simd_bit_table result(maj_stop_bit - maj_start_bit, num_minor_bits_padded()); diff --git a/src/stim/mem/simd_bit_table.test.cc b/src/stim/mem/simd_bit_table.test.cc index 5e6537a12..82d4d9ca4 100644 --- a/src/stim/mem/simd_bit_table.test.cc +++ b/src/stim/mem/simd_bit_table.test.cc @@ -293,7 +293,7 @@ TEST(simd_bit_table, lg) { TEST_EACH_WORD_SIZE_W(simd_bit_table, destructive_resize, { auto rng = INDEPENDENT_TEST_RNG(); - simd_bit_table table = table.random(5, 7, rng); + simd_bit_table table = simd_bit_table::random(5, 7, rng); const uint8_t *prev_pointer = table.data.u8; table.destructive_resize(5, 7); ASSERT_EQ(table.data.u8, prev_pointer); @@ -302,3 +302,107 @@ TEST_EACH_WORD_SIZE_W(simd_bit_table, destructive_resize, { ASSERT_GE(table.num_major_bits_padded(), 1025); ASSERT_GE(table.num_minor_bits_padded(), 7); }) + +TEST_EACH_WORD_SIZE_W(simd_bit_table, read_across_majors_at_minor_index, { + auto rng = INDEPENDENT_TEST_RNG(); + simd_bit_table table = simd_bit_table::random(5, 7, rng); + simd_bits slice = table.read_across_majors_at_minor_index(2, 5, 1); + ASSERT_GE(slice.num_bits_padded(), 4); + ASSERT_EQ(slice[0], table[2][1]); + ASSERT_EQ(slice[1], table[3][1]); + ASSERT_EQ(slice[2], table[4][1]); + ASSERT_EQ(slice[3], false); +}) + +template +bool is_table_overlap_identical(const simd_bit_table &a, const simd_bit_table &b) { + size_t w_min = std::min(a.num_simd_words_minor, b.num_simd_words_minor); + size_t n_maj = std::min(a.num_major_bits_padded(), b.num_major_bits_padded()); + for (size_t k_maj = 0; k_maj < n_maj; k_maj++) { + if (a[k_maj].word_range_ref(0, w_min) != b[k_maj].word_range_ref(0, w_min)) { + return false; + } + } + return true; +} + +template +bool is_table_zero_outside(const simd_bit_table &a, size_t num_major_bits, size_t num_minor_bits) { + size_t num_major_words = min_bits_to_num_simd_words(num_major_bits); + size_t num_minor_words = min_bits_to_num_simd_words(num_minor_bits); + if (a.num_simd_words_minor > num_minor_words) { + for (size_t k = 0; k < a.num_simd_words_major; k++) { + if (a[k].word_range_ref(num_minor_words, a.num_simd_words_minor - num_minor_words).not_zero()) { + return false; + } + } + } + for (size_t k = a.num_simd_words_major; k < num_major_words; k++) { + if (a[k].not_zero()) { + return false; + } + } + return true; +} + +TEST_EACH_WORD_SIZE_W(simd_bit_table, copy_into_different_size_table, { + auto rng = INDEPENDENT_TEST_RNG(); + + auto check_size = [&](size_t w1, size_t h1, size_t w2, size_t h2) { + simd_bit_table src = simd_bit_table::random(w1, h1, rng); + simd_bit_table dst = simd_bit_table::random(w1, h1, rng); + src.copy_into_different_size_table(dst); + return is_table_overlap_identical(src, dst); + }; + + EXPECT_TRUE(check_size(0, 0, 0, 0)); + + EXPECT_TRUE(check_size(64, 0, 0, 0)); + EXPECT_TRUE(check_size(0, 64, 0, 0)); + EXPECT_TRUE(check_size(0, 0, 64, 0)); + EXPECT_TRUE(check_size(0, 0, 0, 64)); + + EXPECT_TRUE(check_size(64, 64, 64, 64)); + EXPECT_TRUE(check_size(512, 64, 64, 64)); + EXPECT_TRUE(check_size(64, 512, 64, 64)); + EXPECT_TRUE(check_size(64, 64, 512, 64)); + EXPECT_TRUE(check_size(64, 64, 64, 512)); + + EXPECT_TRUE(check_size(512, 512, 64, 64)); + EXPECT_TRUE(check_size(512, 64, 512, 64)); + EXPECT_TRUE(check_size(512, 64, 64, 512)); + EXPECT_TRUE(check_size(64, 512, 512, 64)); + EXPECT_TRUE(check_size(64, 512, 64, 512)); + EXPECT_TRUE(check_size(64, 64, 512, 512)); +}) + +TEST_EACH_WORD_SIZE_W(simd_bit_table, resize, { + auto rng = INDEPENDENT_TEST_RNG(); + + auto check_size = [&](size_t w1, size_t h1, size_t w2, size_t h2) { + simd_bit_table src = simd_bit_table::random(w1, h1, rng); + simd_bit_table dst = src; + dst.resize(w2, h2); + return is_table_overlap_identical(src, dst) && is_table_zero_outside(dst, std::min(w1, w2), std::min(h1, h2)); + }; + + EXPECT_TRUE(check_size(0, 0, 0, 0)); + + EXPECT_TRUE(check_size(64, 0, 0, 0)); + EXPECT_TRUE(check_size(0, 64, 0, 0)); + EXPECT_TRUE(check_size(0, 0, 64, 0)); + EXPECT_TRUE(check_size(0, 0, 0, 64)); + + EXPECT_TRUE(check_size(64, 64, 64, 64)); + EXPECT_TRUE(check_size(512, 64, 64, 64)); + EXPECT_TRUE(check_size(64, 512, 64, 64)); + EXPECT_TRUE(check_size(64, 64, 512, 64)); + EXPECT_TRUE(check_size(64, 64, 64, 512)); + + EXPECT_TRUE(check_size(512, 512, 64, 64)); + EXPECT_TRUE(check_size(512, 64, 512, 64)); + EXPECT_TRUE(check_size(512, 64, 64, 512)); + EXPECT_TRUE(check_size(64, 512, 512, 64)); + EXPECT_TRUE(check_size(64, 512, 64, 512)); + EXPECT_TRUE(check_size(64, 64, 512, 512)); +}) diff --git a/src/stim/py/compiled_detector_sampler.pybind.cc b/src/stim/py/compiled_detector_sampler.pybind.cc index 5b5480270..e9bb59ece 100644 --- a/src/stim/py/compiled_detector_sampler.pybind.cc +++ b/src/stim/py/compiled_detector_sampler.pybind.cc @@ -39,7 +39,8 @@ pybind11::object CompiledDetectorSampler::sample_to_numpy( } frame_sim.configure_for(circuit_stats, FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_shots); - frame_sim.reset_all_and_run(circuit); + frame_sim.reset_all(); + frame_sim.do_circuit(circuit); const auto &det_data = frame_sim.det_record.storage; const auto &obs_data = frame_sim.obs_record; diff --git a/src/stim/py/stim.pybind.cc b/src/stim/py/stim.pybind.cc index 40eb06917..4a6828450 100644 --- a/src/stim/py/stim.pybind.cc +++ b/src/stim/py/stim.pybind.cc @@ -32,6 +32,7 @@ #include "stim/py/compiled_measurement_sampler.pybind.h" #include "stim/py/march.pybind.h" #include "stim/simulators/dem_sampler.pybind.h" +#include "stim/simulators/frame_simulator.pybind.h" #include "stim/simulators/matched_error.pybind.h" #include "stim/simulators/measurements_to_detection_events.pybind.h" #include "stim/simulators/tableau_simulator.pybind.h" @@ -432,6 +433,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) { auto c_detector_error_model = pybind_detector_error_model(m); auto c_tableau_simulator = pybind_tableau_simulator(m); + auto c_frame_simulator = pybind_frame_simulator(m); auto c_circuit_error_location_stack_frame = pybind_circuit_error_location_stack_frame(m); auto c_gate_target_with_coords = pybind_gate_target_with_coords(m); @@ -470,6 +472,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) { pybind_compiled_measurements_to_detection_events_converter_methods(m, c_compiled_m2d_converter); pybind_tableau_simulator_methods(m, c_tableau_simulator); + pybind_frame_simulator_methods(m, c_frame_simulator); pybind_circuit_error_location_stack_frame_methods(m, c_circuit_error_location_stack_frame); pybind_gate_target_with_coords_methods(m, c_gate_target_with_coords); diff --git a/src/stim/simulators/frame_simulator.h b/src/stim/simulators/frame_simulator.h index 64b7d1304..cdcb650f1 100644 --- a/src/stim/simulators/frame_simulator.h +++ b/src/stim/simulators/frame_simulator.h @@ -27,10 +27,11 @@ namespace stim { enum FrameSimulatorMode { - STORE_MEASUREMENTS_TO_MEMORY, - STREAM_MEASUREMENTS_TO_DISK, - STORE_DETECTIONS_TO_MEMORY, - STREAM_DETECTIONS_TO_DISK, + STORE_MEASUREMENTS_TO_MEMORY, // all measurements stored, detections not stored + STREAM_MEASUREMENTS_TO_DISK, // measurements stored up to lookback, detections not stored + STORE_DETECTIONS_TO_MEMORY, // measurements stored up to lookback, all detections stored + STREAM_DETECTIONS_TO_DISK, // measurements stored up to lookback, detections stored until write + STORE_EVERYTHING_TO_MEMORY, // all measurements stored and all detections stored }; /// A Pauli Frame simulator that computes many samples simultaneously. @@ -42,8 +43,9 @@ enum FrameSimulatorMode { /// The template parameter, W, represents the SIMD width template struct FrameSimulator { - size_t num_qubits; // Number of qubits being tracked. - bool keeping_detection_data; + size_t num_qubits; // Number of qubits being tracked. + size_t num_observables; // Number of observables being tracked. + bool keeping_detection_data; // Whether or not to store dets and obs data. size_t batch_size; // Number of instances being tracked. simd_bit_table x_table; // x_table[q][k] is whether or not there's an X error on qubit q in instance k. simd_bit_table z_table; // z_table[q][k] is whether or not there's a Z error on qubit q in instance k. @@ -77,8 +79,12 @@ struct FrameSimulator { PauliString get_frame(size_t sample_index) const; void set_frame(size_t sample_index, const PauliStringRef &new_frame); void configure_for(CircuitStats new_circuit_stats, FrameSimulatorMode new_mode, size_t new_batch_size); + void ensure_safe_to_do_circuit_with_stats(const CircuitStats &stats); - void reset_all_and_run(const Circuit &circuit); + void safe_do_instruction(const CircuitInstruction &instruction); + void safe_do_circuit(const Circuit &circuit, uint64_t repetititions = 1); + + void do_circuit(const Circuit &circuit); void reset_all(); void do_gate(const CircuitInstruction &inst); diff --git a/src/stim/simulators/frame_simulator.inl b/src/stim/simulators/frame_simulator.inl index 67ac9f998..1b5c6ebad 100644 --- a/src/stim/simulators/frame_simulator.inl +++ b/src/stim/simulators/frame_simulator.inl @@ -41,6 +41,7 @@ template FrameSimulator::FrameSimulator( CircuitStats circuit_stats, FrameSimulatorMode mode, size_t batch_size, std::mt19937_64 &&rng) : num_qubits(0), + num_observables(0), keeping_detection_data(false), batch_size(0), x_table(0, 0), @@ -58,18 +59,73 @@ FrameSimulator::FrameSimulator( template void FrameSimulator::configure_for(CircuitStats new_circuit_stats, FrameSimulatorMode new_mode, size_t new_batch_size) { + bool storing_all_measurements = new_mode == STORE_MEASUREMENTS_TO_MEMORY || new_mode == STORE_EVERYTHING_TO_MEMORY; + bool storing_all_detections = new_mode == STORE_DETECTIONS_TO_MEMORY || new_mode == STORE_EVERYTHING_TO_MEMORY; + bool storing_any_detections = new_mode == STORE_DETECTIONS_TO_MEMORY || new_mode == STORE_EVERYTHING_TO_MEMORY || new_mode == STREAM_DETECTIONS_TO_DISK; + batch_size = new_batch_size; num_qubits = new_circuit_stats.num_qubits; - keeping_detection_data = new_mode == STREAM_DETECTIONS_TO_DISK || new_mode == STORE_DETECTIONS_TO_MEMORY; + keeping_detection_data = storing_any_detections; x_table.destructive_resize(new_circuit_stats.num_qubits, batch_size); z_table.destructive_resize(new_circuit_stats.num_qubits, batch_size); - m_record.destructive_resize(batch_size, new_mode == STORE_MEASUREMENTS_TO_MEMORY ? new_circuit_stats.num_measurements : new_circuit_stats.max_lookback); - det_record.destructive_resize(batch_size, new_mode == STORE_DETECTIONS_TO_MEMORY ? new_circuit_stats.num_detectors : new_mode == STREAM_DETECTIONS_TO_DISK ? 1 : 0), - obs_record.destructive_resize(new_mode == STORE_DETECTIONS_TO_MEMORY || new_mode == STREAM_DETECTIONS_TO_DISK ? new_circuit_stats.num_observables : 0, batch_size); rng_buffer.destructive_resize(batch_size); tmp_storage.destructive_resize(batch_size); last_correlated_error_occurred.destructive_resize(batch_size); sweep_table.destructive_resize(0, batch_size); + + uint64_t num_stored_measurements = new_circuit_stats.max_lookback; + if (storing_all_measurements) { + num_stored_measurements = std::max(new_circuit_stats.num_measurements, num_stored_measurements); + } + m_record.destructive_resize(batch_size, num_stored_measurements); + + num_observables = storing_any_detections ? new_circuit_stats.num_observables : 0; + det_record.destructive_resize(batch_size, storing_all_detections ? new_circuit_stats.num_detectors : storing_any_detections ? 1 : 0), + obs_record.destructive_resize(num_observables, batch_size); +} + +template +void FrameSimulator::ensure_safe_to_do_circuit_with_stats(const CircuitStats &stats) { + if (x_table.num_major_bits_padded() < stats.num_qubits) { + x_table.resize(stats.num_qubits * 2, batch_size); + z_table.resize(stats.num_qubits * 2, batch_size); + } + while (num_qubits < stats.num_qubits) { + if (guarantee_anticommutation_via_frame_randomization) { + z_table[num_qubits].randomize(batch_size, rng); + } + num_qubits += 1; + } + + size_t num_used_measurements = m_record.stored + stats.num_measurements; + if (m_record.storage.num_major_bits_padded() < num_used_measurements) { + m_record.storage.resize(num_used_measurements * 2, batch_size); + } + + if (keeping_detection_data) { + size_t num_detectors = det_record.stored + stats.num_detectors; + if (det_record.storage.num_major_bits_padded() < num_detectors) { + det_record.storage.resize(num_detectors * 2, batch_size); + } + if (obs_record.num_major_bits_padded() < stats.num_observables) { + obs_record.resize(stats.num_observables * 2, batch_size); + } + num_observables = std::max(stats.num_observables, num_observables); + } +} + +template +void FrameSimulator::safe_do_circuit(const Circuit &circuit, uint64_t repetitions) { + ensure_safe_to_do_circuit_with_stats(circuit.compute_stats().repeated(repetitions)); + for (size_t rep = 0; rep < repetitions; rep++) { + do_circuit(circuit); + } +} + +template +void FrameSimulator::safe_do_instruction(const CircuitInstruction &instruction) { + ensure_safe_to_do_circuit_with_stats(instruction.compute_stats(nullptr)); + do_gate(instruction); } template @@ -99,8 +155,7 @@ void FrameSimulator::reset_all() { } template -void FrameSimulator::reset_all_and_run(const Circuit &circuit) { - reset_all(); +void FrameSimulator::do_circuit(const Circuit &circuit) { circuit.for_each_operation([&](const CircuitInstruction &op) { do_gate(op); }); diff --git a/src/stim/simulators/frame_simulator.perf.cc b/src/stim/simulators/frame_simulator.perf.cc index fde548c81..b5b1f13e6 100644 --- a/src/stim/simulators/frame_simulator.perf.cc +++ b/src/stim/simulators/frame_simulator.perf.cc @@ -112,7 +112,8 @@ BENCHMARK(FrameSimulator_surface_code_rotated_memory_z_d11_r100_batch1024) { circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, 1024, std::mt19937_64(0)); benchmark_go([&]() { - sim.reset_all_and_run(circuit); + sim.reset_all(); + sim.do_circuit(circuit); }) .goal_millis(5.1) .show_rate("Shots", 1024) diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc new file mode 100644 index 000000000..a38deb06c --- /dev/null +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -0,0 +1,757 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stim/simulators/frame_simulator.pybind.h" + +#include "stim/py/base.pybind.h" +#include "stim/py/numpy.pybind.h" +#include "stim/simulators/frame_simulator.h" +#include "stim/circuit/circuit_instruction.pybind.h" +#include "stim/circuit/circuit_repeat_block.pybind.h" + +using namespace stim; +using namespace stim_pybind; + +pybind11::class_> stim_pybind::pybind_frame_simulator(pybind11::module &m) { + return pybind11::class_>( + m, + "FlipSimulator", + clean_doc_string(R"DOC( + A simulator that tracks whether things are flipped, instead of what they are. + + Tracking flips is significantly cheaper than tracking actual values, requiring + O(1) work per gate (compared to O(n) for unitary operations and O(n^2) for + collapsing operations in the tableau simulator, where n is the qubit count). + + Supports interactive usage, where gates and measurements are applied on demand. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator() + >>> assert False + )DOC") + .data()); +} + +template +pybind11::object peek_current_pauli_errors(const FrameSimulator &self) { + uint8_t *buffer = new uint8_t[self.num_qubits * self.batch_size]; + size_t t = 0; + for (size_t k_maj = 0; k_maj < self.batch_size; k_maj++) { + for (size_t k_min = 0; k_min < self.num_qubits; k_min++) { + uint8_t x = self.x_table[k_maj][k_min]; + uint8_t z = self.z_table[k_maj][k_min]; + buffer[t++] = (x ^ z) + z * 2; + } + } + + pybind11::capsule free_when_done(buffer, [](void *f) { + delete[] reinterpret_cast(f); + }); + + return pybind11::array_t( + {(pybind11::ssize_t)self.num_qubits, (pybind11::ssize_t)self.batch_size}, + {(pybind11::ssize_t)self.batch_size, (pybind11::ssize_t)1}, + buffer, + free_when_done); +} + +template +FrameSimulator create_frame_simulator(size_t batch_size, bool disable_heisenberg_uncertainty, uint32_t num_qubits, const pybind11::object &seed) { + FrameSimulator result( + CircuitStats{ + 0, // num_detectors + 0, // num_observables + 0, // num_measurements + num_qubits, + 0, // num_ticks + (uint32_t)(1 << 24), // max_lookback + 0, // num_sweep_bits + }, + FrameSimulatorMode::STORE_EVERYTHING_TO_MEMORY, + batch_size, + make_py_seeded_rng(seed)); + result.guarantee_anticommutation_via_frame_randomization = !disable_heisenberg_uncertainty; + result.reset_all(); + return result; +} + +template +pybind11::object sliced_table_to_numpy( + const simd_bit_table &table, + size_t num_major_exact, + size_t num_minor_exact, + std::optional major_index, + std::optional minor_index, + bool bit_packed) { + if (major_index.has_value()) { + simd_bits_range_ref row = table[major_index.value()]; + if (minor_index.has_value()) { + bool b = row[minor_index.value()]; + auto np = pybind11::module::import("numpy"); + return np.attr("array")(b, bit_packed ? np.attr("bool_") : np.attr("uint8")); + } else { + return simd_bits_to_numpy(row, num_minor_exact, bit_packed); + } + } else { + if (minor_index.has_value()) { + auto data = table.read_across_majors_at_minor_index(0, num_major_exact, minor_index.value()); + return simd_bits_to_numpy(data, num_major_exact, bit_packed); + } else { + return simd_bit_table_to_numpy(table, num_major_exact, num_minor_exact, bit_packed); + } + } +} + +std::optional py_index_to_optional_size_t(pybind11::object index, size_t length, const char *val_name, const char *len_name) { + std::optional instance_index; + if (index.is_none()) { + return {}; + } + int64_t i = pybind11::cast(index); + if (i < -(int64_t)length || (uint64_t)i >= length) { + std::stringstream msg; + msg << "not ("; + msg << "-" << len_name << " <= "; + msg << val_name << "=" << index; + msg << " < "; + msg << len_name << "=" << length; + msg << ")"; + throw std::out_of_range(msg.str()); + } + if (i < 0) { + i += length; + } + assert(i >= 0); + return (size_t)i; +} + +template +pybind11::object get_measurement_flips( + FrameSimulator &self, + const pybind11::object &py_record_index, + const pybind11::object &py_instance_index, + bool bit_packed) { + + size_t num_measurements = self.m_record.stored; + + std::optional instance_index = py_index_to_optional_size_t( + py_instance_index, + self.batch_size, + "instance_index", + "batch_size"); + + std::optional record_index = py_index_to_optional_size_t( + py_record_index, + num_measurements, + "record_index", + "num_measurements"); + + return sliced_table_to_numpy( + self.m_record.storage, + num_measurements, + self.batch_size, + record_index, + instance_index, + bit_packed); +} + +template +pybind11::object get_detector_flips( + FrameSimulator &self, + const pybind11::object &py_detector_index, + const pybind11::object &py_instance_index, + bool bit_packed) { + + size_t num_detectors = self.det_record.stored; + + std::optional instance_index = py_index_to_optional_size_t( + py_instance_index, + self.batch_size, + "instance_index", + "batch_size"); + + std::optional detector_index = py_index_to_optional_size_t( + py_detector_index, + num_detectors, + "detector_index", + "num_measurements"); + + return sliced_table_to_numpy( + self.det_record.storage, + num_detectors, + self.batch_size, + detector_index, + instance_index, + bit_packed); +} + +template +pybind11::object get_obs_flips( + FrameSimulator &self, + const pybind11::object &py_observable_index, + const pybind11::object &py_instance_index, + bool bit_packed) { + + std::optional instance_index = py_index_to_optional_size_t( + py_instance_index, + self.batch_size, + "instance_index", + "batch_size"); + + std::optional observable_index = py_index_to_optional_size_t( + py_observable_index, + self.num_observables, + "observable_index", + "num_observables"); + + return sliced_table_to_numpy( + self.obs_record, + self.num_observables, + self.batch_size, + observable_index, + instance_index, + bit_packed); +} + +template +pybind11::object get_xz_flips( + FrameSimulator &self, + bool x, + bool z, + const pybind11::object &py_observable_index, + const pybind11::object &py_instance_index, + bool bit_packed) { + + std::optional instance_index = py_index_to_optional_size_t( + py_instance_index, + self.batch_size, + "instance_index", + "batch_size"); + + std::optional qubit_index = py_index_to_optional_size_t( + py_observable_index, + self.num_observables, + "qubit_index", + "num_qubits"); + + simd_bit_table *table; + if (x & z) { + self.x_table.data ^= self.z_table.data; + table = &self.x_table; + } else if (x) { + table = &self.x_table; + } else { + assert(z); + table = &self.z_table; + } + auto result = sliced_table_to_numpy( + *table, + self.num_qubits, + self.batch_size, + qubit_index, + instance_index, + bit_packed); + if (x & z) { + self.x_table.data ^= self.z_table.data; + } + return result; +} + +//template +//PyCircuitInstruction build_single_qubit_gate_instruction_ensure_size( +// TableauSimulator &self, GateType gate_type, const pybind11::args &args, SpanRef gate_args = {}) { +// std::vector targets; +// uint32_t max_q = 0; +// try { +// for (const auto &e : args) { +// if (pybind11::isinstance(e)) { +// targets.push_back(pybind11::cast(e)); +// } else { +// uint32_t q = e.cast(); +// max_q = std::max(max_q, q & TARGET_VALUE_MASK); +// targets.push_back(GateTarget{q}); +// } +// } +// } catch (const pybind11::cast_error &) { +// throw std::out_of_range("Target qubits must be non-negative integers."); +// } +// +// std::vector gate_args_vec; +// for (const auto &e : gate_args) { +// gate_args_vec.push_back(e); +// } +// +// // Note: quadratic behavior. +// self.ensure_large_enough_for_qubits(max_q + 1); +// +// return PyCircuitInstruction(gate_type, targets, gate_args_vec); +//} +// +void stim_pybind::pybind_frame_simulator_methods( + pybind11::module &m, pybind11::class_> &c) { + c.def( + pybind11::init(&create_frame_simulator), + pybind11::kw_only(), + pybind11::arg("batch_size"), + pybind11::arg("disable_stabilizer_randomization") = false, + pybind11::arg("num_qubits") = 0, + pybind11::arg("seed") = pybind11::none(), + clean_doc_string(R"DOC( + @signature def __init__(self, *, batch_size: int, disable_stabilizer_randomization: bool = False, num_qubits: int = 0, seed: Optional[int] = None) -> None: + Initializes a stim.FlipSimulator. + + Args: + batch_size: For speed, the flip simulator simulates many instances in + parallel. This argument determines the number of parallel instances. + + It's recommended to use a multiple of 256, because internally the state + of the instances is striped across SSE (128 bit) or AVX (256 bit) + words with one bit in the word belonging to each instance. The result is + that, even if you only ask for 1 instance, probably the same amount of + work is being done as if you'd asked for 256 instances. The extra + results just aren't being used, creating waste. + + disable_stabilizer_randomization: Determines whether or not the flip + simulator uses stabilizer randomization. Defaults to False (stabilizer + randomization used). Set to True to disable stabilizer randomization. + + Stabilizer randomization means that, when a qubit is initialized or + measured in the Z basis, a Z error is added to the qubit with 50% + probability. More generally, anytime a stabilizer is introduced into + the system by any means, an error equal to that stabilizer is applied + with 50% probability. This ensures that observables anticommuting with + stabilizers of the system must be maximally uncertain. In other words, + this feature enforces Heisenberg's uncertainty principle. + + This is a safety feature that you should not turn off unless you have a + reason to do so. Stabilizer randomization is turned on by default + because it catches mistakes. For example, suppose you are trying to + create a stabilizer code but you accidentally have the code measure two + anticommuting stabilizers. With stabilizer randomization turned off, it + will look like this code works. With stabilizer randomization turned on, + the two measurements will correctly randomize each other revealing that + the code doesn't work. + + In some use cases, stabilizer randomization is a hindrance instead of + helpful. For example, if you are using the flip simulator to understand + how an error propagates through the system, the stabilizer randomization + will be introducing error terms that you don't want. + + num_qubits: Sets the initial number of qubits tracked by the simulation. + The simulator will still automatically resize as needed when qubits + beyond this limit are touched. + + This parameter exists as a way to hint at the desired size of the + simulator's state for performance, and to ensure methods that + peek at the size have the expected size from the start instead of + only after the relevant qubits have been touched. + + seed: PARTIALLY determines simulation results by deterministically seeding + the random number generator. + + Must be None or an integer in range(2**64). + + Defaults to None. When None, the prng is seeded from system entropy. + + When set to an integer, making the exact same series calls on the exact + same machine with the exact same version of Stim will produce the exact + same simulation results. + + CAUTION: simulation results *WILL NOT* be consistent between versions of + Stim. This restriction is present to make it possible to have future + optimizations to the random sampling, and is enforced by introducing + intentional differences in the seeding strategy from version to version. + + CAUTION: simulation results *MAY NOT* be consistent across machines that + differ in the width of supported SIMD instructions. For example, using + the same seed on a machine that supports AVX instructions and one that + only supports SSE instructions may produce different simulation results. + + CAUTION: simulation results *MAY NOT* be consistent if you vary how the + circuit is executed. For example, reordering whether a reset on one + qubit happens before or after a reset on another qubit can result in + different measurement results being observed starting from the same + seed. + + Returns: + An initialized stim.FlipSimulator. + + Examples: + >>> import stim + >>> s = stim.FlipSimulator(seed=0) + >>> s2 = stim.FlipSimulator(seed=0) + >>> s.x_error(0, p=0.1) + >>> s2.x_error(0, p=0.1) + >>> s.measure(0) == s2.measure(0) + True + )DOC") + .data()); + + c.def_property_readonly( + "batch_size", + [](FrameSimulator &self) -> size_t { + return self.batch_size; + }, + clean_doc_string(R"DOC( + Returns the number of instances being simulated by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.batch_size + 256 + >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim.batch_size + 42 + )DOC") + .data()); + + c.def_property_readonly( + "num_qubits", + [](FrameSimulator &self) -> size_t { + return self.num_qubits; + }, + clean_doc_string(R"DOC( + Returns the number of qubits currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_qubits + 0 + >>> sim.h(5) + >>> sim.num_qubits + 6 + )DOC") + .data()); + + c.def_property_readonly( + "num_observables", + [](FrameSimulator &self) -> size_t { + return self.num_observables; + }, + clean_doc_string(R"DOC( + Returns the number of observables currently tracked by the simulator. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_observables + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 + ... OBSERVABLE_INCLUDE(4) rec[-1] + ... ''')) + >>> sim.num_observables + 5 + )DOC") + .data()); + + c.def_property_readonly( + "num_measurements", + [](FrameSimulator &self) -> size_t { + return self.m_record.stored; + }, + clean_doc_string(R"DOC( + Returns the number of measurements that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_measurements + 0 + >>> sim.measure(5) + >>> sim.num_measurements + 1 + )DOC") + .data()); + + c.def_property_readonly( + "num_detectors", + [](FrameSimulator &self) -> size_t { + return self.det_record.stored; + }, + clean_doc_string(R"DOC( + Returns the number of detectors that have been simulated and stored. + + Examples: + >>> import stim + >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim.num_detectors + 0 + >>> sim.do_circuit(stim.Circuit(''' + ... M 0 0 + ... DETECTOR rec[-1] rec[-2] + ... ''')) + >>> sim.num_detectors + 1 + )DOC") + .data()); + + c.def( + "peek_current_pauli_errors", + &peek_current_pauli_errors, + clean_doc_string(R"DOC( + @signature def peek_current_errors(self) -> np.ndarray: + Creates a numpy array describing the current pauli errors. + + Returns: + A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + + Each entry in the array is the Pauli error on one qubit in one instance, + using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 + then there's a Y error on the qubit with index 5 in the shot with index 3. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "get_measurement_flips", + &get_measurement_flips, + pybind11::kw_only(), + pybind11::arg("record_index") = pybind11::none(), + pybind11::arg("instance_index") = pybind11::none(), + pybind11::arg("bit_packed") = false, + clean_doc_string(R"DOC( + @signature def get_measurement_flips(self, *, record_index: Optional[int] = None, instance_index: Optional[int] = None, bit_packed: bool = False) -> np.ndarray: + Retrieves measurement flip data from the simulator's measurement record. + + Args: + record_index: Identifies a measurement to read results from. + Setting this to None (default) returns results from all measurements. + Setting this to a non-negative integer indexes measurements by the order + they occurred. For example, record index 0 is the first measurement. + Setting this to a negative integer indexes measurements by recency. + For example, recording index -1 is the most recent measurement. + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be set to an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_measurements, self.batch_size), where the first index is + the measurement_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying record_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a measurement_index (or a 0d array, a boolean, if record_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "get_detector_flips", + &get_detector_flips, + pybind11::kw_only(), + pybind11::arg("detector_index") = pybind11::none(), + pybind11::arg("instance_index") = pybind11::none(), + pybind11::arg("bit_packed") = false, + clean_doc_string(R"DOC( + @signature def get_detector_flips(self, *, detector_index: Optional[int] = None, instance_index: Optional[int] = None, bit_packed: bool = False) -> np.ndarray: + Retrieves detector flip data from the simulator's detection event record. + + Args: + record_index: Identifies a detector to read results from. + Setting this to None (default) returns results from all detectors. + Otherwise this should be an integer in range(0, self.num_detectors). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_detectors, self.batch_size), where the first index is + the detector_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying detector_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a detector_index (or a 0d array, a boolean, if detector_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "get_observable_flips", + &get_obs_flips, + pybind11::kw_only(), + pybind11::arg("observable_index") = pybind11::none(), + pybind11::arg("instance_index") = pybind11::none(), + pybind11::arg("bit_packed") = false, + clean_doc_string(R"DOC( + @signature def get_observable_flips(self, *, observable_index: Optional[int] = None, instance_index: Optional[int] = None, bit_packed: bool = False) -> np.ndarray: + Retrieves observable flip data from the simulator's detection event record. + + Args: + record_index: Identifies a observable to read results from. + Setting this to None (default) returns results from all observables. + Otherwise this should be an integer in range(0, self.num_observables). + instance_index: Identifies a simulation instance to read results from. + Setting this to None (the default) returns results from all instances. + Otherwise this should be an integer in range(0, self.batch_size). + bit_packed: Defaults to False. Determines whether the result is bit packed. + If this is set to true, the returned numpy array will be bit packed as + if by applying + + out = np.packbits(out, axis=len(out.shape) - 1, bitorder='little') + + Behind the scenes the data is always bit packed, so setting this + argument avoids ever unpacking in the first place. This substantially + improves performance when there is a lot of data. + + Returns: + A numpy array containing the requested data. By default this is a 2d array + of shape (self.num_observables, self.batch_size), where the first index is + the observable_index and the second index is the instance_index and the + dtype is np.bool_. + + Specifying observable_index slices away the first index, leaving a 1d array + with only an instance_index. + + Specifying instance_index slices away the last index, leaving a 1d array + with only a observable_index (or a 0d array, a boolean, if observable_index + was also specified). + + Specifying bit_packed=True bit packs the last remaining index, changing + the dtype to np.uint8. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "do_circuit", + [](FrameSimulator &self, const Circuit &circuit) { + self.safe_do_circuit(circuit); + }, + pybind11::arg("circuit"), + clean_doc_string(R"DOC( + Applies a all the instructions in a circuit to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "do_instruction", + [](FrameSimulator &self, const pybind11::object &instruction) { + if (pybind11::isinstance(instruction)) { + self.safe_do_instruction(pybind11::cast(instruction).as_operation_ref()); + } else if (pybind11::isinstance(instruction)) { + const CircuitRepeatBlock &block = pybind11::cast(instruction); + self.safe_do_circuit(block.body, block.repeat_count); + } else { + throw std::invalid_argument("Not a stim.CircuitInstruction or stim.CircuitRepeatBlock '" + pybind11::cast(pybind11::repr(instruction)) + "'."); + } + }, + pybind11::arg("instruction"), + clean_doc_string(R"DOC( + @signature def do_instruction(self, instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock]) -> None: + Applies a circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + circuit: The circuit to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); + + c.def( + "do", + [](FrameSimulator &self, const pybind11::object &obj) { + if (pybind11::isinstance(obj)) { + self.safe_do_circuit(pybind11::cast(obj)); + } else if (pybind11::isinstance(obj)) { + const CircuitRepeatBlock &block = pybind11::cast(obj); + self.safe_do_circuit(block.body, block.repeat_count); + } else if (pybind11::isinstance(obj)) { + const CircuitRepeatBlock &block = pybind11::cast(obj); + self.safe_do_circuit(block.body, block.repeat_count); + } else { + throw std::invalid_argument("Don't know how to do a '" + pybind11::cast(pybind11::repr(obj)) + "'."); + } + }, + pybind11::arg("obj"), + clean_doc_string(R"DOC( + @signature def do(self, obj: Union[stim.Circuit, stim.CircuitInstruction, stim.CircuitRepeatBlock]) -> None: + Applies a circuit or circuit instruction to the simulator's state. + + The results of any measurements performed can be retrieved using the + `get_measurement_flips` method. + + Args: + obj: The circuit or instruction to apply to the simulator's state. + + Examples: + >>> import stim + >>> assert False + )DOC") + .data()); +} diff --git a/src/stim/simulators/frame_simulator.pybind.h b/src/stim/simulators/frame_simulator.pybind.h new file mode 100644 index 000000000..bb38a44e5 --- /dev/null +++ b/src/stim/simulators/frame_simulator.pybind.h @@ -0,0 +1,17 @@ +#ifndef _STIM_SIMULATORS_FRAME_SIMULATOR_PYBIND_H +#define _STIM_SIMULATORS_FRAME_SIMULATOR_PYBIND_H + +#include + +#include "stim/simulators/frame_simulator.h" + +namespace stim_pybind { + +pybind11::class_> pybind_frame_simulator(pybind11::module &m); + +void pybind_frame_simulator_methods( + pybind11::module &m, pybind11::class_> &c); + +} // namespace stim_pybind + +#endif diff --git a/src/stim/simulators/frame_simulator.test.cc b/src/stim/simulators/frame_simulator.test.cc index 9b2e04fb1..4e0cedefb 100644 --- a/src/stim/simulators/frame_simulator.test.cc +++ b/src/stim/simulators/frame_simulator.test.cc @@ -1476,7 +1476,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, reconfigure_for, { FrameSimulator frame_sim( circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 0, INDEPENDENT_TEST_RNG()); frame_sim.configure_for(circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 256); - frame_sim.reset_all_and_run(circuit); + frame_sim.reset_all(); + frame_sim.do_circuit(circuit); ASSERT_EQ(frame_sim.det_record.storage[0].popcnt(), 256); }) @@ -1560,7 +1561,8 @@ TEST_EACH_WORD_SIZE_W(FrameSimulator, heralded_erase_detect_statistics, { FrameSimulator sim( circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, 1024, INDEPENDENT_TEST_RNG()); for (n = 0; n < 1024 * 256; n += 1024) { - sim.reset_all_and_run(circuit); + sim.reset_all(); + sim.do_circuit(circuit); auto sample = sim.det_record.storage.transposed(); for (size_t k = 0; k < 1024; k++) { bins[sample[k].u8[0]]++; diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py new file mode 100644 index 000000000..565a30d1f --- /dev/null +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -0,0 +1,114 @@ +import stim +import numpy as np + + +def test_get_measurement_flips(): + s = stim.FlipSimulator(batch_size=11) + assert s.num_measurements == 0 + assert s.num_qubits == 0 + assert s.batch_size == 11 + + s.do_circuit(stim.Circuit(""" + X_ERROR(1) 100 + M 100 + """)) + assert s.num_measurements == 1 + assert s.num_qubits == 101 + assert s.batch_size == 11 + + m = s.get_measurement_flips(record_index=0) + np.testing.assert_array_equal(m, [True] * 11) + assert s.num_measurements == 1 + assert s.num_qubits == 101 + assert s.batch_size == 11 + + +def test_stabilizer_randomization(): + s = stim.FlipSimulator( + batch_size=256, + num_qubits=10, + disable_stabilizer_randomization=True, + ) + np.testing.assert_array_equal( + s.peek_current_pauli_errors(), + np.zeros(shape=(10, 256), dtype=np.uint8), + ) + s.do_circuit(stim.Circuit("R 19")) + np.testing.assert_array_equal( + s.peek_current_pauli_errors(), + np.zeros(shape=(20, 256), dtype=np.uint8), + ) + + s = stim.FlipSimulator( + batch_size=256, + num_qubits=10, + disable_stabilizer_randomization=False, + ) + v = s.peek_current_pauli_errors() + assert v.shape == (10, 256) + assert np.all((v == 0) | (v == 3)) + assert 0.2 < np.count_nonzero(v == 3) / (256 * 10) < 0.8 + s.do_circuit(stim.Circuit("R 19")) + v = s.peek_current_pauli_errors() + assert v.shape == (20, 256) + assert np.all((v == 0) | (v == 3)) + assert 0.2 < np.count_nonzero(v == 3) / (256 * 20) < 0.8 + + +def test_get_detector_flips(): + s = stim.FlipSimulator(batch_size=11) + assert s.num_measurements == 0 + assert s.num_qubits == 0 + assert s.batch_size == 11 + + s.do_circuit(stim.Circuit(""" + X_ERROR(1) 25 + M 24 25 + DETECTOR rec[-1] + DETECTOR rec[-1] + DETECTOR rec[-1] + """)) + assert s.num_measurements == 2 + assert s.num_detectors == 3 + assert s.num_qubits == 26 + assert s.batch_size == 11 + np.testing.assert_array_equal( + s.get_detector_flips(), + np.ones(shape=(3, 11), dtype=np.bool_)) + np.testing.assert_array_equal( + s.get_detector_flips(detector_index=1), + np.ones(shape=(11,), dtype=np.bool_)) + np.testing.assert_array_equal( + s.get_detector_flips(instance_index=1), + np.ones(shape=(3,), dtype=np.bool_)) + assert s.get_detector_flips(detector_index=1, instance_index=1) + + +def test_get_observable_flips(): + s = stim.FlipSimulator(batch_size=11) + assert s.num_measurements == 0 + assert s.num_qubits == 0 + assert s.batch_size == 11 + assert s.num_observables == 0 + + s.do_circuit(stim.Circuit(""" + X_ERROR(1) 25 + M 24 25 + OBSERVABLE_INCLUDE(2) rec[-1] + """)) + assert s.num_measurements == 2 + assert s.num_detectors == 0 + assert s.num_observables == 3 + assert s.num_qubits == 26 + assert s.batch_size == 11 + np.testing.assert_array_equal( + s.get_observable_flips(observable_index=1), + np.zeros(shape=(11,), dtype=np.bool_)) + np.testing.assert_array_equal( + s.get_observable_flips(observable_index=2), + np.ones(shape=(11,), dtype=np.bool_)) + np.testing.assert_array_equal( + s.get_observable_flips(instance_index=1), + [False, False, True]) + assert not s.get_observable_flips(observable_index=1, instance_index=1) + assert s.get_observable_flips(observable_index=2, instance_index=1) diff --git a/src/stim/simulators/frame_simulator_util.inl b/src/stim/simulators/frame_simulator_util.inl index 3607ceb94..c4f3b89cf 100644 --- a/src/stim/simulators/frame_simulator_util.inl +++ b/src/stim/simulators/frame_simulator_util.inl @@ -23,7 +23,8 @@ template std::pair, simd_bit_table> sample_batch_detection_events( const Circuit &circuit, size_t num_shots, std::mt19937_64 &rng) { FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_DETECTIONS_TO_MEMORY, num_shots, std::move(rng)); - sim.reset_all_and_run(circuit); + sim.reset_all(); + sim.do_circuit(circuit); rng = std::move(sim.rng); // Update input rng as if it was used directly, by moving the updated state out of the simulator. return std::pair, simd_bit_table>{ @@ -126,7 +127,8 @@ void rerun_frame_sim_in_memory_and_write_dets_to_disk( throw std::out_of_range("Can't combine --prepend_observables, --append_observables, or --obs_out"); } - frame_sim.reset_all_and_run(circuit); + frame_sim.reset_all(); + frame_sim.do_circuit(circuit); const auto &obs_data = frame_sim.obs_record; const auto &det_data = frame_sim.det_record.storage; @@ -190,7 +192,8 @@ void rerun_frame_sim_in_memory_and_write_measurements_to_disk( size_t num_shots, FILE *out, SampleFormat format) { - frame_sim.reset_all_and_run(circuit); + frame_sim.reset_all(); + frame_sim.do_circuit(circuit); const auto &measure_data = frame_sim.m_record.storage; write_table_data( @@ -289,7 +292,8 @@ simd_bit_table sample_batch_measurements( std::mt19937_64 &rng, bool transposed) { FrameSimulator sim(circuit.compute_stats(), FrameSimulatorMode::STORE_MEASUREMENTS_TO_MEMORY, num_samples, std::move(rng)); - sim.reset_all_and_run(circuit); + sim.reset_all(); + sim.do_circuit(circuit); simd_bit_table result = std::move(sim.m_record.storage); rng = std::move(sim.rng); // Update input rng as if it was used directly, by moving the updated state out of the simulator. From 8881bd7a70f015509beb8404418870293db48cc2 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 21:32:46 -0700 Subject: [PATCH 6/8] doctest fixes --- doc/python_api_reference_vDev.md | 242 ++++++++----- doc/stim.pyi | 220 ++++++++---- glue/python/src/stim/__init__.pyi | 220 ++++++++---- src/stim/simulators/frame_simulator.h | 2 +- src/stim/simulators/frame_simulator.pybind.cc | 333 ++++++++++-------- .../simulators/frame_simulator_pybind_test.py | 90 ++++- 6 files changed, 744 insertions(+), 363 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index f719ffb5a..9429eb9bb 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -171,8 +171,6 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.FlipSimulator.__init__`](#stim.FlipSimulator.__init__) - [`stim.FlipSimulator.batch_size`](#stim.FlipSimulator.batch_size) - [`stim.FlipSimulator.do`](#stim.FlipSimulator.do) - - [`stim.FlipSimulator.do_circuit`](#stim.FlipSimulator.do_circuit) - - [`stim.FlipSimulator.do_instruction`](#stim.FlipSimulator.do_instruction) - [`stim.FlipSimulator.get_detector_flips`](#stim.FlipSimulator.get_detector_flips) - [`stim.FlipSimulator.get_measurement_flips`](#stim.FlipSimulator.get_measurement_flips) - [`stim.FlipSimulator.get_observable_flips`](#stim.FlipSimulator.get_observable_flips) @@ -180,7 +178,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.FlipSimulator.num_measurements`](#stim.FlipSimulator.num_measurements) - [`stim.FlipSimulator.num_observables`](#stim.FlipSimulator.num_observables) - [`stim.FlipSimulator.num_qubits`](#stim.FlipSimulator.num_qubits) - - [`stim.FlipSimulator.peek_current_pauli_errors`](#stim.FlipSimulator.peek_current_pauli_errors) + - [`stim.FlipSimulator.peek_pauli_flips`](#stim.FlipSimulator.peek_pauli_flips) - [`stim.FlippedMeasurement`](#stim.FlippedMeasurement) - [`stim.FlippedMeasurement.__init__`](#stim.FlippedMeasurement.__init__) - [`stim.FlippedMeasurement.observable`](#stim.FlippedMeasurement.observable) @@ -5759,8 +5757,7 @@ class FlipSimulator: Examples: >>> import stim - >>> s = stim.FlipSimulator() - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=256) """ ``` @@ -5857,12 +5854,7 @@ def __init__( Examples: >>> import stim - >>> s = stim.FlipSimulator(seed=0) - >>> s2 = stim.FlipSimulator(seed=0) - >>> s.x_error(0, p=0.1) - >>> s2.x_error(0, p=0.1) - >>> s.measure(0) == s2.measure(0) - True + >>> sim = stim.FlipSimulator(batch_size=256) """ ``` @@ -5879,10 +5871,10 @@ def batch_size( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.batch_size 256 - >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim = stim.FlipSimulator(batch_size=42) >>> sim.batch_size 42 """ @@ -5907,53 +5899,28 @@ def do( Examples: >>> import stim - >>> assert False - """ -``` - - -```python -# stim.FlipSimulator.do_circuit - -# (in class stim.FlipSimulator) -def do_circuit( - self, - circuit: stim.Circuit, -) -> None: - """Applies a all the instructions in a circuit to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. - - Args: - circuit: The circuit to apply to the simulator's state. - - Examples: - >>> import stim - >>> assert False - """ -``` - - -```python -# stim.FlipSimulator.do_instruction - -# (in class stim.FlipSimulator) -def do_instruction( - self, - instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], -) -> None: - """Applies a circuit instruction to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... disable_stabilizer_randomization=True, + ... ) + >>> circuit = stim.Circuit(''' + ... X_ERROR(1) 0 1 3 + ... REPEAT 5 { + ... H 0 + ... C_XYZ 1 + ... } + ... ''') + >>> sim.do(circuit) + >>> sim.peek_pauli_flips() + [stim.PauliString("+ZZ_X")] - Args: - circuit: The circuit to apply to the simulator's state. + >>> sim.do(circuit[0]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YY__")] - Examples: - >>> import stim - >>> assert False + >>> sim.do(circuit[1]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YX__")] """ ``` @@ -6006,7 +5973,29 @@ def get_detector_flips( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... DETECTOR rec[-2] rec[-3] + ... DETECTOR rec[-1] rec[-2] + ... ''')) + + >>> sim.get_detector_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_detector_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_detector_flips(instance_index=2) + array([False, False]) + + >>> sim.get_detector_flips(detector_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_detector_flips(instance_index=2, detector_index=1) + array(False) """ ``` @@ -6062,7 +6051,27 @@ def get_measurement_flips( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit('M 0 1 2')) + + >>> sim.get_measurement_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_measurement_flips(bit_packed=True) + array([[0, 0], + [0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_measurement_flips(instance_index=1) + array([False, False, False]) + + >>> sim.get_measurement_flips(record_index=2) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_measurement_flips(instance_index=1, record_index=2) + array(False) """ ``` @@ -6115,7 +6124,29 @@ def get_observable_flips( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... OBSERVABLE_INCLUDE(0) rec[-2] + ... OBSERVABLE_INCLUDE(1) rec[-1] + ... ''')) + + >>> sim.get_observable_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_observable_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_observable_flips(instance_index=2) + array([False, False]) + + >>> sim.get_observable_flips(observable_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_observable_flips(instance_index=2, observable_index=1) + array(False) """ ``` @@ -6132,10 +6163,10 @@ def num_detectors( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_detectors 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 0 ... DETECTOR rec[-1] rec[-2] ... ''')) @@ -6157,12 +6188,12 @@ def num_measurements( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_measurements 0 - >>> sim.measure(5) + >>> sim.do(stim.Circuit('M 3 5')) >>> sim.num_measurements - 1 + 2 """ ``` @@ -6179,10 +6210,10 @@ def num_observables( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_observables 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 ... OBSERVABLE_INCLUDE(4) rec[-1] ... ''')) @@ -6204,35 +6235,84 @@ def num_qubits( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_qubits 0 - >>> sim.h(5) + >>> sim = stim.FlipSimulator(batch_size=256, num_qubits=4) + >>> sim.num_qubits + 4 + >>> sim.do(stim.Circuit('H 5')) >>> sim.num_qubits 6 """ ``` - + ```python -# stim.FlipSimulator.peek_current_pauli_errors +# stim.FlipSimulator.peek_pauli_flips # (in class stim.FlipSimulator) -def peek_current_errors( +@overload +def peek_pauli_flips( self, -) -> np.ndarray: - """Creates a numpy array describing the current pauli errors. +) -> List[stim.PauliString]: + pass +@overload +def peek_pauli_flips( + self, + *, + instance_index: int, +) -> stim.PauliString: + pass +def peek_pauli_flips( + self, + *, + instance_index: Optional[int] = None, +) -> Union[stim.PauliString, List[stim.PauliString]]: + """Returns the current pauli errors packed into stim.PauliString instances. - Returns: - A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + Args: + instance_index: Defaults to None. When set to None, the pauli errors from + all instances are returned as a list of `stim.PauliString`. When set to + an integer, a single `stim.PauliString` is returned containing the + errors for the indexed instance. - Each entry in the array is the Pauli error on one qubit in one instance, - using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 - then there's a Y error on the qubit with index 5 in the shot with index 3. + Returns: + if instance_index is None: + A list of stim.PauliString, with the k'th entry being the errors from + the k'th simulation instance. + else: + A stim.PauliString with the errors from the k'th simulation instance. Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... disable_stabilizer_randomization=True, + ... num_qubits=10, + ... ) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+__________"), stim.PauliString("+__________")] + + >>> sim.peek_pauli_flips(instance_index=0) + stim.PauliString("+__________") + + >>> sim.do(stim.Circuit(''' + ... X_ERROR(1) 0 3 5 + ... Z_ERROR(1) 3 6 + ... ''')) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+X__Y_XZ___"), stim.PauliString("+X__Y_XZ___")] + + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... num_qubits=100, + ... ) + >>> flips: stim.PauliString = sim.peek_pauli_flips(instance_index=0) + >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization + ['+', 'Z', '_'] """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index f0865de7e..fa3311f7f 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4328,8 +4328,7 @@ class FlipSimulator: Examples: >>> import stim - >>> s = stim.FlipSimulator() - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=256) """ def __init__( self, @@ -4419,12 +4418,7 @@ class FlipSimulator: Examples: >>> import stim - >>> s = stim.FlipSimulator(seed=0) - >>> s2 = stim.FlipSimulator(seed=0) - >>> s.x_error(0, p=0.1) - >>> s2.x_error(0, p=0.1) - >>> s.measure(0) == s2.measure(0) - True + >>> sim = stim.FlipSimulator(batch_size=256) """ @property def batch_size( @@ -4434,10 +4428,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.batch_size 256 - >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim = stim.FlipSimulator(batch_size=42) >>> sim.batch_size 42 """ @@ -4455,39 +4449,28 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False - """ - def do_circuit( - self, - circuit: stim.Circuit, - ) -> None: - """Applies a all the instructions in a circuit to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. - - Args: - circuit: The circuit to apply to the simulator's state. - - Examples: - >>> import stim - >>> assert False - """ - def do_instruction( - self, - instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], - ) -> None: - """Applies a circuit instruction to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... disable_stabilizer_randomization=True, + ... ) + >>> circuit = stim.Circuit(''' + ... X_ERROR(1) 0 1 3 + ... REPEAT 5 { + ... H 0 + ... C_XYZ 1 + ... } + ... ''') + >>> sim.do(circuit) + >>> sim.peek_pauli_flips() + [stim.PauliString("+ZZ_X")] - Args: - circuit: The circuit to apply to the simulator's state. + >>> sim.do(circuit[0]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YY__")] - Examples: - >>> import stim - >>> assert False + >>> sim.do(circuit[1]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YX__")] """ def get_detector_flips( self, @@ -4533,7 +4516,29 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... DETECTOR rec[-2] rec[-3] + ... DETECTOR rec[-1] rec[-2] + ... ''')) + + >>> sim.get_detector_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_detector_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_detector_flips(instance_index=2) + array([False, False]) + + >>> sim.get_detector_flips(detector_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_detector_flips(instance_index=2, detector_index=1) + array(False) """ def get_measurement_flips( self, @@ -4582,7 +4587,27 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit('M 0 1 2')) + + >>> sim.get_measurement_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_measurement_flips(bit_packed=True) + array([[0, 0], + [0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_measurement_flips(instance_index=1) + array([False, False, False]) + + >>> sim.get_measurement_flips(record_index=2) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_measurement_flips(instance_index=1, record_index=2) + array(False) """ def get_observable_flips( self, @@ -4628,7 +4653,29 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... OBSERVABLE_INCLUDE(0) rec[-2] + ... OBSERVABLE_INCLUDE(1) rec[-1] + ... ''')) + + >>> sim.get_observable_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_observable_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_observable_flips(instance_index=2) + array([False, False]) + + >>> sim.get_observable_flips(observable_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_observable_flips(instance_index=2, observable_index=1) + array(False) """ @property def num_detectors( @@ -4638,10 +4685,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_detectors 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 0 ... DETECTOR rec[-1] rec[-2] ... ''')) @@ -4656,12 +4703,12 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_measurements 0 - >>> sim.measure(5) + >>> sim.do(stim.Circuit('M 3 5')) >>> sim.num_measurements - 1 + 2 """ @property def num_observables( @@ -4671,10 +4718,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_observables 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 ... OBSERVABLE_INCLUDE(4) rec[-1] ... ''')) @@ -4689,28 +4736,77 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_qubits 0 - >>> sim.h(5) + >>> sim = stim.FlipSimulator(batch_size=256, num_qubits=4) + >>> sim.num_qubits + 4 + >>> sim.do(stim.Circuit('H 5')) >>> sim.num_qubits 6 """ - def peek_current_errors( + @overload + def peek_pauli_flips( self, - ) -> np.ndarray: - """Creates a numpy array describing the current pauli errors. + ) -> List[stim.PauliString]: + pass + @overload + def peek_pauli_flips( + self, + *, + instance_index: int, + ) -> stim.PauliString: + pass + def peek_pauli_flips( + self, + *, + instance_index: Optional[int] = None, + ) -> Union[stim.PauliString, List[stim.PauliString]]: + """Returns the current pauli errors packed into stim.PauliString instances. - Returns: - A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + Args: + instance_index: Defaults to None. When set to None, the pauli errors from + all instances are returned as a list of `stim.PauliString`. When set to + an integer, a single `stim.PauliString` is returned containing the + errors for the indexed instance. - Each entry in the array is the Pauli error on one qubit in one instance, - using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 - then there's a Y error on the qubit with index 5 in the shot with index 3. + Returns: + if instance_index is None: + A list of stim.PauliString, with the k'th entry being the errors from + the k'th simulation instance. + else: + A stim.PauliString with the errors from the k'th simulation instance. Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... disable_stabilizer_randomization=True, + ... num_qubits=10, + ... ) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+__________"), stim.PauliString("+__________")] + + >>> sim.peek_pauli_flips(instance_index=0) + stim.PauliString("+__________") + + >>> sim.do(stim.Circuit(''' + ... X_ERROR(1) 0 3 5 + ... Z_ERROR(1) 3 6 + ... ''')) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+X__Y_XZ___"), stim.PauliString("+X__Y_XZ___")] + + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... num_qubits=100, + ... ) + >>> flips: stim.PauliString = sim.peek_pauli_flips(instance_index=0) + >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization + ['+', 'Z', '_'] """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index f0865de7e..fa3311f7f 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4328,8 +4328,7 @@ class FlipSimulator: Examples: >>> import stim - >>> s = stim.FlipSimulator() - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=256) """ def __init__( self, @@ -4419,12 +4418,7 @@ class FlipSimulator: Examples: >>> import stim - >>> s = stim.FlipSimulator(seed=0) - >>> s2 = stim.FlipSimulator(seed=0) - >>> s.x_error(0, p=0.1) - >>> s2.x_error(0, p=0.1) - >>> s.measure(0) == s2.measure(0) - True + >>> sim = stim.FlipSimulator(batch_size=256) """ @property def batch_size( @@ -4434,10 +4428,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.batch_size 256 - >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim = stim.FlipSimulator(batch_size=42) >>> sim.batch_size 42 """ @@ -4455,39 +4449,28 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False - """ - def do_circuit( - self, - circuit: stim.Circuit, - ) -> None: - """Applies a all the instructions in a circuit to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. - - Args: - circuit: The circuit to apply to the simulator's state. - - Examples: - >>> import stim - >>> assert False - """ - def do_instruction( - self, - instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock], - ) -> None: - """Applies a circuit instruction to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... disable_stabilizer_randomization=True, + ... ) + >>> circuit = stim.Circuit(''' + ... X_ERROR(1) 0 1 3 + ... REPEAT 5 { + ... H 0 + ... C_XYZ 1 + ... } + ... ''') + >>> sim.do(circuit) + >>> sim.peek_pauli_flips() + [stim.PauliString("+ZZ_X")] - Args: - circuit: The circuit to apply to the simulator's state. + >>> sim.do(circuit[0]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YY__")] - Examples: - >>> import stim - >>> assert False + >>> sim.do(circuit[1]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YX__")] """ def get_detector_flips( self, @@ -4533,7 +4516,29 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... DETECTOR rec[-2] rec[-3] + ... DETECTOR rec[-1] rec[-2] + ... ''')) + + >>> sim.get_detector_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_detector_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_detector_flips(instance_index=2) + array([False, False]) + + >>> sim.get_detector_flips(detector_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_detector_flips(instance_index=2, detector_index=1) + array(False) """ def get_measurement_flips( self, @@ -4582,7 +4587,27 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit('M 0 1 2')) + + >>> sim.get_measurement_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_measurement_flips(bit_packed=True) + array([[0, 0], + [0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_measurement_flips(instance_index=1) + array([False, False, False]) + + >>> sim.get_measurement_flips(record_index=2) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_measurement_flips(instance_index=1, record_index=2) + array(False) """ def get_observable_flips( self, @@ -4628,7 +4653,29 @@ class FlipSimulator: Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... OBSERVABLE_INCLUDE(0) rec[-2] + ... OBSERVABLE_INCLUDE(1) rec[-1] + ... ''')) + + >>> sim.get_observable_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_observable_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_observable_flips(instance_index=2) + array([False, False]) + + >>> sim.get_observable_flips(observable_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_observable_flips(instance_index=2, observable_index=1) + array(False) """ @property def num_detectors( @@ -4638,10 +4685,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_detectors 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 0 ... DETECTOR rec[-1] rec[-2] ... ''')) @@ -4656,12 +4703,12 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_measurements 0 - >>> sim.measure(5) + >>> sim.do(stim.Circuit('M 3 5')) >>> sim.num_measurements - 1 + 2 """ @property def num_observables( @@ -4671,10 +4718,10 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_observables 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 ... OBSERVABLE_INCLUDE(4) rec[-1] ... ''')) @@ -4689,28 +4736,77 @@ class FlipSimulator: Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_qubits 0 - >>> sim.h(5) + >>> sim = stim.FlipSimulator(batch_size=256, num_qubits=4) + >>> sim.num_qubits + 4 + >>> sim.do(stim.Circuit('H 5')) >>> sim.num_qubits 6 """ - def peek_current_errors( + @overload + def peek_pauli_flips( self, - ) -> np.ndarray: - """Creates a numpy array describing the current pauli errors. + ) -> List[stim.PauliString]: + pass + @overload + def peek_pauli_flips( + self, + *, + instance_index: int, + ) -> stim.PauliString: + pass + def peek_pauli_flips( + self, + *, + instance_index: Optional[int] = None, + ) -> Union[stim.PauliString, List[stim.PauliString]]: + """Returns the current pauli errors packed into stim.PauliString instances. - Returns: - A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + Args: + instance_index: Defaults to None. When set to None, the pauli errors from + all instances are returned as a list of `stim.PauliString`. When set to + an integer, a single `stim.PauliString` is returned containing the + errors for the indexed instance. - Each entry in the array is the Pauli error on one qubit in one instance, - using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 - then there's a Y error on the qubit with index 5 in the shot with index 3. + Returns: + if instance_index is None: + A list of stim.PauliString, with the k'th entry being the errors from + the k'th simulation instance. + else: + A stim.PauliString with the errors from the k'th simulation instance. Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... disable_stabilizer_randomization=True, + ... num_qubits=10, + ... ) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+__________"), stim.PauliString("+__________")] + + >>> sim.peek_pauli_flips(instance_index=0) + stim.PauliString("+__________") + + >>> sim.do(stim.Circuit(''' + ... X_ERROR(1) 0 3 5 + ... Z_ERROR(1) 3 6 + ... ''')) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+X__Y_XZ___"), stim.PauliString("+X__Y_XZ___")] + + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... num_qubits=100, + ... ) + >>> flips: stim.PauliString = sim.peek_pauli_flips(instance_index=0) + >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization + ['+', 'Z', '_'] """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/src/stim/simulators/frame_simulator.h b/src/stim/simulators/frame_simulator.h index cdcb650f1..1bc421cca 100644 --- a/src/stim/simulators/frame_simulator.h +++ b/src/stim/simulators/frame_simulator.h @@ -82,7 +82,7 @@ struct FrameSimulator { void ensure_safe_to_do_circuit_with_stats(const CircuitStats &stats); void safe_do_instruction(const CircuitInstruction &instruction); - void safe_do_circuit(const Circuit &circuit, uint64_t repetititions = 1); + void safe_do_circuit(const Circuit &circuit, uint64_t repetitions = 1); void do_circuit(const Circuit &circuit); void reset_all(); diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index a38deb06c..1a8577892 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -1,28 +1,37 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #include "stim/simulators/frame_simulator.pybind.h" +#include "stim/circuit/circuit_instruction.pybind.h" +#include "stim/circuit/circuit_repeat_block.pybind.h" #include "stim/py/base.pybind.h" #include "stim/py/numpy.pybind.h" #include "stim/simulators/frame_simulator.h" -#include "stim/circuit/circuit_instruction.pybind.h" -#include "stim/circuit/circuit_repeat_block.pybind.h" +#include "stim/stabilizers/pauli_string.pybind.h" using namespace stim; using namespace stim_pybind; +std::optional py_index_to_optional_size_t(const pybind11::object &index, size_t length, const char *val_name, const char *len_name) { + if (index.is_none()) { + return {}; + } + int64_t i = pybind11::cast(index); + if (i < -(int64_t)length || (uint64_t)i >= length) { + std::stringstream msg; + msg << "not ("; + msg << "-" << len_name << " <= "; + msg << val_name << "=" << index; + msg << " < "; + msg << len_name << "=" << length; + msg << ")"; + throw std::out_of_range(msg.str()); + } + if (i < 0) { + i += length; + } + assert(i >= 0); + return (size_t)i; +} + pybind11::class_> stim_pybind::pybind_frame_simulator(pybind11::module &m) { return pybind11::class_>( m, @@ -38,33 +47,28 @@ pybind11::class_> stim_pybind::pybind_frame_si Examples: >>> import stim - >>> s = stim.FlipSimulator() - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=256) )DOC") .data()); } template -pybind11::object peek_current_pauli_errors(const FrameSimulator &self) { - uint8_t *buffer = new uint8_t[self.num_qubits * self.batch_size]; - size_t t = 0; - for (size_t k_maj = 0; k_maj < self.batch_size; k_maj++) { - for (size_t k_min = 0; k_min < self.num_qubits; k_min++) { - uint8_t x = self.x_table[k_maj][k_min]; - uint8_t z = self.z_table[k_maj][k_min]; - buffer[t++] = (x ^ z) + z * 2; - } - } +pybind11::object peek_pauli_flips(const FrameSimulator &self, const pybind11::object &py_instance_index) { + std::optional instance_index = py_index_to_optional_size_t( + py_instance_index, + self.batch_size, + "instance_index", + "batch_size"); - pybind11::capsule free_when_done(buffer, [](void *f) { - delete[] reinterpret_cast(f); - }); + if (instance_index.has_value()) { + return pybind11::cast(PyPauliString(self.get_frame(instance_index.value()))); + } - return pybind11::array_t( - {(pybind11::ssize_t)self.num_qubits, (pybind11::ssize_t)self.batch_size}, - {(pybind11::ssize_t)self.batch_size, (pybind11::ssize_t)1}, - buffer, - free_when_done); + std::vector result; + for (size_t k = 0; k < self.batch_size; k++) { + result.push_back(PyPauliString(self.get_frame(k))); + } + return pybind11::cast(std::move(result)); } template @@ -100,7 +104,7 @@ pybind11::object sliced_table_to_numpy( if (minor_index.has_value()) { bool b = row[minor_index.value()]; auto np = pybind11::module::import("numpy"); - return np.attr("array")(b, bit_packed ? np.attr("bool_") : np.attr("uint8")); + return np.attr("array")(b, bit_packed ? np.attr("uint8") : np.attr("bool_")); } else { return simd_bits_to_numpy(row, num_minor_exact, bit_packed); } @@ -114,29 +118,6 @@ pybind11::object sliced_table_to_numpy( } } -std::optional py_index_to_optional_size_t(pybind11::object index, size_t length, const char *val_name, const char *len_name) { - std::optional instance_index; - if (index.is_none()) { - return {}; - } - int64_t i = pybind11::cast(index); - if (i < -(int64_t)length || (uint64_t)i >= length) { - std::stringstream msg; - msg << "not ("; - msg << "-" << len_name << " <= "; - msg << val_name << "=" << index; - msg << " < "; - msg << len_name << "=" << length; - msg << ")"; - throw std::out_of_range(msg.str()); - } - if (i < 0) { - i += length; - } - assert(i >= 0); - return (size_t)i; -} - template pybind11::object get_measurement_flips( FrameSimulator &self, @@ -186,7 +167,7 @@ pybind11::object get_detector_flips( py_detector_index, num_detectors, "detector_index", - "num_measurements"); + "num_detectors"); return sliced_table_to_numpy( self.det_record.storage, @@ -390,12 +371,7 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> s = stim.FlipSimulator(seed=0) - >>> s2 = stim.FlipSimulator(seed=0) - >>> s.x_error(0, p=0.1) - >>> s2.x_error(0, p=0.1) - >>> s.measure(0) == s2.measure(0) - True + >>> sim = stim.FlipSimulator(batch_size=256) )DOC") .data()); @@ -409,10 +385,10 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.batch_size 256 - >>> sim = stim.FrameSimulator(batch_size=42) + >>> sim = stim.FlipSimulator(batch_size=42) >>> sim.batch_size 42 )DOC") @@ -428,10 +404,13 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_qubits 0 - >>> sim.h(5) + >>> sim = stim.FlipSimulator(batch_size=256, num_qubits=4) + >>> sim.num_qubits + 4 + >>> sim.do(stim.Circuit('H 5')) >>> sim.num_qubits 6 )DOC") @@ -447,10 +426,10 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_observables 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 ... OBSERVABLE_INCLUDE(4) rec[-1] ... ''')) @@ -469,12 +448,12 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_measurements 0 - >>> sim.measure(5) + >>> sim.do(stim.Circuit('M 3 5')) >>> sim.num_measurements - 1 + 2 )DOC") .data()); @@ -488,10 +467,10 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> sim = stim.FrameSimulator(batch_size=256) + >>> sim = stim.FlipSimulator(batch_size=256) >>> sim.num_detectors 0 - >>> sim.do_circuit(stim.Circuit(''' + >>> sim.do(stim.Circuit(''' ... M 0 0 ... DETECTOR rec[-1] rec[-2] ... ''')) @@ -501,22 +480,59 @@ void stim_pybind::pybind_frame_simulator_methods( .data()); c.def( - "peek_current_pauli_errors", - &peek_current_pauli_errors, + "peek_pauli_flips", + &peek_pauli_flips, + pybind11::kw_only(), + pybind11::arg("instance_index") = pybind11::none(), clean_doc_string(R"DOC( - @signature def peek_current_errors(self) -> np.ndarray: - Creates a numpy array describing the current pauli errors. + @overload def peek_pauli_flips(self) -> List[stim.PauliString]: + @overload def peek_pauli_flips(self, *, instance_index: int) -> stim.PauliString: + @signature def peek_pauli_flips(self, *, instance_index: Optional[int] = None) -> Union[stim.PauliString, List[stim.PauliString]]: + Returns the current pauli errors packed into stim.PauliString instances. - Returns: - A numpy array with shape=(self.num_qubits, self.batch_size), dtype=np.uint8. + Args: + instance_index: Defaults to None. When set to None, the pauli errors from + all instances are returned as a list of `stim.PauliString`. When set to + an integer, a single `stim.PauliString` is returned containing the + errors for the indexed instance. - Each entry in the array is the Pauli error on one qubit in one instance, - using the convention 0=I, 1=X, 2=Y, 3=Z. For example, if result[5][3] == 2 - then there's a Y error on the qubit with index 5 in the shot with index 3. + Returns: + if instance_index is None: + A list of stim.PauliString, with the k'th entry being the errors from + the k'th simulation instance. + else: + A stim.PauliString with the errors from the k'th simulation instance. Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... disable_stabilizer_randomization=True, + ... num_qubits=10, + ... ) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+__________"), stim.PauliString("+__________")] + + >>> sim.peek_pauli_flips(instance_index=0) + stim.PauliString("+__________") + + >>> sim.do(stim.Circuit(''' + ... X_ERROR(1) 0 3 5 + ... Z_ERROR(1) 3 6 + ... ''')) + + >>> sim.peek_pauli_flips() + [stim.PauliString("+X__Y_XZ___"), stim.PauliString("+X__Y_XZ___")] + + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... num_qubits=100, + ... ) + >>> flips: stim.PauliString = sim.peek_pauli_flips(instance_index=0) + >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization + ['+', 'Z', '_'] + )DOC") .data()); @@ -569,7 +585,27 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit('M 0 1 2')) + + >>> sim.get_measurement_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_measurement_flips(bit_packed=True) + array([[0, 0], + [0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_measurement_flips(instance_index=1) + array([False, False, False]) + + >>> sim.get_measurement_flips(record_index=2) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_measurement_flips(instance_index=1, record_index=2) + array(False) )DOC") .data()); @@ -619,7 +655,30 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... DETECTOR rec[-2] rec[-3] + ... DETECTOR rec[-1] rec[-2] + ... ''')) + + >>> sim.get_detector_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) + + >>> sim.get_detector_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) + + >>> sim.get_detector_flips(instance_index=2) + array([False, False]) + + >>> sim.get_detector_flips(detector_index=1) + array([False, False, False, False, False, False, False, False, False]) + + >>> sim.get_detector_flips(instance_index=2, detector_index=1) + array(False) + )DOC") .data()); @@ -669,68 +728,39 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> assert False - )DOC") - .data()); + >>> sim = stim.FlipSimulator(batch_size=9) + >>> sim.do(stim.Circuit(''' + ... M 0 0 0 + ... OBSERVABLE_INCLUDE(0) rec[-2] + ... OBSERVABLE_INCLUDE(1) rec[-1] + ... ''')) - c.def( - "do_circuit", - [](FrameSimulator &self, const Circuit &circuit) { - self.safe_do_circuit(circuit); - }, - pybind11::arg("circuit"), - clean_doc_string(R"DOC( - Applies a all the instructions in a circuit to the simulator's state. + >>> sim.get_observable_flips() + array([[False, False, False, False, False, False, False, False, False], + [False, False, False, False, False, False, False, False, False]]) - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. + >>> sim.get_observable_flips(bit_packed=True) + array([[0, 0], + [0, 0]], dtype=uint8) - Args: - circuit: The circuit to apply to the simulator's state. + >>> sim.get_observable_flips(instance_index=2) + array([False, False]) - Examples: - >>> import stim - >>> assert False - )DOC") - .data()); + >>> sim.get_observable_flips(observable_index=1) + array([False, False, False, False, False, False, False, False, False]) - c.def( - "do_instruction", - [](FrameSimulator &self, const pybind11::object &instruction) { - if (pybind11::isinstance(instruction)) { - self.safe_do_instruction(pybind11::cast(instruction).as_operation_ref()); - } else if (pybind11::isinstance(instruction)) { - const CircuitRepeatBlock &block = pybind11::cast(instruction); - self.safe_do_circuit(block.body, block.repeat_count); - } else { - throw std::invalid_argument("Not a stim.CircuitInstruction or stim.CircuitRepeatBlock '" + pybind11::cast(pybind11::repr(instruction)) + "'."); - } - }, - pybind11::arg("instruction"), - clean_doc_string(R"DOC( - @signature def do_instruction(self, instruction: Union[stim.CircuitInstruction, stim.CircuitRepeatBlock]) -> None: - Applies a circuit instruction to the simulator's state. - - The results of any measurements performed can be retrieved using the - `get_measurement_flips` method. - - Args: - circuit: The circuit to apply to the simulator's state. - - Examples: - >>> import stim - >>> assert False + >>> sim.get_observable_flips(instance_index=2, observable_index=1) + array(False) )DOC") .data()); c.def( "do", [](FrameSimulator &self, const pybind11::object &obj) { - if (pybind11::isinstance(obj)) { + if (pybind11::isinstance(obj)) { self.safe_do_circuit(pybind11::cast(obj)); - } else if (pybind11::isinstance(obj)) { - const CircuitRepeatBlock &block = pybind11::cast(obj); - self.safe_do_circuit(block.body, block.repeat_count); + } else if (pybind11::isinstance(obj)) { + self.safe_do_instruction(pybind11::cast(obj)); } else if (pybind11::isinstance(obj)) { const CircuitRepeatBlock &block = pybind11::cast(obj); self.safe_do_circuit(block.body, block.repeat_count); @@ -751,7 +781,28 @@ void stim_pybind::pybind_frame_simulator_methods( Examples: >>> import stim - >>> assert False + >>> sim = stim.FlipSimulator( + ... batch_size=1, + ... disable_stabilizer_randomization=True, + ... ) + >>> circuit = stim.Circuit(''' + ... X_ERROR(1) 0 1 3 + ... REPEAT 5 { + ... H 0 + ... C_XYZ 1 + ... } + ... ''') + >>> sim.do(circuit) + >>> sim.peek_pauli_flips() + [stim.PauliString("+ZZ_X")] + + >>> sim.do(circuit[0]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YY__")] + + >>> sim.do(circuit[1]) + >>> sim.peek_pauli_flips() + [stim.PauliString("+YX__")] )DOC") .data()); } diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index 565a30d1f..fe57a7f8a 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -1,3 +1,5 @@ +import collections + import stim import numpy as np @@ -8,7 +10,7 @@ def test_get_measurement_flips(): assert s.num_qubits == 0 assert s.batch_size == 11 - s.do_circuit(stim.Circuit(""" + s.do(stim.Circuit(""" X_ERROR(1) 100 M 100 """)) @@ -29,28 +31,31 @@ def test_stabilizer_randomization(): num_qubits=10, disable_stabilizer_randomization=True, ) - np.testing.assert_array_equal( - s.peek_current_pauli_errors(), - np.zeros(shape=(10, 256), dtype=np.uint8), - ) - s.do_circuit(stim.Circuit("R 19")) - np.testing.assert_array_equal( - s.peek_current_pauli_errors(), - np.zeros(shape=(20, 256), dtype=np.uint8), + assert s.peek_pauli_flips() == [stim.PauliString(10)] * 256 + s.do(stim.Circuit("R 19")) + assert s.peek_pauli_flips() == [stim.PauliString(20)] * 256 + + s = stim.FlipSimulator( + batch_size=256, + num_qubits=10, ) + v = np.array([list(p) for p in s.peek_pauli_flips()], dtype=np.uint8) + assert v.shape == (256, 10) + assert np.all((v == 0) | (v == 3)) + assert 0.2 < np.count_nonzero(v == 3) / (256 * 10) < 0.8 s = stim.FlipSimulator( batch_size=256, num_qubits=10, disable_stabilizer_randomization=False, ) - v = s.peek_current_pauli_errors() - assert v.shape == (10, 256) + v = np.array([list(p) for p in s.peek_pauli_flips()], dtype=np.uint8) + assert v.shape == (256, 10) assert np.all((v == 0) | (v == 3)) assert 0.2 < np.count_nonzero(v == 3) / (256 * 10) < 0.8 - s.do_circuit(stim.Circuit("R 19")) - v = s.peek_current_pauli_errors() - assert v.shape == (20, 256) + s.do(stim.Circuit("R 19")) + v = np.array([list(p) for p in s.peek_pauli_flips()], dtype=np.uint8) + assert v.shape == (256, 20) assert np.all((v == 0) | (v == 3)) assert 0.2 < np.count_nonzero(v == 3) / (256 * 20) < 0.8 @@ -61,7 +66,7 @@ def test_get_detector_flips(): assert s.num_qubits == 0 assert s.batch_size == 11 - s.do_circuit(stim.Circuit(""" + s.do(stim.Circuit(""" X_ERROR(1) 25 M 24 25 DETECTOR rec[-1] @@ -91,7 +96,7 @@ def test_get_observable_flips(): assert s.batch_size == 11 assert s.num_observables == 0 - s.do_circuit(stim.Circuit(""" + s.do(stim.Circuit(""" X_ERROR(1) 25 M 24 25 OBSERVABLE_INCLUDE(2) rec[-1] @@ -112,3 +117,56 @@ def test_get_observable_flips(): [False, False, True]) assert not s.get_observable_flips(observable_index=1, instance_index=1) assert s.get_observable_flips(observable_index=2, instance_index=1) + + +def test_peek_pauli_flips(): + sim = stim.FlipSimulator(batch_size=500, disable_stabilizer_randomization=True) + sim.do(stim.Circuit(""" + X_ERROR(0.3) 1 + Y_ERROR(0.3) 2 + Z_ERROR(0.3) 3 + DEPOLARIZE1(0.3) 4 + """)) + assert sim.num_qubits == 5 + assert sim.batch_size == 500 + flips = sim.peek_pauli_flips() + assert len(flips) == 500 + assert len(flips[0]) == 5 + v0 = collections.Counter([p[0] for p in flips]) + v1 = collections.Counter([p[1] for p in flips]) + v2 = collections.Counter([p[2] for p in flips]) + v3 = collections.Counter([p[3] for p in flips]) + v4 = collections.Counter([p[4] for p in flips]) + assert v0.keys() == {0} + assert v1.keys() == {0, 1} + assert v2.keys() == {0, 2} + assert v3.keys() == {0, 3} + assert v4.keys() == {0, 1, 2, 3} + assert v0[0] == 500 + assert 250 < v1[0] < 450 + assert 250 < v2[0] < 450 + assert 250 < v3[0] < 450 + assert 250 < v4[0] < 450 + + +def test_surface_code(): + circuit = stim.Circuit.generated( + "surface_code:rotated_memory_x", + distance=3, + rounds=5, + before_round_data_depolarization=1e-2, + before_measure_flip_probability=1e-2, + after_reset_flip_probability=1e-2, + after_clifford_depolarization=1e-2, + ) + + # Find the index of the final MR layer. + mr_layer = len(circuit) - 1 + while circuit[mr_layer].name != 'MR': + mr_layer -= 1 + circuit_before_mr = circuit[:mr_layer] + + sim = stim.FlipSimulator(batch_size=256, disable_stabilizer_randomization=True) + sim.do(circuit_before_mr) + for b in sim.peek_pauli_flips(): + print(list(b)) From 67f0886f08b8d093b6b1d6b879bc15e753e7799d Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 21:41:28 -0700 Subject: [PATCH 7/8] typefix --- src/stim/circuit/circuit_instruction.h | 2 +- src/stim/simulators/frame_simulator.h | 2 +- src/stim/simulators/frame_simulator.pybind.cc | 30 ------------------- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/src/stim/circuit/circuit_instruction.h b/src/stim/circuit/circuit_instruction.h index f32534d9c..c231de5c8 100644 --- a/src/stim/circuit/circuit_instruction.h +++ b/src/stim/circuit/circuit_instruction.h @@ -30,7 +30,7 @@ struct CircuitStats { uint64_t num_observables = 0; uint64_t num_measurements = 0; uint32_t num_qubits = 0; - uint32_t num_ticks = 0; + uint64_t num_ticks = 0; uint32_t max_lookback = 0; uint32_t num_sweep_bits = 0; diff --git a/src/stim/simulators/frame_simulator.h b/src/stim/simulators/frame_simulator.h index 1bc421cca..5b60396e0 100644 --- a/src/stim/simulators/frame_simulator.h +++ b/src/stim/simulators/frame_simulator.h @@ -44,7 +44,7 @@ enum FrameSimulatorMode { template struct FrameSimulator { size_t num_qubits; // Number of qubits being tracked. - size_t num_observables; // Number of observables being tracked. + uint64_t num_observables; // Number of observables being tracked. bool keeping_detection_data; // Whether or not to store dets and obs data. size_t batch_size; // Number of instances being tracked. simd_bit_table x_table; // x_table[q][k] is whether or not there's an X error on qubit q in instance k. diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index 1a8577892..079465185 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -250,36 +250,6 @@ pybind11::object get_xz_flips( return result; } -//template -//PyCircuitInstruction build_single_qubit_gate_instruction_ensure_size( -// TableauSimulator &self, GateType gate_type, const pybind11::args &args, SpanRef gate_args = {}) { -// std::vector targets; -// uint32_t max_q = 0; -// try { -// for (const auto &e : args) { -// if (pybind11::isinstance(e)) { -// targets.push_back(pybind11::cast(e)); -// } else { -// uint32_t q = e.cast(); -// max_q = std::max(max_q, q & TARGET_VALUE_MASK); -// targets.push_back(GateTarget{q}); -// } -// } -// } catch (const pybind11::cast_error &) { -// throw std::out_of_range("Target qubits must be non-negative integers."); -// } -// -// std::vector gate_args_vec; -// for (const auto &e : gate_args) { -// gate_args_vec.push_back(e); -// } -// -// // Note: quadratic behavior. -// self.ensure_large_enough_for_qubits(max_q + 1); -// -// return PyCircuitInstruction(gate_type, targets, gate_args_vec); -//} -// void stim_pybind::pybind_frame_simulator_methods( pybind11::module &m, pybind11::class_> &c) { c.def( From 24d6c709896c5d2d32f7659966f23f91b5db3d2c Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 19 Aug 2023 22:12:14 -0700 Subject: [PATCH 8/8] Avoid .value for OSX, add set_pauli_flip --- doc/python_api_reference_vDev.md | 37 ++++++ doc/stim.pyi | 29 ++++ glue/python/src/stim/__init__.pyi | 29 ++++ src/stim/simulators/frame_simulator.pybind.cc | 124 +++++++++++------- .../simulators/frame_simulator_pybind_test.py | 68 ++++++++++ 5 files changed, 239 insertions(+), 48 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 9429eb9bb..8c2a392a1 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -179,6 +179,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.FlipSimulator.num_observables`](#stim.FlipSimulator.num_observables) - [`stim.FlipSimulator.num_qubits`](#stim.FlipSimulator.num_qubits) - [`stim.FlipSimulator.peek_pauli_flips`](#stim.FlipSimulator.peek_pauli_flips) + - [`stim.FlipSimulator.set_pauli_flip`](#stim.FlipSimulator.set_pauli_flip) - [`stim.FlippedMeasurement`](#stim.FlippedMeasurement) - [`stim.FlippedMeasurement.__init__`](#stim.FlippedMeasurement.__init__) - [`stim.FlippedMeasurement.observable`](#stim.FlippedMeasurement.observable) @@ -6316,6 +6317,42 @@ def peek_pauli_flips( """ ``` + +```python +# stim.FlipSimulator.set_pauli_flip + +# (in class stim.FlipSimulator) +def set_pauli_flip( + self, + pauli: Union[str, int], + *, + qubit_index: int, + instance_index: int, +) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + qubit_index: The qubit to put the error on. Must be non-negative. The state + will automatically expand as needed to store the error. + instance_index: The simulation index to put the error inside. Use negative + indices to index from the end of the list. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.set_pauli_flip('X', qubit_index=2, instance_index=1) + >>> sim.peek_pauli_flips() + [stim.PauliString("+___"), stim.PauliString("+__X")] + """ +``` + ```python # stim.FlippedMeasurement diff --git a/doc/stim.pyi b/doc/stim.pyi index fa3311f7f..03b0e6ab7 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -4808,6 +4808,35 @@ class FlipSimulator: >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization ['+', 'Z', '_'] """ + def set_pauli_flip( + self, + pauli: Union[str, int], + *, + qubit_index: int, + instance_index: int, + ) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + qubit_index: The qubit to put the error on. Must be non-negative. The state + will automatically expand as needed to store the error. + instance_index: The simulation index to put the error inside. Use negative + indices to index from the end of the list. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.set_pauli_flip('X', qubit_index=2, instance_index=1) + >>> sim.peek_pauli_flips() + [stim.PauliString("+___"), stim.PauliString("+__X")] + """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index fa3311f7f..03b0e6ab7 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -4808,6 +4808,35 @@ class FlipSimulator: >>> sorted(set(str(flips))) # Should have Zs from stabilizer randomization ['+', 'Z', '_'] """ + def set_pauli_flip( + self, + pauli: Union[str, int], + *, + qubit_index: int, + instance_index: int, + ) -> None: + """Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + qubit_index: The qubit to put the error on. Must be non-negative. The state + will automatically expand as needed to store the error. + instance_index: The simulation index to put the error inside. Use negative + indices to index from the end of the list. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.set_pauli_flip('X', qubit_index=2, instance_index=1) + >>> sim.peek_pauli_flips() + [stim.PauliString("+___"), stim.PauliString("+__X")] + """ class FlippedMeasurement: """Describes a measurement that was flipped. diff --git a/src/stim/simulators/frame_simulator.pybind.cc b/src/stim/simulators/frame_simulator.pybind.cc index 079465185..d2717986f 100644 --- a/src/stim/simulators/frame_simulator.pybind.cc +++ b/src/stim/simulators/frame_simulator.pybind.cc @@ -61,7 +61,7 @@ pybind11::object peek_pauli_flips(const FrameSimulator &self, const pybind11: "batch_size"); if (instance_index.has_value()) { - return pybind11::cast(PyPauliString(self.get_frame(instance_index.value()))); + return pybind11::cast(PyPauliString(self.get_frame(*instance_index))); } std::vector result; @@ -100,9 +100,9 @@ pybind11::object sliced_table_to_numpy( std::optional minor_index, bool bit_packed) { if (major_index.has_value()) { - simd_bits_range_ref row = table[major_index.value()]; + simd_bits_range_ref row = table[*major_index]; if (minor_index.has_value()) { - bool b = row[minor_index.value()]; + bool b = row[*minor_index]; auto np = pybind11::module::import("numpy"); return np.attr("array")(b, bit_packed ? np.attr("uint8") : np.attr("bool_")); } else { @@ -110,7 +110,7 @@ pybind11::object sliced_table_to_numpy( } } else { if (minor_index.has_value()) { - auto data = table.read_across_majors_at_minor_index(0, num_major_exact, minor_index.value()); + auto data = table.read_across_majors_at_minor_index(0, num_major_exact, *minor_index); return simd_bits_to_numpy(data, num_major_exact, bit_packed); } else { return simd_bit_table_to_numpy(table, num_major_exact, num_minor_exact, bit_packed); @@ -206,50 +206,6 @@ pybind11::object get_obs_flips( bit_packed); } -template -pybind11::object get_xz_flips( - FrameSimulator &self, - bool x, - bool z, - const pybind11::object &py_observable_index, - const pybind11::object &py_instance_index, - bool bit_packed) { - - std::optional instance_index = py_index_to_optional_size_t( - py_instance_index, - self.batch_size, - "instance_index", - "batch_size"); - - std::optional qubit_index = py_index_to_optional_size_t( - py_observable_index, - self.num_observables, - "qubit_index", - "num_qubits"); - - simd_bit_table *table; - if (x & z) { - self.x_table.data ^= self.z_table.data; - table = &self.x_table; - } else if (x) { - table = &self.x_table; - } else { - assert(z); - table = &self.z_table; - } - auto result = sliced_table_to_numpy( - *table, - self.num_qubits, - self.batch_size, - qubit_index, - instance_index, - bit_packed); - if (x & z) { - self.x_table.data ^= self.z_table.data; - } - return result; -} - void stim_pybind::pybind_frame_simulator_methods( pybind11::module &m, pybind11::class_> &c) { c.def( @@ -449,6 +405,78 @@ void stim_pybind::pybind_frame_simulator_methods( )DOC") .data()); + c.def( + "set_pauli_flip", + [](FrameSimulator &self, const pybind11::object &pauli, int64_t qubit_index, int64_t instance_index) { + uint8_t p = 255; + try { + p = pybind11::cast(pauli); + } catch (const pybind11::cast_error &) { + try { + std::string s = pybind11::cast(pauli); + if (s == "X") { + p = 1; + } else if (s == "Y") { + p = 2; + } else if (s == "Z") { + p = 3; + } else if (s == "I" || s == "_") { + p = 0; + } + } catch (const pybind11::cast_error &) { + } + } + if (p > 3) { + throw std::invalid_argument("Expected pauli in [0, 1, 2, 3, '_', 'I', 'X', 'Y', 'Z']"); + } + if (instance_index < 0) { + instance_index += self.batch_size; + } + if (qubit_index < 0) { + throw std::out_of_range("qubit_index"); + } + if (instance_index < 0 || (uint64_t)instance_index >= self.batch_size) { + throw std::out_of_range("instance_index"); + } + if ((uint64_t)qubit_index >= self.num_qubits) { + CircuitStats stats; + stats.num_qubits = qubit_index + 1; + self.ensure_safe_to_do_circuit_with_stats(stats); + } + p ^= p >> 1; + self.x_table[qubit_index][instance_index] = (p & 1) != 0; + self.z_table[qubit_index][instance_index] = (p & 2) != 0; + }, + pybind11::arg("pauli"), + pybind11::kw_only(), + pybind11::arg("qubit_index"), + pybind11::arg("instance_index"), + clean_doc_string(R"DOC( + @signature def set_pauli_flip(self, pauli: Union[str, int], *, qubit_index: int, instance_index: int) -> None: + Sets the pauli flip on a given qubit in a given simulation instance. + + Args: + pauli: The pauli, specified as an integer or string. + Uses the convention 0=I, 1=X, 2=Y, 3=Z. + Any value from [0, 1, 2, 3, 'X', 'Y', 'Z', 'I', '_'] is allowed. + qubit_index: The qubit to put the error on. Must be non-negative. The state + will automatically expand as needed to store the error. + instance_index: The simulation index to put the error inside. Use negative + indices to index from the end of the list. + + Examples: + >>> import stim + >>> sim = stim.FlipSimulator( + ... batch_size=2, + ... num_qubits=3, + ... disable_stabilizer_randomization=True, + ... ) + >>> sim.set_pauli_flip('X', qubit_index=2, instance_index=1) + >>> sim.peek_pauli_flips() + [stim.PauliString("+___"), stim.PauliString("+__X")] + )DOC") + .data()); + c.def( "peek_pauli_flips", &peek_pauli_flips, diff --git a/src/stim/simulators/frame_simulator_pybind_test.py b/src/stim/simulators/frame_simulator_pybind_test.py index fe57a7f8a..778838c0d 100644 --- a/src/stim/simulators/frame_simulator_pybind_test.py +++ b/src/stim/simulators/frame_simulator_pybind_test.py @@ -1,5 +1,6 @@ import collections +import pytest import stim import numpy as np @@ -170,3 +171,70 @@ def test_surface_code(): sim.do(circuit_before_mr) for b in sim.peek_pauli_flips(): print(list(b)) + + +def test_set_pauli_flip(): + sim = stim.FlipSimulator( + batch_size=2, + disable_stabilizer_randomization=True, + num_qubits=3, + ) + assert sim.peek_pauli_flips() == [ + stim.PauliString('___'), + stim.PauliString('___'), + ] + + sim.set_pauli_flip('X', qubit_index=2, instance_index=0) + assert sim.peek_pauli_flips() == [ + stim.PauliString('__X'), + stim.PauliString('___'), + ] + + sim.set_pauli_flip(3, qubit_index=1, instance_index=1) + assert sim.peek_pauli_flips() == [ + stim.PauliString('__X'), + stim.PauliString('_Z_'), + ] + + sim.set_pauli_flip(2, qubit_index=0, instance_index=1) + assert sim.peek_pauli_flips() == [ + stim.PauliString('__X'), + stim.PauliString('YZ_'), + ] + + sim.set_pauli_flip(1, qubit_index=0, instance_index=-1) + assert sim.peek_pauli_flips() == [ + stim.PauliString('__X'), + stim.PauliString('XZ_'), + ] + + sim.set_pauli_flip(0, qubit_index=2, instance_index=-2) + assert sim.peek_pauli_flips() == [ + stim.PauliString('___'), + stim.PauliString('XZ_'), + ] + + with pytest.raises(ValueError, match='Expected pauli'): + sim.set_pauli_flip(-1, qubit_index=0, instance_index=0) + with pytest.raises(ValueError, match='Expected pauli'): + sim.set_pauli_flip(4, qubit_index=0, instance_index=0) + with pytest.raises(ValueError, match='Expected pauli'): + sim.set_pauli_flip('R', qubit_index=0, instance_index=0) + with pytest.raises(ValueError, match='Expected pauli'): + sim.set_pauli_flip('XY', qubit_index=0, instance_index=0) + with pytest.raises(ValueError, match='Expected pauli'): + sim.set_pauli_flip(object(), qubit_index=0, instance_index=0) + + with pytest.raises(IndexError, match='instance_index'): + sim.set_pauli_flip('X', qubit_index=0, instance_index=-3) + with pytest.raises(IndexError, match='instance_index'): + sim.set_pauli_flip('X', qubit_index=0, instance_index=3) + + with pytest.raises(IndexError, match='qubit_index'): + sim.set_pauli_flip('X', qubit_index=-1, instance_index=0) + + sim.set_pauli_flip('X', qubit_index=4, instance_index=0) + assert sim.peek_pauli_flips() == [ + stim.PauliString('____X'), + stim.PauliString('XZ___'), + ]