From 855b6e256759ce0bcdf0030aec9da1da7fd916dd Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 17 Aug 2023 22:03:38 -0700 Subject: [PATCH 1/3] - Add `stim.CircuitRepeatBlock.name` for duck typing convenience - Add `stim.DemRepeatBlock.type` for duck typing convenience - Add `stim.TableauSimulator.postselect_observable` - Improve logical error search messages Fixes https://github.com/quantumlib/Stim/issues/606 Fixes https://github.com/quantumlib/Stim/issues/605 Fixes https://github.com/quantumlib/Stim/issues/278 --- .../circuit/circuit_repeat_block.pybind.cc | 47 ++++++++++ src/stim/circuit/circuit_repeat_block_test.py | 10 +++ src/stim/circuit/gate_data.test.cc | 3 +- src/stim/cmd/command_diagram.pybind.cc | 43 +++++---- src/stim/cmd/command_gen.test.cc | 3 +- ...etector_error_model_repeat_block.pybind.cc | 26 ++++++ ...or_error_model_repeat_block_pybind_test.py | 10 +++ .../detector_slice/detector_slice_set.cc | 4 +- src/stim/io/sparse_shot.cc | 3 +- src/stim/io/sparse_shot.test.cc | 4 +- src/stim/mem/bitword.h | 1 - src/stim/mem/bitword_128_sse.h | 4 +- src/stim/mem/bitword_256_avx.h | 4 +- src/stim/mem/bitword_64.h | 2 +- src/stim/mem/simd_word.h | 2 +- src/stim/mem/simd_word.test.cc | 10 ++- src/stim/search/graphlike/algo.cc | 24 +++-- src/stim/search/graphlike/edge.h | 1 + src/stim/search/graphlike/graph.cc | 10 ++- src/stim/search/graphlike/search_state.cc | 3 +- src/stim/search/graphlike/search_state.h | 6 +- .../search/graphlike/search_state.test.cc | 3 +- src/stim/search/hyper/algo.cc | 16 ++-- src/stim/search/hyper/edge.h | 2 +- src/stim/search/hyper/graph.cc | 10 ++- src/stim/search/hyper/search_state.h | 2 +- src/stim/search/hyper/search_state.test.cc | 9 +- src/stim/simulators/dem_sampler.pybind.cc | 6 +- .../sparse_rev_frame_tracker.test.cc | 2 - src/stim/simulators/tableau_simulator.h | 10 ++- src/stim/simulators/tableau_simulator.inl | 79 +++++++++++++++++ .../simulators/tableau_simulator.pybind.cc | 88 +++++++++++++++++++ src/stim/simulators/tableau_simulator.test.cc | 36 ++++++++ .../tableau_simulator_pybind_test.py | 32 +++++++ src/stim/stabilizers/conversions.test.cc | 43 +++------ src/stim/stabilizers/pauli_string.h | 2 + src/stim/stabilizers/pauli_string.inl | 5 ++ src/stim/stabilizers/tableau.h | 2 +- 38 files changed, 463 insertions(+), 104 deletions(-) diff --git a/src/stim/circuit/circuit_repeat_block.pybind.cc b/src/stim/circuit/circuit_repeat_block.pybind.cc index 8cbc7071f..ae4f640a0 100644 --- a/src/stim/circuit/circuit_repeat_block.pybind.cc +++ b/src/stim/circuit/circuit_repeat_block.pybind.cc @@ -105,6 +105,53 @@ void stim_pybind::pybind_circuit_repeat_block_methods(pybind11::module &m, pybin )DOC") .data()); + c.def_property_readonly( + "name", + [](const CircuitRepeatBlock &self) -> pybind11::object { + return pybind11::cast("REPEAT"); + }, + clean_doc_string(R"DOC( + Returns the name "REPEAT". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.CircuitInstruction` or a `stim.CircuitRepeatBlock` + can check the object's name without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(''' + ... H 0 + ... REPEAT 5 { + ... CX 1 2 + ... } + ... S 1 + ... ''') + >>> [instruction.name for instruction in circuit] + ['H', 'REPEAT', 'S'] + )DOC") + .data()); + + c.def_readonly( + "repeat_count", + &CircuitRepeatBlock::repeat_count, + clean_doc_string(R"DOC( + The repetition count of the repeat block. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(''' + ... H 0 + ... REPEAT 5 { + ... CX 0 1 + ... CZ 1 2 + ... } + ... ''') + >>> repeat_block = circuit[1] + >>> repeat_block.repeat_count + 5 + )DOC") + .data()); + c.def( "body_copy", &CircuitRepeatBlock::body_copy, diff --git a/src/stim/circuit/circuit_repeat_block_test.py b/src/stim/circuit/circuit_repeat_block_test.py index 874a4edf8..a9d3fdefc 100644 --- a/src/stim/circuit/circuit_repeat_block_test.py +++ b/src/stim/circuit/circuit_repeat_block_test.py @@ -39,3 +39,13 @@ def test_init_and_equality(): def test_repr(value): assert eval(repr(value), {'stim': stim}) == value assert repr(eval(repr(value), {'stim': stim})) == repr(value) + + +def test_name(): + assert [e.name for e in stim.Circuit(''' + H 0 + REPEAT 5 { + CX 1 2 + } + S 1 + ''')] == ['H', 'REPEAT', 'S'] diff --git a/src/stim/circuit/gate_data.test.cc b/src/stim/circuit/gate_data.test.cc index 2d8197803..5e7a80d09 100644 --- a/src/stim/circuit/gate_data.test.cc +++ b/src/stim/circuit/gate_data.test.cc @@ -71,8 +71,7 @@ TEST(gate_data, hash_matches_storage_location) { } template -std::pair>, std::vector>> -circuit_output_eq_val(const Circuit &circuit) { +std::pair>, std::vector>> circuit_output_eq_val(const Circuit &circuit) { if (circuit.count_measurements() > 1) { throw std::invalid_argument("count_measurements > 1"); } diff --git a/src/stim/cmd/command_diagram.pybind.cc b/src/stim/cmd/command_diagram.pybind.cc index 8daa8622f..888e0e420 100644 --- a/src/stim/cmd/command_diagram.pybind.cc +++ b/src/stim/cmd/command_diagram.pybind.cc @@ -44,17 +44,29 @@ pybind11::class_ stim_pybind::pybind_diagram(pybind11::module &m) return c; } -std::string escape_html_for_srcdoc(const std::string& src) { +std::string escape_html_for_srcdoc(const std::string &src) { // From https://stackoverflow.com/a/9907752 std::stringstream dst; for (char ch : src) { switch (ch) { - case '&': dst << "&"; break; - case '\'': dst << "'"; break; - case '"': dst << """; break; - case '<': dst << "<"; break; - case '>': dst << ">"; break; - default: dst << ch; break; + case '&': + dst << "&"; + break; + case '\'': + dst << "'"; + break; + case '"': + dst << """; + break; + case '<': + dst << "<"; + break; + case '>': + dst << ">"; + break; + default: + dst << ch; + break; } } return dst.str(); @@ -71,11 +83,10 @@ void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_)HTML"; - // out << R"HTML()HTML"; - // output = out.str(); + // out << R"HTML(
)HTML"; out << R"HTML(
)HTML"; output = out.str(); } if (self.type == DIAGRAM_TYPE_GLTF) { std::stringstream out; @@ -85,8 +96,8 @@ void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_)HTML"; + std::string framed = + R"HTML()HTML"; return pybind11::cast(framed); }); c.def("_repr_svg_", [](const DiagramHelper &self) -> pybind11::object { diff --git a/src/stim/cmd/command_gen.test.cc b/src/stim/cmd/command_gen.test.cc index 830814904..8e98145bf 100644 --- a/src/stim/cmd/command_gen.test.cc +++ b/src/stim/cmd/command_gen.test.cc @@ -45,8 +45,7 @@ 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 [det_samples, obs_samples] = sample_batch_detection_events(circuit, 256, SHARED_TEST_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/dem/detector_error_model_repeat_block.pybind.cc b/src/stim/dem/detector_error_model_repeat_block.pybind.cc index 30410d352..cc6f37cab 100644 --- a/src/stim/dem/detector_error_model_repeat_block.pybind.cc +++ b/src/stim/dem/detector_error_model_repeat_block.pybind.cc @@ -80,6 +80,32 @@ void stim_pybind::pybind_detector_error_model_repeat_block_methods( Returns a copy of the block's body, as a stim.DetectorErrorModel. )DOC") .data()); + c.def_property_readonly( + "type", + [](const ExposedDemRepeatBlock &self) -> pybind11::object { + return pybind11::cast("repeat"); + }, + clean_doc_string(R"DOC( + Returns the type name "repeat". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.DemInstruction` or a `stim.DemRepeatBlock` + can check the type field without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.1) D0 L0 + ... repeat 5 { + ... error(0.1) D0 D1 + ... shift_detectors 1 + ... } + ... logical_observable L0 + ... ''') + >>> [instruction.type for instruction in dem] + ['error', 'repeat', 'logical_observable'] + )DOC") + .data()); c.def(pybind11::self == pybind11::self, "Determines if two repeat blocks are identical."); c.def(pybind11::self != pybind11::self, "Determines if two repeat blocks are different."); diff --git a/src/stim/dem/detector_error_model_repeat_block_pybind_test.py b/src/stim/dem/detector_error_model_repeat_block_pybind_test.py index a6716a77f..227cb61c9 100644 --- a/src/stim/dem/detector_error_model_repeat_block_pybind_test.py +++ b/src/stim/dem/detector_error_model_repeat_block_pybind_test.py @@ -35,3 +35,13 @@ def test_equality(): def test_repr(): v = stim.DemRepeatBlock(5, stim.DetectorErrorModel('error(0.125) D1 L2')) assert eval(repr(v), {"stim": stim}) == v + + +def test_type(): + assert [e.type for e in stim.DetectorErrorModel(''' + detector D0 + REPEAT 5 { + error(0.1) D0 + } + logical_observable L0 + ''')] == ['detector', 'repeat', 'logical_observable'] diff --git a/src/stim/diagram/detector_slice/detector_slice_set.cc b/src/stim/diagram/detector_slice/detector_slice_set.cc index 8b42e333e..24fff18b7 100644 --- a/src/stim/diagram/detector_slice/detector_slice_set.cc +++ b/src/stim/diagram/detector_slice/detector_slice_set.cc @@ -55,7 +55,9 @@ bool DetectorSliceSetComputer::process_op_rev(const Circuit &parent, const Circu uint64_t max_skip = std::max(tick_cur, stop_iter) - stop_iter; uint64_t reps = op.repeat_block_rep_count(); uint64_t ticks_per_iteration = loop_body.count_ticks(); - uint64_t skipped_iterations = max_skip == 0 ? 0 : ticks_per_iteration == 0 ? reps : std::min(reps, max_skip / ticks_per_iteration); + uint64_t skipped_iterations = max_skip == 0 ? 0 + : ticks_per_iteration == 0 ? reps + : std::min(reps, max_skip / ticks_per_iteration); if (skipped_iterations) { // We can allow the analyzer to fold parts of the loop we aren't yielding. tracker.undo_loop(loop_body, skipped_iterations); diff --git a/src/stim/io/sparse_shot.cc b/src/stim/io/sparse_shot.cc index bff7163e4..439ae0900 100644 --- a/src/stim/io/sparse_shot.cc +++ b/src/stim/io/sparse_shot.cc @@ -23,7 +23,8 @@ using namespace stim; SparseShot::SparseShot() : hits(), obs_mask(0) { } -SparseShot::SparseShot(std::vector hits, simd_bits<64> obs_mask) : hits(std::move(hits)), obs_mask(std::move(obs_mask)) { +SparseShot::SparseShot(std::vector hits, simd_bits<64> obs_mask) + : hits(std::move(hits)), obs_mask(std::move(obs_mask)) { } void SparseShot::clear() { diff --git a/src/stim/io/sparse_shot.test.cc b/src/stim/io/sparse_shot.test.cc index bcfe919bd..32df848b9 100644 --- a/src/stim/io/sparse_shot.test.cc +++ b/src/stim/io/sparse_shot.test.cc @@ -41,5 +41,7 @@ TEST(sparse_shot, equality) { } TEST(sparse_shot, str) { - ASSERT_EQ((SparseShot{{1, 2, 3}, obs_mask(4)}.str()), "SparseShot{{1, 2, 3}, __1_____________________________________________________________}"); + ASSERT_EQ( + (SparseShot{{1, 2, 3}, obs_mask(4)}.str()), + "SparseShot{{1, 2, 3}, __1_____________________________________________________________}"); } diff --git a/src/stim/mem/bitword.h b/src/stim/mem/bitword.h index de159bc3e..b0bce4636 100644 --- a/src/stim/mem/bitword.h +++ b/src/stim/mem/bitword.h @@ -15,7 +15,6 @@ */ #include -#include #ifndef _STIM_MEM_BIT_WORD_H #define _STIM_MEM_BIT_WORD_H diff --git a/src/stim/mem/bitword_128_sse.h b/src/stim/mem/bitword_128_sse.h index 3530e1f62..3164784ca 100644 --- a/src/stim/mem/bitword_128_sse.h +++ b/src/stim/mem/bitword_128_sse.h @@ -24,7 +24,7 @@ #include #include -#include "stim/mem/simd_word.h" +#include "stim/mem/bitword.h" #include "stim/mem/simd_util.h" namespace stim { @@ -89,7 +89,7 @@ struct bitword<128> { return (bool)(words[0] | words[1]); } inline operator int() const { // NOLINT(hicpp-explicit-conversions) - return (int64_t)*this; + return (int64_t) * this; } inline operator uint64_t() const { // NOLINT(hicpp-explicit-conversions) auto words = to_u64_array(); diff --git a/src/stim/mem/bitword_256_avx.h b/src/stim/mem/bitword_256_avx.h index 51a105618..fca2fe226 100644 --- a/src/stim/mem/bitword_256_avx.h +++ b/src/stim/mem/bitword_256_avx.h @@ -21,8 +21,8 @@ #include #include #include -#include #include +#include #include "stim/mem/bitword.h" #include "stim/mem/simd_util.h" @@ -88,7 +88,7 @@ struct bitword<256> { return (bool)(words[0] | words[1] | words[2] | words[3]); } inline operator int() const { // NOLINT(hicpp-explicit-conversions) - return (int64_t)*this; + return (int64_t) * this; } inline operator uint64_t() const { // NOLINT(hicpp-explicit-conversions) auto words = to_u64_array(); diff --git a/src/stim/mem/bitword_64.h b/src/stim/mem/bitword_64.h index 68b574827..50f537396 100644 --- a/src/stim/mem/bitword_64.h +++ b/src/stim/mem/bitword_64.h @@ -68,7 +68,7 @@ struct bitword<64> { return (bool)(u64[0]); } inline operator int() const { // NOLINT(hicpp-explicit-conversions) - return (int64_t)*this; + return (int64_t) * this; } inline operator uint64_t() const { // NOLINT(hicpp-explicit-conversions) return u64[0]; diff --git a/src/stim/mem/simd_word.h b/src/stim/mem/simd_word.h index 90c4b583f..10b095187 100644 --- a/src/stim/mem/simd_word.h +++ b/src/stim/mem/simd_word.h @@ -19,9 +19,9 @@ #ifndef _STIM_MEM_SIMD_WORD_H #define _STIM_MEM_SIMD_WORD_H -#include "stim/mem/bitword_64.h" #include "stim/mem/bitword_128_sse.h" #include "stim/mem/bitword_256_avx.h" +#include "stim/mem/bitword_64.h" namespace stim { #if __AVX2__ diff --git a/src/stim/mem/simd_word.test.cc b/src/stim/mem/simd_word.test.cc index 77c3db2c4..6fa3eeb22 100644 --- a/src/stim/mem/simd_word.test.cc +++ b/src/stim/mem/simd_word.test.cc @@ -76,10 +76,12 @@ TEST_EACH_WORD_SIZE_W(simd_word, integer_conversions, { ASSERT_EQ((int64_t)(simd_word{(int64_t)23}), 23); ASSERT_EQ((int64_t)(simd_word{(int64_t)-23}), -23); if (W > 64) { - ASSERT_THROW({ - uint64_t u = (uint64_t)(simd_word{(int64_t)-23}); - std::cerr << u; - }, std::invalid_argument); + ASSERT_THROW( + { + uint64_t u = (uint64_t)(simd_word{(int64_t)-23}); + std::cerr << u; + }, + std::invalid_argument); } simd_word w0{(uint64_t)0}; diff --git a/src/stim/search/graphlike/algo.cc b/src/stim/search/graphlike/algo.cc index d466ee036..96a5e234d 100644 --- a/src/stim/search/graphlike/algo.cc +++ b/src/stim/search/graphlike/algo.cc @@ -96,17 +96,25 @@ DetectorErrorModel stim::shortest_graphlike_undetectable_logical_error( std::stringstream err_msg; err_msg << "Failed to find any graphlike logical errors."; if (graph.num_observables == 0) { - err_msg << " Circuit defines no observables."; + err_msg << "\n WARNING: NO OBSERVABLES. The circuit or detector error model didn't define any observables, " + "making it vacuously impossible to find a logical error."; } if (graph.nodes.size() == 0) { - err_msg << " Circuit defines no detectors."; + err_msg << "\n WARNING: NO DETECTORS. The circuit or detector error model didn't define any detectors."; } - bool edges = 0; - for (const auto &n : graph.nodes) { - edges |= ( n.edges.size() > 0 ); - } - if ( !edges ) { - err_msg << " Circuit defines no errors that can flip detectors or observables."; + if (model.count_errors() == 0) { + err_msg << "\n WARNING: NO ERRORS. The circuit or detector error model didn't include any errors, making it " + "vacuously impossible to find a logical error."; + } else { + bool edges = 0; + for (const auto &n : graph.nodes) { + edges |= n.edges.size() > 0; + } + if (!edges) { + err_msg << "\n WARNING: NO GRAPHLIKE ERRORS. Although the circuit or detector error model does define " + "some errors, none of them are graphlike (i.e. have at most two detection events), making it " + "vacuously impossible to find a graphlike logical error."; + } } throw std::invalid_argument(err_msg.str()); } diff --git a/src/stim/search/graphlike/edge.h b/src/stim/search/graphlike/edge.h index 375eb16c9..13a3e7aa5 100644 --- a/src/stim/search/graphlike/edge.h +++ b/src/stim/search/graphlike/edge.h @@ -20,6 +20,7 @@ #include #include #include + #include "stim/mem/simd_bits.h" namespace stim { diff --git a/src/stim/search/graphlike/graph.cc b/src/stim/search/graphlike/graph.cc index 1808fec44..ce3a2b594 100644 --- a/src/stim/search/graphlike/graph.cc +++ b/src/stim/search/graphlike/graph.cc @@ -105,17 +105,21 @@ Graph Graph::from_dem(const DetectorErrorModel &model, bool ignore_ungraphlike_e return result; } bool Graph::operator==(const Graph &other) const { - return nodes == other.nodes && num_observables == other.num_observables && distance_1_error_mask == other.distance_1_error_mask; + return nodes == other.nodes && num_observables == other.num_observables && + distance_1_error_mask == other.distance_1_error_mask; } bool Graph::operator!=(const Graph &other) const { return !(*this == other); } -Graph::Graph(size_t node_count, size_t num_observables) : nodes(node_count), num_observables(num_observables), distance_1_error_mask(num_observables) { +Graph::Graph(size_t node_count, size_t num_observables) + : nodes(node_count), num_observables(num_observables), distance_1_error_mask(num_observables) { } Graph::Graph(std::vector nodes, size_t num_observables, simd_bits<64> distance_1_error_mask) - : nodes(std::move(nodes)), num_observables(num_observables), distance_1_error_mask(std::move(distance_1_error_mask)) { + : nodes(std::move(nodes)), + num_observables(num_observables), + distance_1_error_mask(std::move(distance_1_error_mask)) { } std::ostream &stim::impl_search_graphlike::operator<<(std::ostream &out, const Graph &v) { diff --git a/src/stim/search/graphlike/search_state.cc b/src/stim/search/graphlike/search_state.cc index 1eb291f45..fafb19498 100644 --- a/src/stim/search/graphlike/search_state.cc +++ b/src/stim/search/graphlike/search_state.cc @@ -29,7 +29,8 @@ std::string SearchState::str() const { return result.str(); } -SearchState::SearchState(size_t num_observables) : det_active(NO_NODE_INDEX), det_held(NO_NODE_INDEX), obs_mask(num_observables) { +SearchState::SearchState(size_t num_observables) + : det_active(NO_NODE_INDEX), det_held(NO_NODE_INDEX), obs_mask(num_observables) { } SearchState::SearchState(uint64_t det_active, uint64_t det_held, simd_bits<64> obs_mask) : det_active(det_active), det_held(det_held), obs_mask(std::move(obs_mask)) { diff --git a/src/stim/search/graphlike/search_state.h b/src/stim/search/graphlike/search_state.h index b7cfd9443..0e99343d3 100644 --- a/src/stim/search/graphlike/search_state.h +++ b/src/stim/search/graphlike/search_state.h @@ -25,9 +25,9 @@ namespace stim { namespace impl_search_graphlike { struct SearchState { - uint64_t det_active; // The detection event being moved around in an attempt to remove it (or NO_NODE_INDEX). - uint64_t det_held; // The detection event being left in the same place (or NO_NODE_INDEX). - simd_bits<64> obs_mask; // The accumulated frame changes from moving the detection events around. + uint64_t det_active; // The detection event being moved around in an attempt to remove it (or NO_NODE_INDEX). + uint64_t det_held; // The detection event being left in the same place (or NO_NODE_INDEX). + simd_bits<64> obs_mask; // The accumulated frame changes from moving the detection events around. SearchState() = delete; SearchState(size_t num_observables); diff --git a/src/stim/search/graphlike/search_state.test.cc b/src/stim/search/graphlike/search_state.test.cc index 51e952283..c150e8bff 100644 --- a/src/stim/search/graphlike/search_state.test.cc +++ b/src/stim/search/graphlike/search_state.test.cc @@ -89,7 +89,8 @@ TEST(search_graphlike, DemAdjGraphSearchState_append_transition_as_error_instruc error(1) D1 D3 )MODEL")); - SearchState(1, 2, obs_mask(9)).append_transition_as_error_instruction_to(SearchState(1, NO_NODE_INDEX, obs_mask(9)), out); + SearchState(1, 2, obs_mask(9)) + .append_transition_as_error_instruction_to(SearchState(1, NO_NODE_INDEX, obs_mask(9)), out); ASSERT_EQ(out, DetectorErrorModel(R"MODEL( error(1) L0 L3 L4 error(1) D1 D3 diff --git a/src/stim/search/hyper/algo.cc b/src/stim/search/hyper/algo.cc index 1a39f6204..da1c00b27 100644 --- a/src/stim/search/hyper/algo.cc +++ b/src/stim/search/hyper/algo.cc @@ -105,19 +105,17 @@ DetectorErrorModel stim::find_undetectable_logical_error( } std::stringstream err_msg; - err_msg << "Failed to find any graphlike logical errors."; + err_msg << "Failed to find any logical errors."; if (graph.num_observables == 0) { - err_msg << " Circuit defines no observables."; + err_msg << "\n WARNING: NO OBSERVABLES. The circuit or detector error model didn't define any observables, " + "making it vacuously impossible to find a logical error."; } if (graph.nodes.size() == 0) { - err_msg << " Circuit defines no detectors."; + err_msg << "\n WARNING: NO DETECTORS. The circuit or detector error model didn't define any detectors."; } - bool edges = 0; - for (const auto &n : graph.nodes) { - edges |= ( n.edges.size() > 0 ); - } - if ( !edges ) { - err_msg << " Circuit defines no errors that can flip detectors or observables."; + if (model.count_errors() == 0) { + err_msg << "\n WARNING: NO ERRORS. The circuit or detector error model didn't include any errors, making it " + "vacuously impossible to find a logical error."; } throw std::invalid_argument(err_msg.str()); } diff --git a/src/stim/search/hyper/edge.h b/src/stim/search/hyper/edge.h index c7faf104e..50839f7ac 100644 --- a/src/stim/search/hyper/edge.h +++ b/src/stim/search/hyper/edge.h @@ -21,8 +21,8 @@ #include #include -#include "stim/mem/sparse_xor_vec.h" #include "stim/mem/simd_bits.h" +#include "stim/mem/sparse_xor_vec.h" namespace stim { diff --git a/src/stim/search/hyper/graph.cc b/src/stim/search/hyper/graph.cc index fa9f5cb5e..cb80d2517 100644 --- a/src/stim/search/hyper/graph.cc +++ b/src/stim/search/hyper/graph.cc @@ -57,17 +57,21 @@ Graph Graph::from_dem(const DetectorErrorModel &model, size_t dont_explore_edges return result; } bool Graph::operator==(const Graph &other) const { - return nodes == other.nodes && num_observables == other.num_observables && distance_1_error_mask == other.distance_1_error_mask; + return nodes == other.nodes && num_observables == other.num_observables && + distance_1_error_mask == other.distance_1_error_mask; } bool Graph::operator!=(const Graph &other) const { return !(*this == other); } -Graph::Graph(size_t node_count, size_t num_observables) : nodes(node_count), num_observables(num_observables), distance_1_error_mask(simd_bits<64>(num_observables)) { +Graph::Graph(size_t node_count, size_t num_observables) + : nodes(node_count), num_observables(num_observables), distance_1_error_mask(simd_bits<64>(num_observables)) { } Graph::Graph(std::vector nodes, size_t num_observables, simd_bits<64> distance_1_error_mask) - : nodes(std::move(nodes)), num_observables(num_observables), distance_1_error_mask(std::move(distance_1_error_mask)) { + : nodes(std::move(nodes)), + num_observables(num_observables), + distance_1_error_mask(std::move(distance_1_error_mask)) { } std::ostream &stim::impl_search_hyper::operator<<(std::ostream &out, const Graph &v) { diff --git a/src/stim/search/hyper/search_state.h b/src/stim/search/hyper/search_state.h index 85c6ecd1d..491f80285 100644 --- a/src/stim/search/hyper/search_state.h +++ b/src/stim/search/hyper/search_state.h @@ -18,8 +18,8 @@ #define _STIM_SEARCH_HYPER_SEARCH_STATE_H #include "stim/dem/detector_error_model.h" -#include "stim/mem/sparse_xor_vec.h" #include "stim/mem/simd_bits.h" +#include "stim/mem/sparse_xor_vec.h" namespace stim { diff --git a/src/stim/search/hyper/search_state.test.cc b/src/stim/search/hyper/search_state.test.cc index 53f56b72a..d941d4c7d 100644 --- a/src/stim/search/hyper/search_state.test.cc +++ b/src/stim/search/hyper/search_state.test.cc @@ -30,18 +30,21 @@ static simd_bits<64> obs_mask(uint64_t v) { TEST(search_hyper_search_state, append_transition_as_error_instruction_to) { DetectorErrorModel out; - SearchState{{{1, 2}}, obs_mask(9)}.append_transition_as_error_instruction_to(SearchState{{{1, 2}}, obs_mask(16)}, out); + SearchState{{{1, 2}}, obs_mask(9)}.append_transition_as_error_instruction_to( + SearchState{{{1, 2}}, obs_mask(16)}, out); ASSERT_EQ(out, DetectorErrorModel(R"MODEL( error(1) L0 L3 L4 )MODEL")); - SearchState{{{}}, obs_mask(9)}.append_transition_as_error_instruction_to(SearchState{{{1, 2, 4}}, obs_mask(16)}, out); + SearchState{{{}}, obs_mask(9)}.append_transition_as_error_instruction_to( + SearchState{{{1, 2, 4}}, obs_mask(16)}, out); ASSERT_EQ(out, DetectorErrorModel(R"MODEL( error(1) L0 L3 L4 error(1) D1 D2 D4 L0 L3 L4 )MODEL")); - SearchState{{{1, 2}}, obs_mask(9)}.append_transition_as_error_instruction_to(SearchState{{{2, 3}}, obs_mask(9)}, out); + SearchState{{{1, 2}}, obs_mask(9)}.append_transition_as_error_instruction_to( + SearchState{{{2, 3}}, obs_mask(9)}, out); ASSERT_EQ(out, DetectorErrorModel(R"MODEL( error(1) L0 L3 L4 error(1) D1 D2 D4 L0 L3 L4 diff --git a/src/stim/simulators/dem_sampler.pybind.cc b/src/stim/simulators/dem_sampler.pybind.cc index ecee562e2..112220389 100644 --- a/src/stim/simulators/dem_sampler.pybind.cc +++ b/src/stim/simulators/dem_sampler.pybind.cc @@ -38,7 +38,11 @@ RaiiFile optional_py_path_to_raii_file(const pybind11::object &obj, const char * } pybind11::object dem_sampler_py_sample( - DemSampler &self, size_t shots, bool bit_packed, bool return_errors, pybind11::object &recorded_errors_to_replay) { + DemSampler &self, + size_t shots, + bool bit_packed, + bool return_errors, + pybind11::object &recorded_errors_to_replay) { self.set_min_stripes(shots); bool replay = !recorded_errors_to_replay.is_none(); diff --git a/src/stim/simulators/sparse_rev_frame_tracker.test.cc b/src/stim/simulators/sparse_rev_frame_tracker.test.cc index dbd3fda62..0ff2c3f83 100644 --- a/src/stim/simulators/sparse_rev_frame_tracker.test.cc +++ b/src/stim/simulators/sparse_rev_frame_tracker.test.cc @@ -141,7 +141,6 @@ static std::vector qubit_targets(const std::vector &target return result; } - TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, fuzz_all_unitary_gates_vs_tableau, { auto &rng = SHARED_TEST_RNG(); for (const auto &gate : GATE_DATA.items) { @@ -511,7 +510,6 @@ TEST_EACH_WORD_SIZE_W(SparseUnsignedRevFrameTracker, feedback_into_measurement, ASSERT_EQ(actual, expected); }) - TEST(SparseUnsignedRevFrameTracker, undo_circuit_feedback) { SparseUnsignedRevFrameTracker actual(20, 100, 10); actual.undo_circuit(Circuit(R"CIRCUIT( diff --git a/src/stim/simulators/tableau_simulator.h b/src/stim/simulators/tableau_simulator.h index 82f13af83..49fdc6380 100644 --- a/src/stim/simulators/tableau_simulator.h +++ b/src/stim/simulators/tableau_simulator.h @@ -177,11 +177,11 @@ struct TableauSimulator { /// Returns the expectation value of measuring the qubit in the Z basis. int8_t peek_z(uint32_t target) const; - /// Forces a desired X basis measurement result, or raises an exception if it was impossible. + /// Projects the system into a desired qubit X observable, or raises an exception if it was impossible. void postselect_x(SpanRef targets, bool desired_result); - /// Forces a desired Y basis measurement result, or raises an exception if it was impossible. + /// Projects the system into a desired qubit Y observable, or raises an exception if it was impossible. void postselect_y(SpanRef targets, bool desired_result); - /// Forces a desired Z basis measurement result, or raises an exception if it was impossible. + /// Projects the system into a desired qubit Z observable, or raises an exception if it was impossible. void postselect_z(SpanRef targets, bool desired_result); /// Applies all of the Pauli operations in the given PauliString to the simulator's state. @@ -258,7 +258,11 @@ struct TableauSimulator { /// 0: Observable will be random when measured. int8_t peek_observable_expectation(const PauliString &observable) const; + /// Forces a desired measurement result, or raises an exception if it was impossible. + void postselect_observable(PauliStringRef observable, bool desired_result); + private: + uint32_t try_isolate_observable_to_qubit_z(PauliStringRef observable, bool undo); void do_MXX_disjoint_controls_segment(const CircuitInstruction &inst); void do_MYY_disjoint_controls_segment(const CircuitInstruction &inst); void do_MZZ_disjoint_controls_segment(const CircuitInstruction &inst); diff --git a/src/stim/simulators/tableau_simulator.inl b/src/stim/simulators/tableau_simulator.inl index 0f4c996a4..3717a05ce 100644 --- a/src/stim/simulators/tableau_simulator.inl +++ b/src/stim/simulators/tableau_simulator.inl @@ -122,6 +122,85 @@ void TableauSimulator::postselect_helper( } } +template +uint32_t TableauSimulator::try_isolate_observable_to_qubit_z(PauliStringRef observable, bool undo) { + uint32_t pivot = UINT32_MAX; + for (uint32_t k = 0; k < observable.num_qubits; k++) { + uint8_t p = observable.xs[k] + observable.zs[k] * 2; + if (p) { + if (pivot == UINT32_MAX) { + pivot = k; + if (!undo) { + if (p == 1) { + inv_state.prepend_H_XZ(pivot); + } else if (p == 3) { + inv_state.prepend_H_YZ(pivot); + } + if (observable.sign) { + inv_state.prepend_X(pivot); + } + } + } else { + if (p == 1) { + inv_state.prepend_XCX(pivot, k); + } else if (p == 2) { + inv_state.prepend_XCZ(pivot, k); + } else if (p == 3) { + inv_state.prepend_XCY(pivot, k); + } + } + } + } + if (undo && pivot != UINT32_MAX) { + uint8_t p = observable.xs[pivot] + observable.zs[pivot] * 2; + if (observable.sign) { + inv_state.prepend_X(pivot); + } + if (p == 1) { + inv_state.prepend_H_XZ(pivot); + } else if (p == 3) { + inv_state.prepend_H_YZ(pivot); + } + } + return pivot; +} + +template +void TableauSimulator::postselect_observable( + PauliStringRef observable, + bool desired_result) { + ensure_large_enough_for_qubits(observable.num_qubits); + + uint32_t pivot = try_isolate_observable_to_qubit_z(observable, false); + int8_t expected; + if (pivot != UINT32_MAX) { + expected = peek_z(pivot); + } else { + expected = observable.sign ? -1 : +1; + } + if (desired_result) { + expected *= -1; + } + + if (expected != -1 && pivot != UINT32_MAX) { + GateTarget t{pivot}; + postselect_z(&t, desired_result); + } + try_isolate_observable_to_qubit_z(observable, true); + + if (expected == -1) { + std::stringstream msg; + msg << "It's impossible to postselect into the "; + msg << (desired_result ? "-1" : "+1"); + msg << " eigenstate of "; + msg << observable; + msg << " because the system is deterministically in the "; + msg << (desired_result ? "+1" : "-1"); + msg << " eigenstate."; + throw std::invalid_argument(msg.str()); + } +} + template void TableauSimulator::postselect_x(SpanRef targets, bool desired_result) { postselect_helper(targets, desired_result, GateType::H, "+", "-"); diff --git a/src/stim/simulators/tableau_simulator.pybind.cc b/src/stim/simulators/tableau_simulator.pybind.cc index 963daa99b..0d4610030 100644 --- a/src/stim/simulators/tableau_simulator.pybind.cc +++ b/src/stim/simulators/tableau_simulator.pybind.cc @@ -1466,6 +1466,48 @@ void stim_pybind::pybind_tableau_simulator_methods( )DOC") .data()); + c.def( + "postselect_observable", + [](TableauSimulator &self, const PyPauliString &observable, bool desired_value) { + if (observable.imag) { + throw std::invalid_argument( + "Observable isn't Hermitian; it has imaginary sign. Need observable.sign in [1, -1]."); + } + self.postselect_observable(observable.value, desired_value); + }, + pybind11::arg("observable"), + pybind11::kw_only(), + pybind11::arg("desired_value") = false, + clean_doc_string(R"DOC( + Projects into a desired observable, or raises an exception if it was impossible. + + Postselecting an observable forces it to collapse to a specific eigenstate, + as if it was measured and that state was the result of the measurement. + + Args: + observable: The observable to postselect, specified as a pauli string. + The pauli string's sign must be -1 or +1 (not -i or +i). + desired_value: + False (default): Postselect into the +1 eigenstate of the observable. + True: Postselect into the -1 eigenstate of the observable. + + Raises: + ValueError: + The given observable had an imaginary sign. + OR + The postselection was impossible. The observable was in the opposite + eigenstate, so measuring it would never ever return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.postselect_observable(stim.PauliString("+XX")) + >>> s.postselect_observable(stim.PauliString("+ZZ")) + >>> s.peek_observable_expectation(stim.PauliString("+YY")) + -1 + )DOC") + .data()); + c.def( "measure", [](TableauSimulator &self, uint32_t target) { @@ -1540,6 +1582,21 @@ void stim_pybind::pybind_tableau_simulator_methods( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=False) + >>> s.peek_x(0) + 1 + >>> s.h(0) + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=True) + >>> s.peek_x(0) + -1 )DOC") .data()); @@ -1571,6 +1628,21 @@ void stim_pybind::pybind_tableau_simulator_methods( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=False) + >>> s.peek_y(0) + 1 + >>> s.reset_x(0) + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=True) + >>> s.peek_y(0) + -1 )DOC") .data()); @@ -1602,6 +1674,22 @@ void stim_pybind::pybind_tableau_simulator_methods( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=False) + >>> s.peek_z(0) + 1 + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=True) + >>> s.peek_z(0) + -1 )DOC") .data()); diff --git a/src/stim/simulators/tableau_simulator.test.cc b/src/stim/simulators/tableau_simulator.test.cc index 81af6faff..c3bc4211d 100644 --- a/src/stim/simulators/tableau_simulator.test.cc +++ b/src/stim/simulators/tableau_simulator.test.cc @@ -2335,3 +2335,39 @@ TEST_EACH_WORD_SIZE_W(TableauSimulator, heralded_erase, { ASSERT_EQ(sim.measurement_record.storage, (std::vector{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1})); ASSERT_NE(sim.inv_state, Tableau(14)); }) + +TEST_EACH_WORD_SIZE_W(TableauSimulator, postselect_observable, { + TableauSimulator sim(SHARED_TEST_RNG(), 0); + sim.postselect_observable(PauliString("ZZ"), false); + sim.postselect_observable(PauliString("XX"), false); + + auto initial_state = sim.inv_state; + ASSERT_THROW({ sim.postselect_observable(PauliString("YY"), false); }, std::invalid_argument); + + sim.postselect_observable(PauliString("ZZ"), false); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("ZZ"), false); + ASSERT_EQ(sim.inv_state, initial_state); + + ASSERT_THROW({ sim.postselect_observable(PauliString("ZZ"), true); }, std::invalid_argument); + ASSERT_EQ(sim.inv_state, initial_state); + + ASSERT_THROW({ sim.postselect_observable(PauliString("ZZ"), true); }, std::invalid_argument); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("ZZ"), false); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("XX"), false); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("-YY"), false); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("YY"), true); + ASSERT_EQ(sim.inv_state, initial_state); + + sim.postselect_observable(PauliString("XZ"), true); + ASSERT_NE(sim.inv_state, initial_state); +}) diff --git a/src/stim/simulators/tableau_simulator_pybind_test.py b/src/stim/simulators/tableau_simulator_pybind_test.py index df17afe26..067bc0e14 100644 --- a/src/stim/simulators/tableau_simulator_pybind_test.py +++ b/src/stim/simulators/tableau_simulator_pybind_test.py @@ -706,3 +706,35 @@ def test_bad_inverse_padding_issue_is_fixed(): sim.do(circuit) stabs = sim.canonical_stabilizers() assert stabs[-1] == stim.PauliString(466 * '_' + 'X') + + +def test_postselect_observable(): + sim = stim.TableauSimulator() + assert sim.peek_bloch(0) == stim.PauliString("+Z") + + sim.postselect_observable(stim.PauliString("+X")) + assert sim.peek_bloch(0) == stim.PauliString("+X") + + sim.postselect_observable(stim.PauliString("+Z")) + assert sim.peek_bloch(0) == stim.PauliString("+Z") + + sim.postselect_observable(stim.PauliString("-X")) + assert sim.peek_bloch(0) == stim.PauliString("-X") + + sim.postselect_observable(stim.PauliString("+Z")) + assert sim.peek_bloch(0) == stim.PauliString("+Z") + + sim.postselect_observable(stim.PauliString("-X"), desired_value=True) + assert sim.peek_bloch(0) == stim.PauliString("+X") + + with pytest.raises(ValueError, match="impossible"): + sim.postselect_observable(stim.PauliString("-X")) + assert sim.peek_bloch(0) == stim.PauliString("+X") + + with pytest.raises(ValueError, match="imaginary sign"): + sim.postselect_observable(stim.PauliString("iZ")) + assert sim.peek_bloch(0) == stim.PauliString("+X") + + sim.postselect_observable(stim.PauliString("+XX")) + sim.postselect_observable(stim.PauliString("+ZZ")) + assert sim.peek_observable_expectation(stim.PauliString("+YY")) == -1 diff --git a/src/stim/stabilizers/conversions.test.cc b/src/stim/stabilizers/conversions.test.cc index 86a178ee9..7c6fb4ef7 100644 --- a/src/stim/stabilizers/conversions.test.cc +++ b/src/stim/stabilizers/conversions.test.cc @@ -337,8 +337,7 @@ TEST_EACH_WORD_SIZE_W(conversions, circuit_to_tableau_ignoring_gates, { ASSERT_EQ(circuit_to_tableau(annotations, false, false, false), Tableau(1)); ASSERT_EQ( - circuit_to_tableau( - annotations + measure_reset + measure + reset + unitary + noise, true, true, true) + circuit_to_tableau(annotations + measure_reset + measure + reset + unitary + noise, true, true, true) .num_qubits, 2); }) @@ -418,8 +417,7 @@ TEST_EACH_WORD_SIZE_W(conversions, circuit_to_tableau, { }) TEST_EACH_WORD_SIZE_W(conversions, circuit_to_output_state_vector, { - ASSERT_EQ( - circuit_to_output_state_vector(Circuit(""), false), (std::vector>{{1}})); + ASSERT_EQ(circuit_to_output_state_vector(Circuit(""), false), (std::vector>{{1}})); ASSERT_EQ( circuit_to_output_state_vector(Circuit("H 0 1"), false), (std::vector>{{0.5}, {0.5}, {0.5}, {0.5}})); @@ -475,8 +473,7 @@ TEST_EACH_WORD_SIZE_W(conversions, tableau_to_circuit, { TEST_EACH_WORD_SIZE_W(conversions, unitary_to_tableau_vs_gate_data, { for (const auto &gate : GATE_DATA.items) { if (gate.flags & GATE_IS_UNITARY) { - EXPECT_EQ(unitary_to_tableau(gate.unitary(), true), gate.tableau()) - << gate.name; + EXPECT_EQ(unitary_to_tableau(gate.unitary(), true), gate.tableau()) << gate.name; } } }) @@ -508,31 +505,15 @@ TEST_EACH_WORD_SIZE_W(conversions, tableau_to_unitary_vs_gate_data, { }) TEST_EACH_WORD_SIZE_W(conversions, unitary_vs_tableau_basic, { - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("XCZ").unitary(), false), - GATE_DATA.at("ZCX").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("XCZ").unitary(), true), - GATE_DATA.at("XCZ").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("ZCX").unitary(), false), - GATE_DATA.at("XCZ").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("ZCX").unitary(), true), - GATE_DATA.at("ZCX").tableau()); - - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("XCY").unitary(), false), - GATE_DATA.at("YCX").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("XCY").unitary(), true), - GATE_DATA.at("XCY").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("YCX").unitary(), false), - GATE_DATA.at("XCY").tableau()); - ASSERT_EQ( - unitary_to_tableau(GATE_DATA.at("YCX").unitary(), true), - GATE_DATA.at("YCX").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("XCZ").unitary(), false), GATE_DATA.at("ZCX").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("XCZ").unitary(), true), GATE_DATA.at("XCZ").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("ZCX").unitary(), false), GATE_DATA.at("XCZ").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("ZCX").unitary(), true), GATE_DATA.at("ZCX").tableau()); + + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("XCY").unitary(), false), GATE_DATA.at("YCX").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("XCY").unitary(), true), GATE_DATA.at("XCY").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("YCX").unitary(), false), GATE_DATA.at("XCY").tableau()); + ASSERT_EQ(unitary_to_tableau(GATE_DATA.at("YCX").unitary(), true), GATE_DATA.at("YCX").tableau()); }) TEST_EACH_WORD_SIZE_W(conversions, unitary_to_tableau_fuzz_vs_tableau_to_unitary, { diff --git a/src/stim/stabilizers/pauli_string.h b/src/stim/stabilizers/pauli_string.h index 5d67fbd1c..42afb0d33 100644 --- a/src/stim/stabilizers/pauli_string.h +++ b/src/stim/stabilizers/pauli_string.h @@ -80,6 +80,8 @@ struct PauliString { /// Identity constructor. explicit PauliString(size_t num_qubits); + /// Parse constructor. + explicit PauliString(const std::string &text); /// Factory method for creating a PauliString whose Pauli entries are returned by a function. static PauliString from_func(bool sign, size_t num_qubits, const std::function &func); /// Factory method for creating a PauliString by parsing a string (e.g. "-XIIYZ"). diff --git a/src/stim/stabilizers/pauli_string.inl b/src/stim/stabilizers/pauli_string.inl index c2f05ddbb..2b017ae95 100644 --- a/src/stim/stabilizers/pauli_string.inl +++ b/src/stim/stabilizers/pauli_string.inl @@ -36,6 +36,11 @@ template PauliString::PauliString(size_t num_qubits) : num_qubits(num_qubits), sign(false), xs(num_qubits), zs(num_qubits) { } +template +PauliString::PauliString(const std::string &text) : num_qubits(0), sign(false), xs(0), zs(0) { + *this = std::move(PauliString::from_str(text.c_str())); +} + template PauliString::PauliString(const PauliStringRef &other) : num_qubits(other.num_qubits), sign((bool)other.sign), xs(other.xs), zs(other.zs) { diff --git a/src/stim/stabilizers/tableau.h b/src/stim/stabilizers/tableau.h index 5c8924517..0c728598e 100644 --- a/src/stim/stabilizers/tableau.h +++ b/src/stim/stabilizers/tableau.h @@ -176,7 +176,7 @@ struct Tableau { void prepend_X(size_t q); void prepend_Y(size_t q); void prepend_Z(size_t q); - void prepend_H_XZ(const size_t q); + void prepend_H_XZ(size_t q); void prepend_H_YZ(size_t q); void prepend_H_XY(size_t q); void prepend_C_XYZ(size_t q); From a34199511aa49755712d65889f012bf85f0f6335 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 17 Aug 2023 22:15:34 -0700 Subject: [PATCH 2/3] regen docs --- doc/python_api_reference_vDev.md | 148 ++++++++++++++++++++++++++++++ doc/stim.pyi | 124 +++++++++++++++++++++++++ glue/python/src/stim/__init__.pyi | 124 +++++++++++++++++++++++++ 3 files changed, 396 insertions(+) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index e030cbf0b..bf19a5642 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -75,6 +75,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.CircuitRepeatBlock.__ne__`](#stim.CircuitRepeatBlock.__ne__) - [`stim.CircuitRepeatBlock.__repr__`](#stim.CircuitRepeatBlock.__repr__) - [`stim.CircuitRepeatBlock.body_copy`](#stim.CircuitRepeatBlock.body_copy) + - [`stim.CircuitRepeatBlock.name`](#stim.CircuitRepeatBlock.name) - [`stim.CircuitRepeatBlock.repeat_count`](#stim.CircuitRepeatBlock.repeat_count) - [`stim.CircuitTargetsInsideInstruction`](#stim.CircuitTargetsInsideInstruction) - [`stim.CircuitTargetsInsideInstruction.__init__`](#stim.CircuitTargetsInsideInstruction.__init__) @@ -117,6 +118,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.DemRepeatBlock.__repr__`](#stim.DemRepeatBlock.__repr__) - [`stim.DemRepeatBlock.body_copy`](#stim.DemRepeatBlock.body_copy) - [`stim.DemRepeatBlock.repeat_count`](#stim.DemRepeatBlock.repeat_count) + - [`stim.DemRepeatBlock.type`](#stim.DemRepeatBlock.type) - [`stim.DemTarget`](#stim.DemTarget) - [`stim.DemTarget.__eq__`](#stim.DemTarget.__eq__) - [`stim.DemTarget.__ne__`](#stim.DemTarget.__ne__) @@ -315,6 +317,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.TableauSimulator.peek_x`](#stim.TableauSimulator.peek_x) - [`stim.TableauSimulator.peek_y`](#stim.TableauSimulator.peek_y) - [`stim.TableauSimulator.peek_z`](#stim.TableauSimulator.peek_z) + - [`stim.TableauSimulator.postselect_observable`](#stim.TableauSimulator.postselect_observable) - [`stim.TableauSimulator.postselect_x`](#stim.TableauSimulator.postselect_x) - [`stim.TableauSimulator.postselect_y`](#stim.TableauSimulator.postselect_y) - [`stim.TableauSimulator.postselect_z`](#stim.TableauSimulator.postselect_z) @@ -2859,6 +2862,35 @@ def body_copy( """ ``` + +```python +# stim.CircuitRepeatBlock.name + +# (in class stim.CircuitRepeatBlock) +@property +def name( + self, +) -> object: + """Returns the name "REPEAT". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.CircuitInstruction` or a `stim.CircuitRepeatBlock` + can check the object's name without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(''' + ... H 0 + ... REPEAT 5 { + ... CX 1 2 + ... } + ... S 1 + ... ''') + >>> [instruction.name for instruction in circuit] + ['H', 'REPEAT', 'S'] + """ +``` + ```python # stim.CircuitRepeatBlock.repeat_count @@ -4213,6 +4245,36 @@ def repeat_count( """ ``` + +```python +# stim.DemRepeatBlock.type + +# (in class stim.DemRepeatBlock) +@property +def type( + self, +) -> object: + """Returns the type name "repeat". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.DemInstruction` or a `stim.DemRepeatBlock` + can check the type field without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.1) D0 L0 + ... repeat 5 { + ... error(0.1) D0 D1 + ... shift_detectors 1 + ... } + ... logical_observable L0 + ... ''') + >>> [instruction.type for instruction in dem] + ['error', 'repeat', 'logical_observable'] + """ +``` + ```python # stim.DemTarget @@ -10844,6 +10906,46 @@ def peek_z( """ ``` + +```python +# stim.TableauSimulator.postselect_observable + +# (in class stim.TableauSimulator) +def postselect_observable( + self, + observable: stim.PauliString, + *, + desired_value: bool = False, +) -> None: + """Projects into a desired observable, or raises an exception if it was impossible. + + Postselecting an observable forces it to collapse to a specific eigenstate, + as if it was measured and that state was the result of the measurement. + + Args: + observable: The observable to postselect, specified as a pauli string. + The pauli string's sign must be -1 or +1 (not -i or +i). + desired_value: + False (default): Postselect into the +1 eigenstate of the observable. + True: Postselect into the -1 eigenstate of the observable. + + Raises: + ValueError: + The given observable had an imaginary sign. + OR + The postselection was impossible. The observable was in the opposite + eigenstate, so measuring it would never ever return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.postselect_observable(stim.PauliString("+XX")) + >>> s.postselect_observable(stim.PauliString("+ZZ")) + >>> s.peek_observable_expectation(stim.PauliString("+YY")) + -1 + """ +``` + ```python # stim.TableauSimulator.postselect_x @@ -10872,6 +10974,21 @@ def postselect_x( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=False) + >>> s.peek_x(0) + 1 + >>> s.h(0) + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=True) + >>> s.peek_x(0) + -1 """ ``` @@ -10903,6 +11020,21 @@ def postselect_y( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=False) + >>> s.peek_y(0) + 1 + >>> s.reset_x(0) + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=True) + >>> s.peek_y(0) + -1 """ ``` @@ -10934,6 +11066,22 @@ def postselect_z( orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=False) + >>> s.peek_z(0) + 1 + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=True) + >>> s.peek_z(0) + -1 """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index a8812e3c4..170c5b615 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -2039,6 +2039,28 @@ class CircuitRepeatBlock: ''') """ @property + def name( + self, + ) -> object: + """Returns the name "REPEAT". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.CircuitInstruction` or a `stim.CircuitRepeatBlock` + can check the object's name without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(''' + ... H 0 + ... REPEAT 5 { + ... CX 1 2 + ... } + ... S 1 + ... ''') + >>> [instruction.name for instruction in circuit] + ['H', 'REPEAT', 'S'] + """ + @property def repeat_count( self, ) -> int: @@ -3154,6 +3176,29 @@ class DemRepeatBlock: ) -> int: """The number of times the repeat block's body is supposed to execute. """ + @property + def type( + self, + ) -> object: + """Returns the type name "repeat". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.DemInstruction` or a `stim.DemRepeatBlock` + can check the type field without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.1) D0 L0 + ... repeat 5 { + ... error(0.1) D0 D1 + ... shift_detectors 1 + ... } + ... logical_observable L0 + ... ''') + >>> [instruction.type for instruction in dem] + ['error', 'repeat', 'logical_observable'] + """ class DemTarget: """An instruction target from a detector error model (.dem) file. """ @@ -8446,6 +8491,39 @@ class TableauSimulator: >>> s.peek_z(0) -1 """ + def postselect_observable( + self, + observable: stim.PauliString, + *, + desired_value: bool = False, + ) -> None: + """Projects into a desired observable, or raises an exception if it was impossible. + + Postselecting an observable forces it to collapse to a specific eigenstate, + as if it was measured and that state was the result of the measurement. + + Args: + observable: The observable to postselect, specified as a pauli string. + The pauli string's sign must be -1 or +1 (not -i or +i). + desired_value: + False (default): Postselect into the +1 eigenstate of the observable. + True: Postselect into the -1 eigenstate of the observable. + + Raises: + ValueError: + The given observable had an imaginary sign. + OR + The postselection was impossible. The observable was in the opposite + eigenstate, so measuring it would never ever return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.postselect_observable(stim.PauliString("+XX")) + >>> s.postselect_observable(stim.PauliString("+ZZ")) + >>> s.peek_observable_expectation(stim.PauliString("+YY")) + -1 + """ def postselect_x( self, targets: Union[int, Iterable[int]], @@ -8469,6 +8547,21 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=False) + >>> s.peek_x(0) + 1 + >>> s.h(0) + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=True) + >>> s.peek_x(0) + -1 """ def postselect_y( self, @@ -8493,6 +8586,21 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=False) + >>> s.peek_y(0) + 1 + >>> s.reset_x(0) + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=True) + >>> s.peek_y(0) + -1 """ def postselect_z( self, @@ -8517,6 +8625,22 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=False) + >>> s.peek_z(0) + 1 + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=True) + >>> s.peek_z(0) + -1 """ def reset( self, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index a8812e3c4..170c5b615 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -2039,6 +2039,28 @@ class CircuitRepeatBlock: ''') """ @property + def name( + self, + ) -> object: + """Returns the name "REPEAT". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.CircuitInstruction` or a `stim.CircuitRepeatBlock` + can check the object's name without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(''' + ... H 0 + ... REPEAT 5 { + ... CX 1 2 + ... } + ... S 1 + ... ''') + >>> [instruction.name for instruction in circuit] + ['H', 'REPEAT', 'S'] + """ + @property def repeat_count( self, ) -> int: @@ -3154,6 +3176,29 @@ class DemRepeatBlock: ) -> int: """The number of times the repeat block's body is supposed to execute. """ + @property + def type( + self, + ) -> object: + """Returns the type name "repeat". + + This is a duck-typing convenience method. It exists so that code that doesn't + know whether it has a `stim.DemInstruction` or a `stim.DemRepeatBlock` + can check the type field without having to do an `instanceof` check first. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.1) D0 L0 + ... repeat 5 { + ... error(0.1) D0 D1 + ... shift_detectors 1 + ... } + ... logical_observable L0 + ... ''') + >>> [instruction.type for instruction in dem] + ['error', 'repeat', 'logical_observable'] + """ class DemTarget: """An instruction target from a detector error model (.dem) file. """ @@ -8446,6 +8491,39 @@ class TableauSimulator: >>> s.peek_z(0) -1 """ + def postselect_observable( + self, + observable: stim.PauliString, + *, + desired_value: bool = False, + ) -> None: + """Projects into a desired observable, or raises an exception if it was impossible. + + Postselecting an observable forces it to collapse to a specific eigenstate, + as if it was measured and that state was the result of the measurement. + + Args: + observable: The observable to postselect, specified as a pauli string. + The pauli string's sign must be -1 or +1 (not -i or +i). + desired_value: + False (default): Postselect into the +1 eigenstate of the observable. + True: Postselect into the -1 eigenstate of the observable. + + Raises: + ValueError: + The given observable had an imaginary sign. + OR + The postselection was impossible. The observable was in the opposite + eigenstate, so measuring it would never ever return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.postselect_observable(stim.PauliString("+XX")) + >>> s.postselect_observable(stim.PauliString("+ZZ")) + >>> s.peek_observable_expectation(stim.PauliString("+YY")) + -1 + """ def postselect_x( self, targets: Union[int, Iterable[int]], @@ -8469,6 +8547,21 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=False) + >>> s.peek_x(0) + 1 + >>> s.h(0) + >>> s.peek_x(0) + 0 + >>> s.postselect_x(0, desired_value=True) + >>> s.peek_x(0) + -1 """ def postselect_y( self, @@ -8493,6 +8586,21 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=False) + >>> s.peek_y(0) + 1 + >>> s.reset_x(0) + >>> s.peek_y(0) + 0 + >>> s.postselect_y(0, desired_value=True) + >>> s.peek_y(0) + -1 """ def postselect_z( self, @@ -8517,6 +8625,22 @@ class TableauSimulator: orthogonal to the desired state, so it was literally impossible for a measurement of the qubit to return the desired result. + + Examples: + >>> import stim + >>> s = stim.TableauSimulator() + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=False) + >>> s.peek_z(0) + 1 + >>> s.h(0) + >>> s.peek_z(0) + 0 + >>> s.postselect_z(0, desired_value=True) + >>> s.peek_z(0) + -1 """ def reset( self, From 7b1297cb03405600fcc26f6833dbb81232b23270 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 17 Aug 2023 22:29:25 -0700 Subject: [PATCH 3/3] fix tests --- src/stim/circuit/circuit_pybind_test.py | 38 +++++++++++-------- .../dem/detector_error_model_pybind_test.py | 13 +++---- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index e5f69f152..529e76358 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import pathlib +import re import tempfile from typing import cast @@ -716,7 +717,7 @@ def test_shortest_graphlike_error_empty(): def test_shortest_graphlike_error_msgs(): with pytest.raises( ValueError, - match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables." + match="NO OBSERVABLES" ): stim.Circuit().shortest_graphlike_error() @@ -724,14 +725,16 @@ def test_shortest_graphlike_error_msgs(): M 0 OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match="NO DETECTORS"): c.shortest_graphlike_error() c = stim.Circuit(""" X_ERROR(0.1) 0 M 0 """) - with pytest.raises(ValueError, match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO OBSERVABLES(.|\n)*NO DETECTORS"): + c.shortest_graphlike_error() + with pytest.raises(ValueError, match=""): c.shortest_graphlike_error() c = stim.Circuit(""" @@ -739,7 +742,17 @@ def test_shortest_graphlike_error_msgs(): DETECTOR rec[-1] OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match="NO ERRORS"): + c.shortest_graphlike_error() + + c = stim.Circuit(""" + M(0.1) 0 + DETECTOR rec[-1] + DETECTOR rec[-1] + DETECTOR rec[-1] + OBSERVABLE_INCLUDE(0) rec[-1] + """) + with pytest.raises(ValueError, match="NO GRAPHLIKE ERRORS"): c.shortest_graphlike_error() c = stim.Circuit(""" @@ -747,7 +760,7 @@ def test_shortest_graphlike_error_msgs(): M 0 DETECTOR rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no observables."): + with pytest.raises(ValueError, match="NO OBSERVABLES"): c.shortest_graphlike_error() @@ -761,10 +774,7 @@ def test_search_for_undetectable_logical_errors_empty(): def test_search_for_undetectable_logical_errors_msgs(): - with pytest.raises( - ValueError, - match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables." - ): + with pytest.raises(ValueError, match=r"NO OBSERVABLES(.|\n)*NO DETECTORS"): stim.Circuit().search_for_undetectable_logical_errors( dont_explore_edges_increasing_symptom_degree=True, dont_explore_edges_with_degree_above=4, @@ -775,8 +785,7 @@ def test_search_for_undetectable_logical_errors_msgs(): M 0 OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, - match="Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO DETECTORS(.|\n)*NO ERRORS"): c.search_for_undetectable_logical_errors( dont_explore_edges_increasing_symptom_degree=True, dont_explore_edges_with_degree_above=4, @@ -787,8 +796,7 @@ def test_search_for_undetectable_logical_errors_msgs(): X_ERROR(0.1) 0 M 0 """) - with pytest.raises(ValueError, - match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO OBSERVABLES(.|\n)*NO DETECTORS(.|\n)*NO ERRORS"): c.search_for_undetectable_logical_errors( dont_explore_edges_increasing_symptom_degree=True, dont_explore_edges_with_degree_above=4, @@ -800,7 +808,7 @@ def test_search_for_undetectable_logical_errors_msgs(): DETECTOR rec[-1] OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match="NO ERRORS"): c.search_for_undetectable_logical_errors( dont_explore_edges_increasing_symptom_degree=True, dont_explore_edges_with_degree_above=4, @@ -812,7 +820,7 @@ def test_search_for_undetectable_logical_errors_msgs(): M 0 DETECTOR rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no observables."): + with pytest.raises(ValueError, match="NO OBSERVABLES"): c.search_for_undetectable_logical_errors( dont_explore_edges_increasing_symptom_degree=True, dont_explore_edges_with_degree_above=4, diff --git a/src/stim/dem/detector_error_model_pybind_test.py b/src/stim/dem/detector_error_model_pybind_test.py index 0a309dae6..fab07f2c0 100644 --- a/src/stim/dem/detector_error_model_pybind_test.py +++ b/src/stim/dem/detector_error_model_pybind_test.py @@ -275,24 +275,21 @@ def test_shortest_graphlike_error_rep_code(): def test_shortest_graphlike_error_msgs(): - with pytest.raises( - ValueError, - match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables." - ): + with pytest.raises(ValueError, match=r"NO OBSERVABLES(.|\n)*NO DETECTORS(.|\n)*NO ERRORS"): stim.Circuit().detector_error_model(decompose_errors=True).shortest_graphlike_error() c = stim.Circuit(""" M 0 OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO DETECTORS(.|\n)*NO ERRORS"): c.detector_error_model(decompose_errors=True).shortest_graphlike_error() c = stim.Circuit(""" X_ERROR(0.1) 0 M 0 """) - with pytest.raises(ValueError, match="Circuit defines no observables. Circuit defines no detectors. Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO OBSERVABLES(.|\n)*NO DETECTORS(.|\n)*NO ERRORS"): c.detector_error_model(decompose_errors=True).shortest_graphlike_error() c = stim.Circuit(""" @@ -300,7 +297,7 @@ def test_shortest_graphlike_error_msgs(): DETECTOR rec[-1] OBSERVABLE_INCLUDE(0) rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no errors that can flip detectors or observables."): + with pytest.raises(ValueError, match=r"NO ERRORS"): c.detector_error_model(decompose_errors=True).shortest_graphlike_error() c = stim.Circuit(""" @@ -308,7 +305,7 @@ def test_shortest_graphlike_error_msgs(): M 0 DETECTOR rec[-1] """) - with pytest.raises(ValueError, match="Circuit defines no observables."): + with pytest.raises(ValueError, match=r"NO OBSERVABLES"): c.detector_error_model(decompose_errors=True).shortest_graphlike_error()