From cc18bfd7a38f35728d5c54c88ef5ee7e6360c609 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 17 May 2024 22:18:23 -0700 Subject: [PATCH 1/5] Add `stim::ReferenceSampleTree` to support loop folded reference sampling Part of https://github.com/quantumlib/Stim/issues/768 --- file_lists/perf_files | 1 + file_lists/source_files_no_main | 1 + file_lists/test_files | 1 + src/stim.h | 1 + src/stim/io/measure_record.cc | 19 ++ src/stim/io/measure_record.h | 6 + src/stim/simulators/tableau_simulator.perf.cc | 1 + src/stim/util_top/reference_sample_tree.cc | 152 ++++++++++++ src/stim/util_top/reference_sample_tree.h | 69 ++++++ src/stim/util_top/reference_sample_tree.inl | 115 ++++++++++ .../util_top/reference_sample_tree.perf.cc | 23 ++ .../util_top/reference_sample_tree.test.cc | 216 ++++++++++++++++++ 12 files changed, 605 insertions(+) create mode 100644 src/stim/util_top/reference_sample_tree.cc create mode 100644 src/stim/util_top/reference_sample_tree.h create mode 100644 src/stim/util_top/reference_sample_tree.inl create mode 100644 src/stim/util_top/reference_sample_tree.perf.cc create mode 100644 src/stim/util_top/reference_sample_tree.test.cc diff --git a/file_lists/perf_files b/file_lists/perf_files index 5797e0695..2d6ac07e1 100644 --- a/file_lists/perf_files +++ b/file_lists/perf_files @@ -18,4 +18,5 @@ src/stim/stabilizers/tableau.perf.cc src/stim/stabilizers/tableau_iter.perf.cc src/stim/util_bot/error_decomp.perf.cc src/stim/util_bot/probability_util.perf.cc +src/stim/util_top/reference_sample_tree.perf.cc src/stim/util_top/stabilizers_to_tableau.perf.cc diff --git a/file_lists/source_files_no_main b/file_lists/source_files_no_main index 0974fe65e..8555fcd89 100644 --- a/file_lists/source_files_no_main +++ b/file_lists/source_files_no_main @@ -94,5 +94,6 @@ src/stim/util_top/circuit_vs_amplitudes.cc src/stim/util_top/export_crumble_url.cc src/stim/util_top/export_qasm.cc src/stim/util_top/export_quirk_url.cc +src/stim/util_top/reference_sample_tree.cc src/stim/util_top/simplified_circuit.cc src/stim/util_top/transform_without_feedback.cc diff --git a/file_lists/test_files b/file_lists/test_files index c3a34b1b7..efab5d9bd 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -89,6 +89,7 @@ src/stim/util_top/export_crumble_url.test.cc src/stim/util_top/export_qasm.test.cc src/stim/util_top/export_quirk_url.test.cc src/stim/util_top/has_flow.test.cc +src/stim/util_top/reference_sample_tree.test.cc src/stim/util_top/simplified_circuit.test.cc src/stim/util_top/stabilizers_to_tableau.test.cc src/stim/util_top/stabilizers_vs_amplitudes.test.cc diff --git a/src/stim.h b/src/stim.h index 4f71b9460..d1f55e3a7 100644 --- a/src/stim.h +++ b/src/stim.h @@ -115,6 +115,7 @@ #include "stim/util_top/export_qasm.h" #include "stim/util_top/export_quirk_url.h" #include "stim/util_top/has_flow.h" +#include "stim/util_top/reference_sample_tree.h" #include "stim/util_top/simplified_circuit.h" #include "stim/util_top/stabilizers_to_tableau.h" #include "stim/util_top/stabilizers_vs_amplitudes.h" diff --git a/src/stim/io/measure_record.cc b/src/stim/io/measure_record.cc index 62fcb0710..1bdf6cbbd 100644 --- a/src/stim/io/measure_record.cc +++ b/src/stim/io/measure_record.cc @@ -53,3 +53,22 @@ void MeasureRecord::record_result(bool result) { storage.push_back(result); unwritten++; } + +void MeasureRecord::record_results(const std::vector &results) { + storage.insert(storage.end(), results.begin(), results.end()); + unwritten += results.size(); +} + +void MeasureRecord::clear() { + unwritten = 0; + storage.clear(); +} + +void MeasureRecord::discard_results_past_max_lookback() { + if (storage.size() > max_lookback) { + storage.erase(storage.begin(), storage.end() - max_lookback); + } + if (unwritten > max_lookback) { + unwritten = max_lookback; + } +} diff --git a/src/stim/io/measure_record.h b/src/stim/io/measure_record.h index feb7901df..907a3a28b 100644 --- a/src/stim/io/measure_record.h +++ b/src/stim/io/measure_record.h @@ -47,8 +47,14 @@ struct MeasureRecord { /// Args: /// lookback: How far back the measurement is. lookback=1 is the latest measurement, 2 the second latest, etc. bool lookback(size_t lookback) const; + /// Batch record. + void record_results(const std::vector &results); /// Appends a measurement to the record. void record_result(bool result); + /// Clear the record. + void clear(); + /// Truncates the record to only include bits within the lookback limit. + void discard_results_past_max_lookback(); }; } // namespace stim diff --git a/src/stim/simulators/tableau_simulator.perf.cc b/src/stim/simulators/tableau_simulator.perf.cc index 545e57508..9534be307 100644 --- a/src/stim/simulators/tableau_simulator.perf.cc +++ b/src/stim/simulators/tableau_simulator.perf.cc @@ -14,6 +14,7 @@ #include "stim/simulators/tableau_simulator.h" +#include "stim/gen/circuit_gen_params.h" #include "stim/perf.perf.h" using namespace stim; diff --git a/src/stim/util_top/reference_sample_tree.cc b/src/stim/util_top/reference_sample_tree.cc new file mode 100644 index 000000000..2eca171af --- /dev/null +++ b/src/stim/util_top/reference_sample_tree.cc @@ -0,0 +1,152 @@ +#include "stim/util_top/reference_sample_tree.h" + +using namespace stim; + +bool ReferenceSampleTree::empty() const { + if (repetitions == 0) { + return true; + } + if (!prefix_bits.empty()) { + return false; + } + for (const auto &child: suffix_children) { + if (!child.empty()) { + return false; + } + } + return true; +} + +void ReferenceSampleTree::flatten_and_simplify_into(std::vector &out) const { + if (repetitions == 0) { + return; + } + + std::vector flattened; + if (!prefix_bits.empty()) { + flattened.push_back(ReferenceSampleTree{ + .prefix_bits = prefix_bits, + .suffix_children = {}, + .repetitions = 1, + }); + } + for (const auto &child : suffix_children) { + child.flatten_and_simplify_into(flattened); + } + + // Fuse children. + auto &f = flattened; + for (size_t k = 0; k < f.size(); k++) { + while (k + 1 < f.size() && f[k].repetitions == 1 && f[k].suffix_children.empty() && f[k + 1].repetitions == 1) { + f[k].suffix_children = std::move(f[k + 1].suffix_children); + f[k].prefix_bits.insert(f[k].prefix_bits.end(), f[k + 1].prefix_bits.begin(), f[k + 1].prefix_bits.end()); + f.erase(f.begin() + k + 1); + } + } + + if (repetitions == 1) { + // Un-nest all the children. + for (auto &e : flattened) { + out.push_back(e); + } + } else if (flattened.size() == 1) { + // Merge with single child. + flattened[0].repetitions *= repetitions; + out.push_back(std::move(flattened[0])); + } else if (flattened.empty()) { + // Nothing to report. + } else if (flattened[0].suffix_children.empty() && flattened[0].repetitions == 1) { + // Take payload from first child. + auto result = std::move(flattened[0]); + flattened.erase(flattened.begin()); + result.repetitions = repetitions; + result.suffix_children = std::move(flattened); + out.push_back(std::move(result)); + } else { + out.push_back(ReferenceSampleTree{ + .prefix_bits={}, + .suffix_children=std::move(flattened), + .repetitions=repetitions, + }); + } +} + +ReferenceSampleTree ReferenceSampleTree::simplified() const { + std::vector flat; + flatten_and_simplify_into(flat); + if (flat.empty()) { + return ReferenceSampleTree(); + } else if (flat.size() == 1) { + return flat[0]; + } + + ReferenceSampleTree result; + result.repetitions = 1; + if (flat[0].repetitions == 1 && flat[0].suffix_children.empty()) { + // Take payload from first child. + result = std::move(flat[0]); + flat.erase(flat.begin()); + } + result.suffix_children = std::move(flat); + return result; +} + +size_t ReferenceSampleTree::size() const { + size_t result = prefix_bits.size(); + for (const auto &child: suffix_children) { + result += child.size(); + } + return result * repetitions; +} + +void ReferenceSampleTree::decompress_into(std::vector &output) const { + for (uint64_t k = 0; k < repetitions; k++) { + output.insert(output.end(), prefix_bits.begin(), prefix_bits.end()); + for (const auto &child: suffix_children) { + child.decompress_into(output); + } + } +} + +ReferenceSampleTree ReferenceSampleTree::from_circuit_reference_sample(const Circuit &circuit) { + auto stats = circuit.compute_stats(); + std::mt19937_64 irrelevant_rng{0}; + CompressedReferenceSampleHelper helper( + TableauSimulator( + std::move(irrelevant_rng), + stats.num_qubits, + +1, + MeasureRecord(stats.max_lookback))); + return helper.do_loop_with_tortoise_hare_folding(circuit, 1).simplified(); +} + +std::string ReferenceSampleTree::str() const { + std::stringstream ss; + ss << *this; + return ss.str(); +} + +bool ReferenceSampleTree::operator==(const ReferenceSampleTree &other) const { + return repetitions == other.repetitions && + prefix_bits == other.prefix_bits && + suffix_children == other.suffix_children; +} +bool ReferenceSampleTree::operator!=(const ReferenceSampleTree &other) const { + return !(*this == other); +} + +std::ostream &stim::operator<<(std::ostream &out, const ReferenceSampleTree &v) { + out << v.repetitions << "*"; + out << "("; + out << "'"; + for (auto b : v.prefix_bits) { + out << "01"[b]; + } + out << "'"; + for (const auto &child : v.suffix_children) { + out << "+"; + out << child; + } + out << ")"; + return out; +} diff --git a/src/stim/util_top/reference_sample_tree.h b/src/stim/util_top/reference_sample_tree.h new file mode 100644 index 000000000..d63477b32 --- /dev/null +++ b/src/stim/util_top/reference_sample_tree.h @@ -0,0 +1,69 @@ +#ifndef _STIM_UTIL_TOP_REFERENCE_SAMPLE_TREE_H +#define _STIM_UTIL_TOP_REFERENCE_SAMPLE_TREE_H + +#include "stim/simulators/tableau_simulator.h" + +namespace stim { + +/// A compressed tree representation of a reference sample. +struct ReferenceSampleTree { + /// Bits to repeatedly output before outputting bits for the children. + std::vector prefix_bits; + /// Compressed representations of additional bits to output after the prefix. + std::vector suffix_children; + /// The number of times to repeatedly output the prefix and suffix bits. + size_t repetitions = 0; + + /// Initializes a reference sample tree containing a reference sample for the given circuit. + static ReferenceSampleTree from_circuit_reference_sample(const Circuit &circuit); + + /// Returns a tree with the same compressed contents, but a simpler tree structure. + ReferenceSampleTree simplified() const; + + /// Determines whether the tree contains any bits at all. + bool empty() const; + + bool operator==(const ReferenceSampleTree &other) const; + bool operator!=(const ReferenceSampleTree &other) const; + std::string str() const; + + /// Computes the total size of the uncompressed bits represented by the tree. + size_t size() const; + + /// Writes the contents of the tree into the given output vector. + void decompress_into(std::vector &output) const; + + private: + void flatten_and_simplify_into(std::vector &out) const; +}; +std::ostream &operator<<(std::ostream &out, const ReferenceSampleTree &v); + +/// Helper class for computing compressed reference samples. +template +struct CompressedReferenceSampleHelper { + TableauSimulator sim; + + CompressedReferenceSampleHelper(TableauSimulator sim) : sim(sim) { + } + + /// Processes a loop with no top-level folding. + /// + /// Loops containing within the body of this loop (or circuit body) may + /// still be compressed. Only the top-level loop is not folded. + ReferenceSampleTree do_loop_with_no_folding( + const Circuit &loop, + uint64_t reps); + + /// Runs tortoise-and-hare analysis of the loop while simulating its + /// reference sample, in order to attempt to return a compressed + /// representation. + ReferenceSampleTree do_loop_with_tortoise_hare_folding( + const Circuit &loop, + uint64_t reps); +}; + +} // namespace stim + +#include "stim/util_top/reference_sample_tree.inl" + +#endif diff --git a/src/stim/util_top/reference_sample_tree.inl b/src/stim/util_top/reference_sample_tree.inl new file mode 100644 index 000000000..e7bc38c67 --- /dev/null +++ b/src/stim/util_top/reference_sample_tree.inl @@ -0,0 +1,115 @@ +#include "stim/util_top/reference_sample_tree.h" + +namespace stim { + +template +ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_no_folding(const Circuit &loop, uint64_t reps) { + ReferenceSampleTree result; + result.repetitions = 1; + size_t start_size = sim.measurement_record.storage.size(); + + auto flush_recorded_into_result = [&]() { + size_t end_size = sim.measurement_record.storage.size(); + if (end_size > start_size) { + result.suffix_children.push_back({}); + auto &child = result.suffix_children.back(); + child.repetitions = 1; + child.prefix_bits.insert( + child.prefix_bits.end(), + sim.measurement_record.storage.begin() + start_size, + sim.measurement_record.storage.begin() + end_size); + } + start_size = end_size; + }; + + for (size_t k = 0; k < reps; k++) { + for (const auto &inst : loop.operations) { + if (inst.gate_type == GateType::REPEAT) { + uint64_t repeats = inst.repeat_block_rep_count(); + const auto& block = inst.repeat_block_body(loop); + flush_recorded_into_result(); + result.suffix_children.push_back(do_loop_with_tortoise_hare_folding(block, repeats)); + start_size = sim.measurement_record.storage.size(); + } else { + sim.do_gate(inst); + } + } + } + + flush_recorded_into_result(); + return result; +} + +template +ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_hare_folding(const Circuit &loop, uint64_t reps) { + if (reps < 10) { + return do_loop_with_no_folding(loop, reps); + } + + ReferenceSampleTree result; + result.repetitions = 1; + + CompressedReferenceSampleHelper tortoise(sim); + CompressedReferenceSampleHelper hare(std::move(sim)); + uint64_t tortoise_steps = 0; + uint64_t hare_steps = 0; + while (hare_steps < reps) { + hare_steps++; + result.suffix_children.push_back(hare.do_loop_with_no_folding(loop, 1)); + assert(result.suffix_children.size() == hare_steps); + + if (hare_steps < 10) { + // Start with cheap equality checks that can have false negatives. + if (tortoise.sim.inv_state == hare.sim.inv_state) { + break; + } + } else { + // For higher repetition counts, transition to more expensive equality checks. + if (tortoise.sim.canonical_stabilizers() == hare.sim.canonical_stabilizers()) { + break; + } + } + + // Tortoise advances half as quickly. + if (hare_steps & 1) { + tortoise_steps++; + tortoise.do_loop_with_no_folding(loop, 1); + } + } + + sim = std::move(hare.sim); + + if (hare_steps == reps) { + // No loop found. + return result; + } + + // Move the loop steps out of the hare and into a loop node. + ReferenceSampleTree loop_contents; + uint64_t period = hare_steps - tortoise_steps; + size_t period_steps_left = (reps - hare_steps) / period; + for (size_t k = tortoise_steps; k < hare_steps; k++) { + loop_contents.suffix_children.push_back(std::move(result.suffix_children[k])); + } + result.suffix_children.resize(tortoise_steps); + + // Add any remaining measurement data into the sim's measurement record. + loop_contents.repetitions = 1; + sim.measurement_record.discard_results_past_max_lookback(); + for (size_t k = 0; k < period_steps_left && sim.measurement_record.storage.size() < sim.measurement_record.max_lookback * 2; k++) { + loop_contents.decompress_into(sim.measurement_record.storage); + } + sim.measurement_record.discard_results_past_max_lookback(); + + // Add the loop node to the output data. + loop_contents.repetitions = period_steps_left + 1; + result.suffix_children.push_back(std::move(loop_contents)); + hare_steps += period * period_steps_left; + + // Process any iterations remaining in the loop. + result.suffix_children.push_back(do_loop_with_no_folding(loop, reps - hare_steps)); + + return result; +} + +} // namespace stim diff --git a/src/stim/util_top/reference_sample_tree.perf.cc b/src/stim/util_top/reference_sample_tree.perf.cc new file mode 100644 index 000000000..ce43b92c9 --- /dev/null +++ b/src/stim/util_top/reference_sample_tree.perf.cc @@ -0,0 +1,23 @@ +#include "stim/util_top/reference_sample_tree.h" + +#include "stim/gen/circuit_gen_params.h" +#include "stim/gen/gen_surface_code.h" +#include "stim/perf.perf.h" + +using namespace stim; + +BENCHMARK(reference_sample_tree_surface_code_d31_r1000000000) { + CircuitGenParameters params(1000000000, 31, "rotated_memory_x"); + auto circuit = generate_surface_code_circuit(params).circuit; + simd_bits ref(0); + auto total = 0; + benchmark_go([&]() { + auto result = ReferenceSampleTree::from_circuit_reference_sample(circuit); + total += result.empty(); + }) + .goal_millis(25) + .show_rate("Samples", circuit.count_measurements()); + if (total) { + std::cerr << "data dependence"; + } +} diff --git a/src/stim/util_top/reference_sample_tree.test.cc b/src/stim/util_top/reference_sample_tree.test.cc new file mode 100644 index 000000000..20a7a2359 --- /dev/null +++ b/src/stim/util_top/reference_sample_tree.test.cc @@ -0,0 +1,216 @@ +#include "stim/util_top/reference_sample_tree.h" + +#include "gtest/gtest.h" + +#include "stim/gen/gen_surface_code.h" + +using namespace stim; + +TEST(ReferenceSampleTree, equality) { + ReferenceSampleTree empty1{ + .prefix_bits={}, + .suffix_children={}, + .repetitions=0, + }; + ReferenceSampleTree empty2; + ASSERT_EQ(empty1, empty2); + + ASSERT_FALSE(empty1 != empty2); + ASSERT_NE(empty1, (ReferenceSampleTree{.prefix_bits={},.suffix_children{},.repetitions=1})); + ASSERT_NE(empty1, (ReferenceSampleTree{.prefix_bits={0},.suffix_children{},.repetitions=0})); + ASSERT_NE(empty1, (ReferenceSampleTree{.prefix_bits={},.suffix_children{{}},.repetitions=0})); +} + +TEST(ReferenceSampleTree, str) { + ASSERT_EQ((ReferenceSampleTree{ + .prefix_bits={}, + .suffix_children={}, + .repetitions=0, + }.str()), "0*('')"); + + ASSERT_EQ((ReferenceSampleTree{ + .prefix_bits={1, 1, 0, 1}, + .suffix_children={}, + .repetitions=0, + }.str()), "0*('1101')"); + + ASSERT_EQ((ReferenceSampleTree{ + .prefix_bits={1, 1, 0, 1}, + .suffix_children={}, + .repetitions=2, + }.str()), "2*('1101')"); + + ASSERT_EQ((ReferenceSampleTree{ + .prefix_bits={1, 1, 0, 1}, + .suffix_children={ + ReferenceSampleTree{ + .prefix_bits={1}, + .suffix_children={}, + .repetitions=5, + } + }, + .repetitions=2, + }.str()), "2*('1101'+5*('1'))"); +} + +TEST(ReferenceSampleTree, simplified) { + ReferenceSampleTree raw{ + .prefix_bits={}, + .suffix_children={ + ReferenceSampleTree{ + .prefix_bits={}, + .suffix_children={}, + .repetitions=1, + }, + ReferenceSampleTree{ + .prefix_bits={1, 0, 1}, + .suffix_children={{}}, + .repetitions=0, + }, + ReferenceSampleTree{ + .prefix_bits={1, 1, 1}, + .suffix_children={}, + .repetitions=2, + }, + }, + .repetitions=3, + }; + ASSERT_EQ(raw.simplified().str(), "6*('111')"); +} + +TEST(ReferenceSampleTree, decompress_into) { + std::vector result; + ReferenceSampleTree{ + .prefix_bits={1, 1, 0, 1}, + .suffix_children={ + ReferenceSampleTree{ + .prefix_bits={1}, + .suffix_children={}, + .repetitions=5, + } + }, + .repetitions=2, + }.decompress_into(result); + ASSERT_EQ(result, (std::vector{1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1})); + + result.clear(); + ReferenceSampleTree{ + .prefix_bits={1, 1, 0, 1}, + .suffix_children={ + ReferenceSampleTree{ + .prefix_bits={1, 0, 1}, + .suffix_children={}, + .repetitions=8, + }, + ReferenceSampleTree{ + .prefix_bits={0, 0}, + .suffix_children={}, + .repetitions=1, + }, + }, + .repetitions=1, + }.decompress_into(result); + ASSERT_EQ(result, (std::vector{1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0})); +} + +TEST(ReferenceSampleTree, simple_circuit) { + Circuit circuit(R"CIRCUIT( + M 0 + X 0 + M 0 + )CIRCUIT"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.str(), "1*('01')"); +} + +TEST(ReferenceSampleTree, simple_loop) { + Circuit circuit(R"CIRCUIT( + REPEAT 50 { + M 0 + X 0 + M 0 + } + )CIRCUIT"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.str(), "1*('01'+24*('1001')+1*('10'))"); +} + +TEST(ReferenceSampleTree, period4_loop) { + Circuit circuit(R"CIRCUIT( + M 0 + X 0 + M 0 + REPEAT 50 { + CX 0 1 1 2 2 3 + M 0 1 2 3 + } + X 0 + M 0 + X 2 + M 2 2 2 2 2 + )CIRCUIT"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.size(), circuit.count_measurements()); + ASSERT_EQ(ref.str(), "1*('01111110101100'+11*('1000111110101100')+1*('100011111010000000'))"); +} + +TEST(ReferenceSampleTree, nested_loops) { + Circuit circuit(R"CIRCUIT( + REPEAT 100 { + REPEAT 100 { + M 0 + X 0 + M 0 + } + REPEAT 200 { + M 0 + } + X 1 + CX 1 0 + } + )CIRCUIT"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.str(), "1*('01'+49*('1001')+1*('10')+200*('0')+1*('10')+49*('0110')+1*('01')+200*('1')+1*('10')+49*('0110')+1*('01')+200*('1')+24*('01'+49*('1001')+1*('10')+200*('0')+1*('01')+49*('1001')+1*('10')+200*('0')+1*('10')+49*('0110')+1*('01')+200*('1')+1*('10')+49*('0110')+1*('01')+200*('1'))+1*('01')+49*('1001')+1*('10')+200*('0'))"); + + std::vector ref_uncompressed; + ref.decompress_into(ref_uncompressed); + simd_bits ref_flat(ref_uncompressed.size()); + for (size_t k = 0; k < ref_uncompressed.size(); k++) { + ref_flat[k] = ref_uncompressed[k]; + } + auto ref2 = TableauSimulator::reference_sample_circuit(circuit); + ASSERT_EQ(ref_flat, ref2); +} + +TEST(ReferenceSampleTree, surface_code) { + CircuitGenParameters params(10000, 5, "rotated_memory_x"); + auto circuit = generate_surface_code_circuit(params).circuit; + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.str(), "1*('000000000000000000000000000000000000000000000000'+4999*('000000000000000000000000000000000000000000000000')+1*('0000000000000000000000000'))"); +} + +TEST(ReferenceSampleTree, surface_code_with_pauli) { + CircuitGenParameters params(10000, 3, "rotated_memory_x"); + auto circuit = generate_surface_code_circuit(params).circuit; + circuit.blocks[0].append_from_text("X 10 11 12 13"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.str(), "1*('0000000000000000'+4999*('0110000000110000')+1*('000000000'))"); +} + +TEST(ReferenceSampleTree, surface_code_with_pauli_vs_normal_reference_sample) { + CircuitGenParameters params(20, 3, "rotated_memory_x"); + auto circuit = generate_surface_code_circuit(params).circuit; + circuit.blocks[0].append_from_text("X 10 11 12 13"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.size(), circuit.count_measurements()); + + std::vector ref_uncompressed; + ref.decompress_into(ref_uncompressed); + simd_bits ref_flat(ref_uncompressed.size()); + for (size_t k = 0; k < ref_uncompressed.size(); k++) { + ref_flat[k] = ref_uncompressed[k]; + } + + auto ref2 = TableauSimulator::reference_sample_circuit(circuit); + ASSERT_EQ(ref_flat, ref2); +} From e6e6e5078c22806dc6d2b40eba7b59fb88102677 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 20 May 2024 02:15:32 -0700 Subject: [PATCH 2/5] Fix feedback, factorize small sub-periods, align early instead of late --- src/stim/util_top/reference_sample_tree.cc | 29 +++++- src/stim/util_top/reference_sample_tree.h | 7 ++ src/stim/util_top/reference_sample_tree.inl | 88 ++++++++++++++----- .../util_top/reference_sample_tree.test.cc | 49 ++++++++--- 4 files changed, 137 insertions(+), 36 deletions(-) diff --git a/src/stim/util_top/reference_sample_tree.cc b/src/stim/util_top/reference_sample_tree.cc index 2eca171af..c1a6d1170 100644 --- a/src/stim/util_top/reference_sample_tree.cc +++ b/src/stim/util_top/reference_sample_tree.cc @@ -37,6 +37,13 @@ void ReferenceSampleTree::flatten_and_simplify_into(std::vector flat; flatten_and_simplify_into(flat); @@ -82,11 +107,13 @@ ReferenceSampleTree ReferenceSampleTree::simplified() const { ReferenceSampleTree result; result.repetitions = 1; + + // Take payload from first child. if (flat[0].repetitions == 1 && flat[0].suffix_children.empty()) { - // Take payload from first child. result = std::move(flat[0]); flat.erase(flat.begin()); } + result.suffix_children = std::move(flat); return result; } diff --git a/src/stim/util_top/reference_sample_tree.h b/src/stim/util_top/reference_sample_tree.h index d63477b32..2c1533b1d 100644 --- a/src/stim/util_top/reference_sample_tree.h +++ b/src/stim/util_top/reference_sample_tree.h @@ -33,6 +33,8 @@ struct ReferenceSampleTree { /// Writes the contents of the tree into the given output vector. void decompress_into(std::vector &output) const; + void try_factorize(size_t period_factor); + private: void flatten_and_simplify_into(std::vector &out) const; }; @@ -60,6 +62,11 @@ struct CompressedReferenceSampleHelper { ReferenceSampleTree do_loop_with_tortoise_hare_folding( const Circuit &loop, uint64_t reps); + + bool in_same_recent_state_as( + const CompressedReferenceSampleHelper &other, + uint64_t max_record_lookback, + bool allow_false_negative) const; }; } // namespace stim diff --git a/src/stim/util_top/reference_sample_tree.inl b/src/stim/util_top/reference_sample_tree.inl index e7bc38c67..ff677c08d 100644 --- a/src/stim/util_top/reference_sample_tree.inl +++ b/src/stim/util_top/reference_sample_tree.inl @@ -40,6 +40,26 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_no_folding( return result; } +static uint64_t max_feedback_lookback_in_loop(const Circuit &loop) { + uint64_t furthest_lookback = 0; + for (const auto &inst : loop.operations) { + if (inst.gate_type == GateType::REPEAT) { + furthest_lookback = std::max(furthest_lookback, max_feedback_lookback_in_loop(inst.repeat_block_body(loop))); + } else { + auto f = GATE_DATA[inst.gate_type].flags; + if ((f & GateFlags::GATE_CAN_TARGET_BITS) && (f & GateFlags::GATE_TARGETS_PAIRS)) { + // Feedback-capable operation. Check for any measurement record targets. + for (auto t : inst.targets) { + if (t.is_measurement_record_target()) { + furthest_lookback = std::max(furthest_lookback, (uint64_t)-t.rec_offset()); + } + } + } + } + } + return furthest_lookback; +} + template ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_hare_folding(const Circuit &loop, uint64_t reps) { if (reps < 10) { @@ -51,6 +71,7 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha CompressedReferenceSampleHelper tortoise(sim); CompressedReferenceSampleHelper hare(std::move(sim)); + uint64_t max_feedback_lookback = max_feedback_lookback_in_loop(loop); uint64_t tortoise_steps = 0; uint64_t hare_steps = 0; while (hare_steps < reps) { @@ -58,16 +79,8 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha result.suffix_children.push_back(hare.do_loop_with_no_folding(loop, 1)); assert(result.suffix_children.size() == hare_steps); - if (hare_steps < 10) { - // Start with cheap equality checks that can have false negatives. - if (tortoise.sim.inv_state == hare.sim.inv_state) { - break; - } - } else { - // For higher repetition counts, transition to more expensive equality checks. - if (tortoise.sim.canonical_stabilizers() == hare.sim.canonical_stabilizers()) { - break; - } + if (tortoise.in_same_recent_state_as(hare, max_feedback_lookback, hare_steps < 10)) { + break; } // Tortoise advances half as quickly. @@ -77,23 +90,32 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha } } - sim = std::move(hare.sim); - if (hare_steps == reps) { // No loop found. + sim = std::move(hare.sim); return result; } - // Move the loop steps out of the hare and into a loop node. - ReferenceSampleTree loop_contents; + // Advance until the remaining iterations are a multiple of the period. + assert(result.suffix_children.size() == hare_steps); uint64_t period = hare_steps - tortoise_steps; size_t period_steps_left = (reps - hare_steps) / period; - for (size_t k = tortoise_steps; k < hare_steps; k++) { + while ((reps - hare_steps) % period) { + result.suffix_children.push_back(hare.do_loop_with_no_folding(loop, 1)); + hare_steps += 1; + } + assert(hare_steps + period_steps_left * period == reps); + assert(hare_steps >= period); + sim = std::move(hare.sim); + + // Move the periodic measurements out of the hare's tail, into a loop node. + ReferenceSampleTree loop_contents; + for (size_t k = hare_steps - period; k < hare_steps; k++) { loop_contents.suffix_children.push_back(std::move(result.suffix_children[k])); } - result.suffix_children.resize(tortoise_steps); + result.suffix_children.resize(hare_steps - period); - // Add any remaining measurement data into the sim's measurement record. + // Add skipped iterations' measurement data into the sim's measurement record. loop_contents.repetitions = 1; sim.measurement_record.discard_results_past_max_lookback(); for (size_t k = 0; k < period_steps_left && sim.measurement_record.storage.size() < sim.measurement_record.max_lookback * 2; k++) { @@ -103,13 +125,37 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha // Add the loop node to the output data. loop_contents.repetitions = period_steps_left + 1; + loop_contents.try_factorize(2); + loop_contents.try_factorize(3); + loop_contents.try_factorize(5); result.suffix_children.push_back(std::move(loop_contents)); - hare_steps += period * period_steps_left; - - // Process any iterations remaining in the loop. - result.suffix_children.push_back(do_loop_with_no_folding(loop, reps - hare_steps)); return result; } +template +bool CompressedReferenceSampleHelper::in_same_recent_state_as( + const CompressedReferenceSampleHelper &other, + uint64_t max_record_lookback, + bool allow_false_negative) const { + const auto &s1 = sim.measurement_record.storage; + const auto &s2 = other.sim.measurement_record.storage; + + // Check that recent measurements gave identical results. + if (s1.size() < max_record_lookback || s2.size() < max_record_lookback) { + return false; + } + for (size_t k = 0; k < max_record_lookback; k++) { + if (s1[s1.size() - k - 1] != s2[s2.size() - k - 1]) { + return false; + } + } + + // Check that quantum states are identical. + if (allow_false_negative) { + return sim.inv_state == other.sim.inv_state; + } + return sim.canonical_stabilizers() == other.sim.canonical_stabilizers(); +} + } // namespace stim diff --git a/src/stim/util_top/reference_sample_tree.test.cc b/src/stim/util_top/reference_sample_tree.test.cc index 20a7a2359..8c3b60b6f 100644 --- a/src/stim/util_top/reference_sample_tree.test.cc +++ b/src/stim/util_top/reference_sample_tree.test.cc @@ -6,6 +6,17 @@ using namespace stim; +void expect_tree_matches_normal_reference_sample_of(const ReferenceSampleTree &tree, const Circuit &circuit) { + std::vector decompressed; + tree.decompress_into(decompressed); + simd_bits actual(decompressed.size()); + for (size_t k = 0; k < decompressed.size(); k++) { + actual[k] = decompressed[k]; + } + auto expected = TableauSimulator::reference_sample_circuit(circuit); + EXPECT_EQ(actual, expected); +} + TEST(ReferenceSampleTree, equality) { ReferenceSampleTree empty1{ .prefix_bits={}, @@ -120,6 +131,7 @@ TEST(ReferenceSampleTree, simple_circuit) { M 0 )CIRCUIT"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + expect_tree_matches_normal_reference_sample_of(ref, circuit); ASSERT_EQ(ref.str(), "1*('01')"); } @@ -132,7 +144,8 @@ TEST(ReferenceSampleTree, simple_loop) { } )CIRCUIT"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); - ASSERT_EQ(ref.str(), "1*('01'+24*('1001')+1*('10'))"); + expect_tree_matches_normal_reference_sample_of(ref, circuit); + ASSERT_EQ(ref.str(), "25*('0110')"); } TEST(ReferenceSampleTree, period4_loop) { @@ -148,10 +161,26 @@ TEST(ReferenceSampleTree, period4_loop) { M 0 X 2 M 2 2 2 2 2 + MPAD 1 0 1 0 1 1 + )CIRCUIT"); + auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); + ASSERT_EQ(ref.size(), circuit.count_measurements()); + expect_tree_matches_normal_reference_sample_of(ref, circuit); + ASSERT_EQ(ref.str(), "1*('01111110101100100011111010'+11*('1100100011111010')+1*('000000101011'))"); +} + +TEST(ReferenceSampleTree, feedback) { + Circuit circuit(R"CIRCUIT( + MPAD 0 0 1 0 + REPEAT 200 { + CX rec[-4] 1 + M 1 + } )CIRCUIT"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); ASSERT_EQ(ref.size(), circuit.count_measurements()); - ASSERT_EQ(ref.str(), "1*('01111110101100'+11*('1000111110101100')+1*('100011111010000000'))"); + expect_tree_matches_normal_reference_sample_of(ref, circuit); + ASSERT_EQ(ref.str(), "1*('0010'+2*('0')+4*('1')+1*('01011001000111')+12*('101011001000111'))"); } TEST(ReferenceSampleTree, nested_loops) { @@ -170,23 +199,15 @@ TEST(ReferenceSampleTree, nested_loops) { } )CIRCUIT"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); - ASSERT_EQ(ref.str(), "1*('01'+49*('1001')+1*('10')+200*('0')+1*('10')+49*('0110')+1*('01')+200*('1')+1*('10')+49*('0110')+1*('01')+200*('1')+24*('01'+49*('1001')+1*('10')+200*('0')+1*('01')+49*('1001')+1*('10')+200*('0')+1*('10')+49*('0110')+1*('01')+200*('1')+1*('10')+49*('0110')+1*('01')+200*('1'))+1*('01')+49*('1001')+1*('10')+200*('0'))"); - - std::vector ref_uncompressed; - ref.decompress_into(ref_uncompressed); - simd_bits ref_flat(ref_uncompressed.size()); - for (size_t k = 0; k < ref_uncompressed.size(); k++) { - ref_flat[k] = ref_uncompressed[k]; - } - auto ref2 = TableauSimulator::reference_sample_circuit(circuit); - ASSERT_EQ(ref_flat, ref2); + expect_tree_matches_normal_reference_sample_of(ref, circuit); + ASSERT_EQ(ref.str(), "1*(''+50*('0110')+200*('0')+50*('1001')+200*('1')+50*('1001')+200*('1')+50*('0110')+200*('0')+24*(''+50*('0110')+200*('0')+50*('1001')+200*('1')+50*('1001')+200*('1')+50*('0110')+200*('0')))"); } TEST(ReferenceSampleTree, surface_code) { CircuitGenParameters params(10000, 5, "rotated_memory_x"); auto circuit = generate_surface_code_circuit(params).circuit; auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); - ASSERT_EQ(ref.str(), "1*('000000000000000000000000000000000000000000000000'+4999*('000000000000000000000000000000000000000000000000')+1*('0000000000000000000000000'))"); + ASSERT_EQ(ref.str(), "1*(''+10000*('000000000000000000000000')+1*('0000000000000000000000000'))"); } TEST(ReferenceSampleTree, surface_code_with_pauli) { @@ -194,7 +215,7 @@ TEST(ReferenceSampleTree, surface_code_with_pauli) { auto circuit = generate_surface_code_circuit(params).circuit; circuit.blocks[0].append_from_text("X 10 11 12 13"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); - ASSERT_EQ(ref.str(), "1*('0000000000000000'+4999*('0110000000110000')+1*('000000000'))"); + ASSERT_EQ(ref.str(), "1*(''+2*('00000000')+4999*('0110000000110000')+1*('000000000'))"); } TEST(ReferenceSampleTree, surface_code_with_pauli_vs_normal_reference_sample) { From e13d3f0c3b2ec358adcec183b0f3ad2cd05b4735 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 20 May 2024 17:12:42 -0700 Subject: [PATCH 3/5] Comments --- src/stim/util_top/reference_sample_tree.cc | 68 +++++++++++++------ src/stim/util_top/reference_sample_tree.h | 20 ++++-- src/stim/util_top/reference_sample_tree.inl | 23 +------ .../util_top/reference_sample_tree.perf.cc | 33 ++++++++- .../util_top/reference_sample_tree.test.cc | 40 +++++++++++ 5 files changed, 136 insertions(+), 48 deletions(-) diff --git a/src/stim/util_top/reference_sample_tree.cc b/src/stim/util_top/reference_sample_tree.cc index c1a6d1170..c6c0f4550 100644 --- a/src/stim/util_top/reference_sample_tree.cc +++ b/src/stim/util_top/reference_sample_tree.cc @@ -22,6 +22,7 @@ void ReferenceSampleTree::flatten_and_simplify_into(std::vector flattened; if (!prefix_bits.empty()) { flattened.push_back(ReferenceSampleTree{ @@ -35,49 +36,76 @@ void ReferenceSampleTree::flatten_and_simplify_into(std::vector fused; + if (!flattened.empty()) { + fused.push_back(std::move(flattened[0])); + } + for (size_t k = 1; k < flattened.size(); k++) { + auto &dst = fused.back(); + auto &src = flattened[k]; + // Combine children with identical contents by adding their rep counts. - while (k + 1 < f.size() && f[k].prefix_bits == f[k + 1].prefix_bits && f[k].suffix_children == f[k + 1].suffix_children) { - f[k].repetitions += f[k + 1].repetitions; - f.erase(f.begin() + k + 1); - } + if (dst.prefix_bits == src.prefix_bits && dst.suffix_children == src.suffix_children) { + dst.repetitions += src.repetitions; // Fuse children with unrepeated contents if they can be fused. - while (k + 1 < f.size() && f[k].repetitions == 1 && f[k].suffix_children.empty() && f[k + 1].repetitions == 1) { - f[k].suffix_children = std::move(f[k + 1].suffix_children); - f[k].prefix_bits.insert(f[k].prefix_bits.end(), f[k + 1].prefix_bits.begin(), f[k + 1].prefix_bits.end()); - f.erase(f.begin() + k + 1); + } else if (src.repetitions == 1 && dst.repetitions == 1 && dst.suffix_children.empty()) { + dst.suffix_children = std::move(src.suffix_children); + dst.prefix_bits.insert(dst.prefix_bits.end(), src.prefix_bits.begin(), src.prefix_bits.end()); + + } else { + fused.push_back(std::move(src)); } } if (repetitions == 1) { // Un-nest all the children. - for (auto &e : flattened) { + for (auto &e : fused) { out.push_back(e); } - } else if (flattened.size() == 1) { + } else if (fused.size() == 1) { // Merge with single child. - flattened[0].repetitions *= repetitions; - out.push_back(std::move(flattened[0])); - } else if (flattened.empty()) { + fused[0].repetitions *= repetitions; + out.push_back(std::move(fused[0])); + } else if (fused.empty()) { // Nothing to report. - } else if (flattened[0].suffix_children.empty() && flattened[0].repetitions == 1) { + } else if (fused[0].suffix_children.empty() && fused[0].repetitions == 1) { // Take payload from first child. - auto result = std::move(flattened[0]); - flattened.erase(flattened.begin()); + ReferenceSampleTree result = std::move(fused[0]); + fused.erase(fused.begin()); result.repetitions = repetitions; - result.suffix_children = std::move(flattened); + result.suffix_children = std::move(fused); out.push_back(std::move(result)); } else { out.push_back(ReferenceSampleTree{ .prefix_bits={}, - .suffix_children=std::move(flattened), + .suffix_children=std::move(fused), .repetitions=repetitions, }); } } +/// Finds how far back feedback operations ever look, within the loop. +uint64_t stim::max_feedback_lookback_in_loop(const Circuit &loop) { + uint64_t furthest_lookback = 0; + for (const auto &inst : loop.operations) { + if (inst.gate_type == GateType::REPEAT) { + furthest_lookback = std::max(furthest_lookback, max_feedback_lookback_in_loop(inst.repeat_block_body(loop))); + } else { + auto f = GATE_DATA[inst.gate_type].flags; + if ((f & GateFlags::GATE_CAN_TARGET_BITS) && (f & GateFlags::GATE_TARGETS_PAIRS)) { + // Feedback-capable operation. Check for any measurement record targets. + for (auto t : inst.targets) { + if (t.is_measurement_record_target()) { + furthest_lookback = std::max(furthest_lookback, (uint64_t)-t.rec_offset()); + } + } + } + } + } + return furthest_lookback; +} + void ReferenceSampleTree::try_factorize(size_t period_factor) { if (prefix_bits.size() != 0 || suffix_children.size() % period_factor != 0) { return; diff --git a/src/stim/util_top/reference_sample_tree.h b/src/stim/util_top/reference_sample_tree.h index 2c1533b1d..2b439b611 100644 --- a/src/stim/util_top/reference_sample_tree.h +++ b/src/stim/util_top/reference_sample_tree.h @@ -7,11 +7,11 @@ namespace stim { /// A compressed tree representation of a reference sample. struct ReferenceSampleTree { - /// Bits to repeatedly output before outputting bits for the children. + /// Raw bits to output before bits from the children. std::vector prefix_bits; /// Compressed representations of additional bits to output after the prefix. std::vector suffix_children; - /// The number of times to repeatedly output the prefix and suffix bits. + /// The number of times to repeatedly output the prefix+suffix bits. size_t repetitions = 0; /// Initializes a reference sample tree containing a reference sample for the given circuit. @@ -20,22 +20,30 @@ struct ReferenceSampleTree { /// Returns a tree with the same compressed contents, but a simpler tree structure. ReferenceSampleTree simplified() const; - /// Determines whether the tree contains any bits at all. - bool empty() const; - + /// Checks if two trees are exactly the same, including structure (not just uncompressed contents). bool operator==(const ReferenceSampleTree &other) const; + /// Checks if two trees are not exactly the same, including structure (not just uncompressed contents). bool operator!=(const ReferenceSampleTree &other) const; + /// Returns a simple description of the tree's structure, like "5*('101'+6*('11'))". std::string str() const; + /// Determines whether the tree contains any bits at all. + bool empty() const; /// Computes the total size of the uncompressed bits represented by the tree. size_t size() const; /// Writes the contents of the tree into the given output vector. void decompress_into(std::vector &output) const; + /// Folds redundant children into the repetition count, if they repeat this many times. + /// + /// For example, if the tree's children are [A, B, C, A, B, C] and the tree has no + /// prefix, then `try_factorize(2)` will reduce the children to [A, B, C] and double + /// the repetition count. void try_factorize(size_t period_factor); private: + /// Helper method for `simplified`. void flatten_and_simplify_into(std::vector &out) const; }; std::ostream &operator<<(std::ostream &out, const ReferenceSampleTree &v); @@ -69,6 +77,8 @@ struct CompressedReferenceSampleHelper { bool allow_false_negative) const; }; +uint64_t max_feedback_lookback_in_loop(const Circuit &loop); + } // namespace stim #include "stim/util_top/reference_sample_tree.inl" diff --git a/src/stim/util_top/reference_sample_tree.inl b/src/stim/util_top/reference_sample_tree.inl index ff677c08d..68ff08241 100644 --- a/src/stim/util_top/reference_sample_tree.inl +++ b/src/stim/util_top/reference_sample_tree.inl @@ -40,29 +40,10 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_no_folding( return result; } -static uint64_t max_feedback_lookback_in_loop(const Circuit &loop) { - uint64_t furthest_lookback = 0; - for (const auto &inst : loop.operations) { - if (inst.gate_type == GateType::REPEAT) { - furthest_lookback = std::max(furthest_lookback, max_feedback_lookback_in_loop(inst.repeat_block_body(loop))); - } else { - auto f = GATE_DATA[inst.gate_type].flags; - if ((f & GateFlags::GATE_CAN_TARGET_BITS) && (f & GateFlags::GATE_TARGETS_PAIRS)) { - // Feedback-capable operation. Check for any measurement record targets. - for (auto t : inst.targets) { - if (t.is_measurement_record_target()) { - furthest_lookback = std::max(furthest_lookback, (uint64_t)-t.rec_offset()); - } - } - } - } - } - return furthest_lookback; -} - template ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_hare_folding(const Circuit &loop, uint64_t reps) { if (reps < 10) { + // Probably not worth the overhead of tortoise-and-hare. Just run it raw. return do_loop_with_no_folding(loop, reps); } @@ -96,7 +77,7 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha return result; } - // Advance until the remaining iterations are a multiple of the period. + // Run more loop iterations until the remaining iterations are a multiple of the found period. assert(result.suffix_children.size() == hare_steps); uint64_t period = hare_steps - tortoise_steps; size_t period_steps_left = (reps - hare_steps) / period; diff --git a/src/stim/util_top/reference_sample_tree.perf.cc b/src/stim/util_top/reference_sample_tree.perf.cc index ce43b92c9..c245f1574 100644 --- a/src/stim/util_top/reference_sample_tree.perf.cc +++ b/src/stim/util_top/reference_sample_tree.perf.cc @@ -15,8 +15,37 @@ BENCHMARK(reference_sample_tree_surface_code_d31_r1000000000) { auto result = ReferenceSampleTree::from_circuit_reference_sample(circuit); total += result.empty(); }) - .goal_millis(25) - .show_rate("Samples", circuit.count_measurements()); + .goal_millis(25); + if (total) { + std::cerr << "data dependence"; + } +} + +BENCHMARK(reference_sample_tree_nested_circuit) { + Circuit circuit(R"CIRCUIT( + M 0 + REPEAT 100000 { + REPEAT 100000 { + REPEAT 100000 { + X 0 + M 0 + } + X 0 + M 0 + } + X 0 + M 0 + } + X 0 + M 0 + )CIRCUIT"); + simd_bits ref(0); + auto total = 0; + benchmark_go([&]() { + auto result = ReferenceSampleTree::from_circuit_reference_sample(circuit); + total += result.empty(); + }) + .goal_micros(230); if (total) { std::cerr << "data dependence"; } diff --git a/src/stim/util_top/reference_sample_tree.test.cc b/src/stim/util_top/reference_sample_tree.test.cc index 8c3b60b6f..d6b7ec2cb 100644 --- a/src/stim/util_top/reference_sample_tree.test.cc +++ b/src/stim/util_top/reference_sample_tree.test.cc @@ -183,6 +183,46 @@ TEST(ReferenceSampleTree, feedback) { ASSERT_EQ(ref.str(), "1*('0010'+2*('0')+4*('1')+1*('01011001000111')+12*('101011001000111'))"); } +TEST(max_feedback_lookback_in_loop, simple) { + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit()), 0); + + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit(R"CIRCUIT( + REPEAT 100 { + REPEAT 100 { + M 0 + X 0 + M 0 + } + REPEAT 200 { + M 0 + DETECTOR rec[-1] + } + X 1 + CX 1 0 + } + )CIRCUIT")), 0); + + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit(R"CIRCUIT( + CX rec[-1] 0 + )CIRCUIT")), 1); + + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit(R"CIRCUIT( + CZ 0 rec[-2] + )CIRCUIT")), 2); + + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit(R"CIRCUIT( + CZ 0 rec[-2] + CY 0 rec[-3] + )CIRCUIT")), 3); + + ASSERT_EQ(max_feedback_lookback_in_loop(Circuit(R"CIRCUIT( + CZ 0 rec[-2] + REPEAT 100 { + CX rec[-5] 0 + } + )CIRCUIT")), 5); +} + TEST(ReferenceSampleTree, nested_loops) { Circuit circuit(R"CIRCUIT( REPEAT 100 { From 166212cb8268982ce1381f9b0a9623910393c665 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Tue, 21 May 2024 14:10:32 -0700 Subject: [PATCH 4/5] minor comments --- src/stim/util_top/reference_sample_tree.cc | 2 +- src/stim/util_top/reference_sample_tree.inl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stim/util_top/reference_sample_tree.cc b/src/stim/util_top/reference_sample_tree.cc index c6c0f4550..bb8764aac 100644 --- a/src/stim/util_top/reference_sample_tree.cc +++ b/src/stim/util_top/reference_sample_tree.cc @@ -130,7 +130,7 @@ ReferenceSampleTree ReferenceSampleTree::simplified() const { if (flat.empty()) { return ReferenceSampleTree(); } else if (flat.size() == 1) { - return flat[0]; + return std::move(flat[0]); } ReferenceSampleTree result; diff --git a/src/stim/util_top/reference_sample_tree.inl b/src/stim/util_top/reference_sample_tree.inl index 68ff08241..746920dc1 100644 --- a/src/stim/util_top/reference_sample_tree.inl +++ b/src/stim/util_top/reference_sample_tree.inl @@ -72,7 +72,7 @@ ReferenceSampleTree CompressedReferenceSampleHelper::do_loop_with_tortoise_ha } if (hare_steps == reps) { - // No loop found. + // No periodic state found before reaching the end of the loop. sim = std::move(hare.sim); return result; } From 5df52f3adf772dd3bc87b735deca847a0eee1dd3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Tue, 21 May 2024 14:11:57 -0700 Subject: [PATCH 5/5] Test typo fix --- src/stim/util_top/reference_sample_tree.test.cc | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/stim/util_top/reference_sample_tree.test.cc b/src/stim/util_top/reference_sample_tree.test.cc index d6b7ec2cb..858a8e667 100644 --- a/src/stim/util_top/reference_sample_tree.test.cc +++ b/src/stim/util_top/reference_sample_tree.test.cc @@ -264,14 +264,5 @@ TEST(ReferenceSampleTree, surface_code_with_pauli_vs_normal_reference_sample) { circuit.blocks[0].append_from_text("X 10 11 12 13"); auto ref = ReferenceSampleTree::from_circuit_reference_sample(circuit); ASSERT_EQ(ref.size(), circuit.count_measurements()); - - std::vector ref_uncompressed; - ref.decompress_into(ref_uncompressed); - simd_bits ref_flat(ref_uncompressed.size()); - for (size_t k = 0; k < ref_uncompressed.size(); k++) { - ref_flat[k] = ref_uncompressed[k]; - } - - auto ref2 = TableauSimulator::reference_sample_circuit(circuit); - ASSERT_EQ(ref_flat, ref2); + expect_tree_matches_normal_reference_sample_of(ref, circuit); }