Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add stim::ReferenceSampleTree to support loop folded reference sampling #772

Merged
merged 5 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions file_lists/perf_files
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions file_lists/source_files_no_main
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions file_lists/test_files
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/stim.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
19 changes: 19 additions & 0 deletions src/stim/io/measure_record.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,22 @@ void MeasureRecord::record_result(bool result) {
storage.push_back(result);
unwritten++;
}

void MeasureRecord::record_results(const std::vector<bool> &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;
}
}
6 changes: 6 additions & 0 deletions src/stim/io/measure_record.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool> &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
Expand Down
1 change: 1 addition & 0 deletions src/stim/simulators/tableau_simulator.perf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
207 changes: 207 additions & 0 deletions src/stim/util_top/reference_sample_tree.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#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<ReferenceSampleTree> &out) const {
if (repetitions == 0) {
return;
}

// Flatten children.
std::vector<ReferenceSampleTree> 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.
std::vector<ReferenceSampleTree> fused;
if (!flattened.empty()) {
fused.push_back(std::move(flattened[0]));
}
for (size_t k = 1; k < flattened.size(); k++) {
Strilanc marked this conversation as resolved.
Show resolved Hide resolved
auto &dst = fused.back();
auto &src = flattened[k];

// Combine children with identical contents by adding their rep counts.
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.
} 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 : fused) {
out.push_back(e);
}
} else if (fused.size() == 1) {
// Merge with single child.
fused[0].repetitions *= repetitions;
out.push_back(std::move(fused[0]));
} else if (fused.empty()) {
// Nothing to report.
} else if (fused[0].suffix_children.empty() && fused[0].repetitions == 1) {
// Take payload from first child.
ReferenceSampleTree result = std::move(fused[0]);
fused.erase(fused.begin());
result.repetitions = repetitions;
result.suffix_children = std::move(fused);
out.push_back(std::move(result));
} else {
out.push_back(ReferenceSampleTree{
.prefix_bits={},
.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;
}

// Check if contents are periodic with the factor.
size_t h = suffix_children.size() / period_factor;
for (size_t k = h; k < suffix_children.size(); k++) {
if (suffix_children[k - h] != suffix_children[k]) {
return;
}
}

// Factorize.
suffix_children.resize(h);
repetitions *= period_factor;
}

ReferenceSampleTree ReferenceSampleTree::simplified() const {
std::vector<ReferenceSampleTree> flat;
flatten_and_simplify_into(flat);
if (flat.empty()) {
return ReferenceSampleTree();
} else if (flat.size() == 1) {
return std::move(flat[0]);
}

ReferenceSampleTree result;
result.repetitions = 1;

// Take payload from first child.
if (flat[0].repetitions == 1 && flat[0].suffix_children.empty()) {
Strilanc marked this conversation as resolved.
Show resolved Hide resolved
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<bool> &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<MAX_BITWORD_WIDTH> helper(
TableauSimulator<MAX_BITWORD_WIDTH>(
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;
}
86 changes: 86 additions & 0 deletions src/stim/util_top/reference_sample_tree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#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 {
/// Raw bits to output before bits from the children.
std::vector<bool> prefix_bits;
/// Compressed representations of additional bits to output after the prefix.
std::vector<ReferenceSampleTree> suffix_children;
/// 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.
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;

/// 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<bool> &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);

Strilanc marked this conversation as resolved.
Show resolved Hide resolved
private:
/// Helper method for `simplified`.
void flatten_and_simplify_into(std::vector<ReferenceSampleTree> &out) const;
};
std::ostream &operator<<(std::ostream &out, const ReferenceSampleTree &v);

/// Helper class for computing compressed reference samples.
template <size_t W>
struct CompressedReferenceSampleHelper {
TableauSimulator<W> sim;

CompressedReferenceSampleHelper(TableauSimulator<MAX_BITWORD_WIDTH> 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);

bool in_same_recent_state_as(
const CompressedReferenceSampleHelper<W> &other,
uint64_t max_record_lookback,
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"

#endif
Loading
Loading