diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index cf691bf7..1399c1a9 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -3235,6 +3235,9 @@ def time_reversed_for_flows( # (in class stim.Circuit) def to_crumble_url( self, + *, + skip_detectors: bool = False, + mark: Optional[Dict[int, List[stim.ExplainedError]]] = None, ) -> str: """Returns a URL that opens up crumble and loads this circuit into it. @@ -3244,6 +3247,15 @@ def to_crumble_url( at https://algassert.com/crumble, which is what the URL returned by this method will point to. + Args: + skip_detectors: Defaults to False. If set to True, detectors from the + circuit aren't included in the crumble URL. This can reduce visual + clutter in crumble, and improve its performance, since it doesn't + need to indicate or track the sensitivity regions of detectors. + mark: Defaults to None (no marks). If set to a dictionary from int to + errors, such as `mark={1: circuit.shortest_graphlike_error()}`, + then the errors will be highlighted and tracked forward by crumble. + Returns: A URL that can be opened in a web browser. @@ -3255,6 +3267,16 @@ def to_crumble_url( ... S 1 ... ''').to_crumble_url() 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + + >>> circuit = stim.Circuit(''' + ... M(0.25) 0 1 2 + ... DETECTOR rec[-1] rec[-2] + ... DETECTOR rec[-2] rec[-3] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''') + >>> err = circuit.shortest_graphlike_error(canonicalize_circuit_errors=True) + >>> circuit.to_crumble_url(skip_detectors=True, mark={1: err}) + 'https://algassert.com/crumble#circuit=;TICK;MARKX(1)1;MARKX(1)2;MARKX(1)0;TICK;M(0.25)0_1_2;OI(0)rec[-1]' """ ``` @@ -4926,6 +4948,19 @@ def sample( (dets[s, m // 8] >> (m % 8)) & 1 The bit for observable `m` in shot `s` is at (obs[s, m // 8] >> (m % 8)) & 1 + + Examples: + >>> import stim + >>> c = stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... X_ERROR(1.0) 0 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''') + >>> s = c.compile_detector_sampler() + >>> s.sample(shots=1) + array([[ True]]) """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index d661f362..d2e6ab0f 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -2483,6 +2483,9 @@ class Circuit: """ def to_crumble_url( self, + *, + skip_detectors: bool = False, + mark: Optional[Dict[int, List[stim.ExplainedError]]] = None, ) -> str: """Returns a URL that opens up crumble and loads this circuit into it. @@ -2492,6 +2495,15 @@ class Circuit: at https://algassert.com/crumble, which is what the URL returned by this method will point to. + Args: + skip_detectors: Defaults to False. If set to True, detectors from the + circuit aren't included in the crumble URL. This can reduce visual + clutter in crumble, and improve its performance, since it doesn't + need to indicate or track the sensitivity regions of detectors. + mark: Defaults to None (no marks). If set to a dictionary from int to + errors, such as `mark={1: circuit.shortest_graphlike_error()}`, + then the errors will be highlighted and tracked forward by crumble. + Returns: A URL that can be opened in a web browser. @@ -2503,6 +2515,16 @@ class Circuit: ... S 1 ... ''').to_crumble_url() 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + + >>> circuit = stim.Circuit(''' + ... M(0.25) 0 1 2 + ... DETECTOR rec[-1] rec[-2] + ... DETECTOR rec[-2] rec[-3] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''') + >>> err = circuit.shortest_graphlike_error(canonicalize_circuit_errors=True) + >>> circuit.to_crumble_url(skip_detectors=True, mark={1: err}) + 'https://algassert.com/crumble#circuit=;TICK;MARKX(1)1;MARKX(1)2;MARKX(1)0;TICK;M(0.25)0_1_2;OI(0)rec[-1]' """ def to_file( self, @@ -3810,6 +3832,19 @@ class CompiledDetectorSampler: (dets[s, m // 8] >> (m % 8)) & 1 The bit for observable `m` in shot `s` is at (obs[s, m // 8] >> (m % 8)) & 1 + + Examples: + >>> import stim + >>> c = stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... X_ERROR(1.0) 0 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''') + >>> s = c.compile_detector_sampler() + >>> s.sample(shots=1) + array([[ True]]) """ def sample_bit_packed( self, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index d661f362..d2e6ab0f 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -2483,6 +2483,9 @@ class Circuit: """ def to_crumble_url( self, + *, + skip_detectors: bool = False, + mark: Optional[Dict[int, List[stim.ExplainedError]]] = None, ) -> str: """Returns a URL that opens up crumble and loads this circuit into it. @@ -2492,6 +2495,15 @@ class Circuit: at https://algassert.com/crumble, which is what the URL returned by this method will point to. + Args: + skip_detectors: Defaults to False. If set to True, detectors from the + circuit aren't included in the crumble URL. This can reduce visual + clutter in crumble, and improve its performance, since it doesn't + need to indicate or track the sensitivity regions of detectors. + mark: Defaults to None (no marks). If set to a dictionary from int to + errors, such as `mark={1: circuit.shortest_graphlike_error()}`, + then the errors will be highlighted and tracked forward by crumble. + Returns: A URL that can be opened in a web browser. @@ -2503,6 +2515,16 @@ class Circuit: ... S 1 ... ''').to_crumble_url() 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + + >>> circuit = stim.Circuit(''' + ... M(0.25) 0 1 2 + ... DETECTOR rec[-1] rec[-2] + ... DETECTOR rec[-2] rec[-3] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''') + >>> err = circuit.shortest_graphlike_error(canonicalize_circuit_errors=True) + >>> circuit.to_crumble_url(skip_detectors=True, mark={1: err}) + 'https://algassert.com/crumble#circuit=;TICK;MARKX(1)1;MARKX(1)2;MARKX(1)0;TICK;M(0.25)0_1_2;OI(0)rec[-1]' """ def to_file( self, @@ -3810,6 +3832,19 @@ class CompiledDetectorSampler: (dets[s, m // 8] >> (m % 8)) & 1 The bit for observable `m` in shot `s` is at (obs[s, m // 8] >> (m % 8)) & 1 + + Examples: + >>> import stim + >>> c = stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... X_ERROR(1.0) 0 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''') + >>> s = c.compile_detector_sampler() + >>> s.sample(shots=1) + array([[ True]]) """ def sample_bit_packed( self, diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 24fa64d1..8f5b78bd 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -3449,8 +3449,18 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_> mark; + if (!obj_mark.is_none()) { + mark = pybind11::cast>>(obj_mark); + } + return export_crumble_url(self, skip_detectors, mark); + }, + pybind11::kw_only(), + pybind11::arg("skip_detectors") = false, + pybind11::arg("mark") = pybind11::none(), clean_doc_string(R"DOC( + @signature def to_crumble_url(self, *, skip_detectors: bool = False, mark: Optional[dict[int, list[stim.ExplainedError]]] = None) -> str: Returns a URL that opens up crumble and loads this circuit into it. Crumble is a tool for editing stabilizer circuits, and visualizing their @@ -3459,6 +3469,15 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> circuit = stim.Circuit(''' + ... M(0.25) 0 1 2 + ... DETECTOR rec[-1] rec[-2] + ... DETECTOR rec[-2] rec[-3] + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''') + >>> err = circuit.shortest_graphlike_error(canonicalize_circuit_errors=True) + >>> circuit.to_crumble_url(skip_detectors=True, mark={1: err}) + 'https://algassert.com/crumble#circuit=;TICK;MARKX(1)1;MARKX(1)2;MARKX(1)0;TICK;M(0.25)0_1_2;OI(0)rec[-1]' )DOC") .data()); diff --git a/src/stim/py/compiled_detector_sampler.pybind.cc b/src/stim/py/compiled_detector_sampler.pybind.cc index 27cbd52e..e682beca 100644 --- a/src/stim/py/compiled_detector_sampler.pybind.cc +++ b/src/stim/py/compiled_detector_sampler.pybind.cc @@ -305,6 +305,19 @@ void stim_pybind::pybind_compiled_detector_sampler_methods( (dets[s, m // 8] >> (m % 8)) & 1 The bit for observable `m` in shot `s` is at (obs[s, m // 8] >> (m % 8)) & 1 + + Examples: + >>> import stim + >>> c = stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... X_ERROR(1.0) 0 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''') + >>> s = c.compile_detector_sampler() + >>> s.sample(shots=1) + array([[ True]]) )DOC") .data()); diff --git a/src/stim/util_top/export_crumble_url.cc b/src/stim/util_top/export_crumble_url.cc index dab4bd92..521449b3 100644 --- a/src/stim/util_top/export_crumble_url.cc +++ b/src/stim/util_top/export_crumble_url.cc @@ -1,36 +1,129 @@ #include "stim/util_top/export_crumble_url.h" +#include "stim/simulators/matched_error.h" + using namespace stim; -std::string stim::export_crumble_url(const Circuit &circuit) { - auto s = circuit.str(); - std::string_view s_view = s; - std::vector> replace_rules{ - {"QUBIT_COORDS", "Q"}, - {"DETECTOR", "DT"}, - {"OBSERVABLE_INCLUDE", "OI"}, - {", ", ","}, - {") ", ")"}, - {" ", ""}, - {" ", "_"}, - {"\n", ";"}, - }; - std::stringstream out; - out << "https://algassert.com/crumble#circuit="; - for (size_t k = 0; k < s.size(); k++) { - std::string_view v = s_view.substr(k); - bool matched = false; - for (auto [src, dst] : replace_rules) { - if (v.starts_with(src)) { - out << dst; - k += src.size() - 1; - matched = true; - break; +void write_crumble_name_with_args(const CircuitInstruction &instruction, std::ostream &out) { + if (instruction.gate_type == GateType::DETECTOR) { + out << "DT"; + } else if (instruction.gate_type == GateType::QUBIT_COORDS) { + out << "Q"; + } else if (instruction.gate_type == GateType::OBSERVABLE_INCLUDE) { + out << "OI"; + } else { + out << GATE_DATA[instruction.gate_type].name; + } + if (!instruction.args.empty()) { + out << '('; + bool first = true; + for (auto e : instruction.args) { + if (first) { + first = false; + } else { + out << ","; + } + if (e > (double)INT64_MIN && e < (double)INT64_MAX && (int64_t)e == e) { + out << (int64_t)e; + } else { + out << e; + } + } + out << ')'; + } +} + +void write_crumble_url(const Circuit &circuit, bool skip_detectors, const std::vector> &marks, std::ostream &out) { + ExplainedError err; + std::vector> active_marks; + for (size_t k = 0; k < circuit.operations.size(); k++) { + active_marks.clear(); + for (const auto &mark : marks) { + if (!mark.second.stack_frames.empty() && mark.second.stack_frames.back().instruction_offset == k) { + active_marks.push_back(mark); + active_marks.back().second.stack_frames.pop_back(); } } - if (!matched) { - out << s[k]; + + bool adding_markers = false; + for (const auto &mark : active_marks) { + adding_markers |= mark.second.stack_frames.empty(); + } + if (adding_markers) { + out << ";TICK"; + } + for (const auto &mark : active_marks) { + if (mark.second.stack_frames.empty()) { + const auto &v1 = mark.second.flipped_pauli_product; + const auto &v2 = mark.second.flipped_measurement.measured_observable; + for (const auto &e : v1) { + out << ";MARK" << e.gate_target.pauli_type() << "(" << mark.first << ")" << e.gate_target.qubit_value(); + } + if (!v2.empty()) { + auto t = v2[0].gate_target; + char c = "XZ"[t.pauli_type() == 'X']; + out << ";MARK" << c << "(" << mark.first << ")" << v2[0].gate_target.qubit_value(); + } + } + } + if (adding_markers) { + out << ";TICK"; + } + + const auto &instruction = circuit.operations[k]; + if (instruction.gate_type == GateType::DETECTOR && skip_detectors) { + continue; + } + if (k > 0 || adding_markers) { + out << ';'; + } + + if (instruction.gate_type == GateType::REPEAT) { + if (active_marks.empty()) { + out << "REPEAT_" << instruction.repeat_block_rep_count() << "_{;"; + write_crumble_url(instruction.repeat_block_body(circuit), skip_detectors, active_marks, out); + out << ";}"; + } else { + std::vector> iter_marks; + for (size_t k2 = 0; k2 < instruction.repeat_block_rep_count(); k2++) { + iter_marks.clear(); + for (const auto &mark : active_marks) { + if (!mark.second.stack_frames.empty() && mark.second.stack_frames.back().iteration_index == k2) { + iter_marks.push_back(mark); + } + } + write_crumble_url(instruction.repeat_block_body(circuit), skip_detectors, iter_marks, out); + } + } + continue; + } + write_crumble_name_with_args(instruction, out); + + for (size_t k2 = 0; k2 < instruction.targets.size(); k2++) { + auto target = instruction.targets[k2]; + if (target.is_combiner()) { + out << '*'; + k2 += 1; + target = instruction.targets[k2]; + } else if (k2 > 0 || instruction.args.empty()) { + out << '_'; + } + target.write_succinct(out); + } + } +} + +std::string stim::export_crumble_url(const Circuit &circuit, bool skip_detectors, const std::map> &mark) { + std::vector> marks; + for (const auto &[k, vs] : mark) { + for (const auto &v : vs) { + if (!v.circuit_error_locations.empty()) { + marks.push_back({k, v.circuit_error_locations[0]}); + } } } - return out.str(); + std::stringstream s; + s << "https://algassert.com/crumble#circuit="; + write_crumble_url(circuit, skip_detectors, marks, s); + return s.str(); } diff --git a/src/stim/util_top/export_crumble_url.h b/src/stim/util_top/export_crumble_url.h index 9c063cec..ac5618b0 100644 --- a/src/stim/util_top/export_crumble_url.h +++ b/src/stim/util_top/export_crumble_url.h @@ -2,10 +2,11 @@ #define _STIM_UTIL_TOP_EXPORT_CRUMBLE_URL_H #include "stim/circuit/circuit.h" +#include "stim/simulators/matched_error.h" namespace stim { -std::string export_crumble_url(const Circuit &circuit); +std::string export_crumble_url(const Circuit &circuit, bool skip_detectors = false, const std::map> &mark = {}); } // namespace stim diff --git a/src/stim/util_top/export_crumble_url.test.cc b/src/stim/util_top/export_crumble_url.test.cc index e1436cb9..45362d01 100644 --- a/src/stim/util_top/export_crumble_url.test.cc +++ b/src/stim/util_top/export_crumble_url.test.cc @@ -3,6 +3,9 @@ #include "gtest/gtest.h" #include "stim/circuit/circuit.test.h" +#include "stim/search/graphlike/algo.h" +#include "stim/simulators/error_analyzer.h" +#include "stim/simulators/error_matcher.h" using namespace stim; @@ -106,3 +109,39 @@ TEST(export_crumble, all_operations) { "CZ_2_rec[-1]"; ASSERT_EQ(actual, expected); } + +TEST(export_crumble, graphlike_error) { + Circuit circuit(R"CIRCUIT( + R 0 1 2 3 + X_ERROR(0.125) 0 1 + M 0 1 + M(0.125) 2 3 + DETECTOR rec[-1] rec[-2] + DETECTOR rec[-2] rec[-3] + DETECTOR rec[-3] rec[-4] + OBSERVABLE_INCLUDE(0) rec[-1] + )CIRCUIT"); + DetectorErrorModel dem = ErrorAnalyzer::circuit_to_detector_error_model(circuit, false, true, false, 1, false, false); + DetectorErrorModel filter = shortest_graphlike_undetectable_logical_error(dem, false); + auto error = ErrorMatcher::explain_errors_from_circuit(circuit, &filter, false); + + auto actual = export_crumble_url(circuit, true, {{0, error}}); + std::cout << actual << "\n"; + std::cout << actual << "\n"; + auto expected = + "https://algassert.com/crumble#circuit=" + "R_0_1_2_3;" + "TICK;" + "MARKX(0)1;" + "MARKX(0)0;" + "TICK;" + "X_ERROR(0.125)0_1;" + "M_0_1;" + "TICK;" + "MARKX(0)2;" + "MARKX(0)3;" + "TICK;" + "M(0.125)2_3;" + "OI(0)rec[-1]"; + ASSERT_EQ(actual, expected); +} diff --git a/src/stim/util_top/export_crumble_url_pybind_test.py b/src/stim/util_top/export_crumble_url_pybind_test.py index 75d7f08d..2bab1f38 100644 --- a/src/stim/util_top/export_crumble_url_pybind_test.py +++ b/src/stim/util_top/export_crumble_url_pybind_test.py @@ -20,3 +20,11 @@ def test_to_crumble_url_simple(): def test_to_crumble_url_complex(): c = stim.Circuit.generated('surface_code:rotated_memory_x', distance=3, rounds=2, after_clifford_depolarization=0.001) assert 'DEPOLARIZE1' in c.to_crumble_url() + + +def test_to_crumble_url_mark_error(): + c = stim.Circuit.generated('surface_code:rotated_memory_x', distance=3, rounds=2, after_clifford_depolarization=0.001, before_round_data_depolarization=0.001) + err = c.shortest_graphlike_error(canonicalize_circuit_errors=True) + url = c.to_crumble_url(skip_detectors=True, mark={1: err}) + assert 'MARKZ' in url + assert 'DT' not in url