From f66de616f09c50ac77267e7e6f392d8eb677923f Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Wed, 31 Jul 2024 00:04:40 -0700 Subject: [PATCH] Improve dem anticommutation error message to suggest making a diagram (#807) - Also give `_DiagramHelper` a repr that explains what it is and how to get at its contents - Also compress DETECTOR and OBSERVABLE_INCLUDE names in crumble URLs --- src/stim/cmd/command_analyze_errors.test.cc | 6 +- src/stim/cmd/command_diagram.pybind.cc | 25 ++ src/stim/simulators/error_analyzer.cc | 34 ++- src/stim/simulators/error_analyzer.test.cc | 288 +++++++++---------- src/stim/stabilizers/pauli_string_ref.inl | 18 +- src/stim/util_top/export_crumble_url.cc | 2 + src/stim/util_top/export_crumble_url.test.cc | 4 +- 7 files changed, 207 insertions(+), 170 deletions(-) diff --git a/src/stim/cmd/command_analyze_errors.test.cc b/src/stim/cmd/command_analyze_errors.test.cc index 875e6cc6c..dfb5c61a4 100644 --- a/src/stim/cmd/command_analyze_errors.test.cc +++ b/src/stim/cmd/command_analyze_errors.test.cc @@ -97,7 +97,11 @@ DETECTOR rec[-2] [stderr=)OUTPUT" "\x1B" R"OUTPUT([31mThe circuit contains non-deterministic detectors. -(To allow non-deterministic detectors, use the `allow_gauge_detectors` option.) + +To make an SVG picture of the problem, you can use the python API like this: + your_circuit.diagram('detslice-with-ops-svg', tick=range(0, 5), filter_coords=['D0', 'D1', ]) +or the command line API like this: + stim diagram --in your_circuit_file.stim --type detslice-with-ops-svg --tick 0:5 --filter_coords D0:D1 > output_image.svg This was discovered while analyzing a Z-basis reset (R) on: qubit 0 diff --git a/src/stim/cmd/command_diagram.pybind.cc b/src/stim/cmd/command_diagram.pybind.cc index 8f35dda0c..9ef8b45ae 100644 --- a/src/stim/cmd/command_diagram.pybind.cc +++ b/src/stim/cmd/command_diagram.pybind.cc @@ -125,6 +125,31 @@ void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_ void { pybind11::getattr(p, "text")(self.content); }); + c.def("__repr__", [](const DiagramHelper &self) -> std::string { + std::stringstream ss; + ss << ""; + return ss.str(); + }); c.def("__str__", [](const DiagramHelper &self) -> pybind11::object { if (self.type == DiagramType::DIAGRAM_TYPE_SVG_HTML) { return diagram_as_html(self); diff --git a/src/stim/simulators/error_analyzer.cc b/src/stim/simulators/error_analyzer.cc index 47f393be3..7edb44316 100644 --- a/src/stim/simulators/error_analyzer.cc +++ b/src/stim/simulators/error_analyzer.cc @@ -414,12 +414,32 @@ void ErrorAnalyzer::check_for_gauge( has_detectors &= !allow_gauge_detectors; if (has_observables) { error_msg << "The circuit contains non-deterministic observables.\n"; - error_msg << "(Error analysis requires deterministic observables.)\n"; } if (has_detectors) { error_msg << "The circuit contains non-deterministic detectors.\n"; - error_msg << "(To allow non-deterministic detectors, use the `allow_gauge_detectors` option.)\n"; } + size_t range_start = num_ticks_in_past - std::min((size_t)num_ticks_in_past, size_t{5}); + size_t range_end = num_ticks_in_past + 5; + error_msg << "\nTo make an SVG picture of the problem, you can use the python API like this:\n "; + error_msg << "your_circuit.diagram('detslice-with-ops-svg'"; + error_msg << ", tick=range(" << range_start << ", " << range_end << ")"; + error_msg << ", filter_coords=["; + for (auto d : potential_gauge) { + error_msg << "'" << d << "', "; + } + error_msg << "])"; + error_msg << "\nor the command line API like this:\n "; + error_msg << "stim diagram --in your_circuit_file.stim"; + error_msg << " --type detslice-with-ops-svg"; + error_msg << " --tick " << range_start << ":" << range_end; + error_msg << " --filter_coords "; + for (size_t k = 0; k < potential_gauge.size(); k++) { + if (k) { + error_msg << ':'; + } + error_msg << potential_gauge.sorted_items[k]; + } + error_msg << " > output_image.svg\n"; std::map> qubit_coords_map; if (current_circuit_being_analyzed != nullptr) { @@ -1492,7 +1512,7 @@ void ErrorAnalyzer::do_global_error_decomposition_pass() { "`--ignore_decomposition_failures` to `stim analyze_errors`."; if (block_decomposition_from_introducing_remnant_edges) { ss << "\n\nNote: `block_decomposition_from_introducing_remnant_edges` is ON.\n"; - ss << "Turning it off may prevent this error.\n"; + ss << "Turning it off may prevent this error."; } throw std::invalid_argument(ss.str()); } @@ -1538,12 +1558,16 @@ void ErrorAnalyzer::add_error_combinations( std::stringstream message; message << "An error case in a composite error exceeded the max supported number of symptoms " - "(<=15). "; + "(<=15)."; message << "\nThe " << std::to_string(s) << " basis error cases (e.g. X, Z) used to form the combined "; message << "error cases (e.g. Y = X*Z) are:\n"; for (size_t k2 = 0; k2 < s; k2++) { - message << std::to_string(k2) << ": " << comma_sep_workaround(basis_errors[k2]) << "\n"; + message << std::to_string(k2) << ":"; + if (!basis_errors[k2].empty()) { + message << ' '; + } + message << comma_sep_workaround(basis_errors[k2]) << "\n"; } throw std::invalid_argument(message.str()); } diff --git a/src/stim/simulators/error_analyzer.test.cc b/src/stim/simulators/error_analyzer.test.cc index 3e6b26c95..c8d7d49c3 100644 --- a/src/stim/simulators/error_analyzer.test.cc +++ b/src/stim/simulators/error_analyzer.test.cc @@ -1,17 +1,3 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - #include "stim/simulators/error_analyzer.h" #include @@ -2349,28 +2335,19 @@ TEST(ErrorAnalyzer, noisy_measurement_mrz) { } template -std::string check_catch(std::string expected_substring, std::function func) { +std::string expect_catch_message(std::function func) { try { func(); - return "Expected an exception with message '" + expected_substring + "', but no exception was thrown."; - } catch (const TEx &ex) { - std::string s = ex.what(); - if (s.find(expected_substring) == std::string::npos) { - return "Didn't find '" + expected_substring + "' in '" + std::string(ex.what()) + "'."; - } + EXPECT_TRUE(false) << "Function didn't throw an exception."; return ""; + } catch (const TEx &ex) { + return ex.what(); } } TEST(ErrorAnalyzer, context_clues_for_errors) { ASSERT_EQ( - "", - check_catch( - "Can't analyze over-mixing DEPOLARIZE1 errors (probability > 3/4).\n" - "\n" - "Circuit stack trace:\n" - " at instruction #2 [which is DEPOLARIZE1(1) 0]", - [&] { + expect_catch_message([&](){ ErrorAnalyzer::circuit_to_detector_error_model( Circuit(R"CIRCUIT( X 0 @@ -2382,33 +2359,28 @@ TEST(ErrorAnalyzer, context_clues_for_errors) { 0.0, false, true); - })); + }), + "Can't analyze over-mixing DEPOLARIZE1 errors (probability > 3/4).\n" + "\n" + "Circuit stack trace:\n" + " at instruction #2 [which is DEPOLARIZE1(1) 0]"); ASSERT_EQ( - "", - check_catch( - "Can't analyze over-mixing DEPOLARIZE1 errors (probability > 3/4).\n" - "\n" - "Circuit stack trace:\n" - " at instruction #3 [which is a REPEAT 500 block]\n" - " at block's instruction #1 [which is DEPOLARIZE1(1) 0]", - [&] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( + expect_catch_message([&](){ + circuit_to_dem(Circuit(R"CIRCUIT( X 0 Y 1 REPEAT 500 { DEPOLARIZE1(1) 0 } Z 3 - )CIRCUIT"), - false, - false, - false, - 0.0, - false, - true); - })); + )CIRCUIT"), {.block_decomposition_from_introducing_remnant_edges = true}); + }), + "Can't analyze over-mixing DEPOLARIZE1 errors (probability > 3/4).\n" + "\n" + "Circuit stack trace:\n" + " at instruction #3 [which is a REPEAT 500 block]\n" + " at block's instruction #1 [which is DEPOLARIZE1(1) 0]"); } TEST(ErrorAnalyzer, too_many_symptoms) { @@ -2436,9 +2408,22 @@ TEST(ErrorAnalyzer, too_many_symptoms) { DETECTOR rec[-1] DETECTOR rec[-1] )CIRCUIT"); - ASSERT_EQ("", check_catch("max supported number of symptoms", [&] { - ErrorAnalyzer::circuit_to_detector_error_model(symptoms_20, true, false, false, 0.0, false, true); - })); + + ASSERT_EQ( + expect_catch_message([&](){ + circuit_to_dem( + symptoms_20, + {.decompose_errors = true, .block_decomposition_from_introducing_remnant_edges = true}); + }), + R"MSG(An error case in a composite error exceeded the max supported number of symptoms (<=15). +The 2 basis error cases (e.g. X, Z) used to form the combined error cases (e.g. Y = X*Z) are: +0: +1: D0, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14, D15, D16, D17, D18, D19 + + +Circuit stack trace: + at instruction #1 [which is DEPOLARIZE1(0.001) 0])MSG"); + ASSERT_EQ( ErrorAnalyzer::circuit_to_detector_error_model(symptoms_20, false, false, false, 0.0, false, true), DetectorErrorModel(R"model( @@ -2447,57 +2432,63 @@ TEST(ErrorAnalyzer, too_many_symptoms) { } TEST(ErrorAnalyzer, decompose_error_failures) { - ASSERT_EQ("", check_catch("failed to decompose is 'D0, D1, D2'", [] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( + ASSERT_EQ( + expect_catch_message([&](){ + circuit_to_dem(Circuit(R"CIRCUIT( DEPOLARIZE1(0.001) 0 M 0 DETECTOR rec[-1] DETECTOR rec[-1] DETECTOR rec[-1] - )CIRCUIT"), - true, - false, - false, - 0.0, - false, - true); - })); - - ASSERT_EQ("", check_catch("decompose errors into graphlike components", [] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( + )CIRCUIT"), {.decompose_errors = true, .block_decomposition_from_introducing_remnant_edges = true}); + }), + R"MSG(Failed to decompose errors into graphlike components with at most two symptoms. +The error component that failed to decompose is 'D0, D1, D2'. + +In Python, you can ignore this error by passing `ignore_decomposition_failures=True` to `stim.Circuit.detector_error_model(...)`. +From the command line, you can ignore this error by passing the flag `--ignore_decomposition_failures` to `stim analyze_errors`. + +Note: `block_decomposition_from_introducing_remnant_edges` is ON. +Turning it off may prevent this error.)MSG"); + + ASSERT_EQ( + expect_catch_message([&](){ + circuit_to_dem(Circuit(R"CIRCUIT( X_ERROR(0.001) 0 M 0 DETECTOR rec[-1] DETECTOR rec[-1] DETECTOR rec[-1] - )CIRCUIT"), - true, - false, - false, - 0.0, - false, - true); - })); - - ASSERT_EQ("", check_catch("failed to decompose is 'D0, D1, D2, L5'", [] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( + )CIRCUIT"), {.decompose_errors = true, .block_decomposition_from_introducing_remnant_edges = true}); + }), + R"MSG(Failed to decompose errors into graphlike components with at most two symptoms. +The error component that failed to decompose is 'D0, D1, D2'. + +In Python, you can ignore this error by passing `ignore_decomposition_failures=True` to `stim.Circuit.detector_error_model(...)`. +From the command line, you can ignore this error by passing the flag `--ignore_decomposition_failures` to `stim analyze_errors`. + +Note: `block_decomposition_from_introducing_remnant_edges` is ON. +Turning it off may prevent this error.)MSG"); + + ASSERT_EQ( + expect_catch_message([&](){ + circuit_to_dem(Circuit(R"CIRCUIT( X_ERROR(0.001) 0 M 0 DETECTOR rec[-1] DETECTOR rec[-1] DETECTOR rec[-1] OBSERVABLE_INCLUDE(5) rec[-1] - )CIRCUIT"), - true, - false, - false, - 0.0, - false, - true); - })); + )CIRCUIT"), {.decompose_errors = true, .block_decomposition_from_introducing_remnant_edges = true}); + }), + R"MSG(Failed to decompose errors into graphlike components with at most two symptoms. +The error component that failed to decompose is 'D0, D1, D2, L5'. + +In Python, you can ignore this error by passing `ignore_decomposition_failures=True` to `stim.Circuit.detector_error_model(...)`. +From the command line, you can ignore this error by passing the flag `--ignore_decomposition_failures` to `stim analyze_errors`. + +Note: `block_decomposition_from_introducing_remnant_edges` is ON. +Turning it off may prevent this error.)MSG"); } TEST(ErrorAnalyzer, other_error_decomposition_fallback) { @@ -2846,10 +2837,31 @@ TEST(ErrorAnalyzer, mpp_ordering) { TEST(ErrorAnalyzer, anticommuting_observable_error_message_help) { for (size_t folding = 0; folding < 2; folding++) { ASSERT_EQ( - "", - check_catch( - R"ERROR(The circuit contains non-deterministic observables. -(Error analysis requires deterministic observables.) + expect_catch_message([&](){ + circuit_to_dem( + Circuit(R"CIRCUIT( + QUBIT_COORDS(1, 2, 3) 0 + RX 2 + REPEAT 10 { + REPEAT 20 { + C_XYZ 0 + R 1 + M 1 + DETECTOR rec[-1] + TICK + } + } + M 0 2 + OBSERVABLE_INCLUDE(0) rec[-1] rec[-2] + )CIRCUIT"), + {.flatten_loops = folding != 1}); + }), + R"ERROR(The circuit contains non-deterministic observables. + +To make an SVG picture of the problem, you can use the python API like this: + your_circuit.diagram('detslice-with-ops-svg', tick=range(0, 5), filter_coords=['L0', ]) +or the command line API like this: + stim diagram --in your_circuit_file.stim --type detslice-with-ops-svg --tick 0:5 --filter_coords L0 > output_image.svg This was discovered while analyzing an X-basis reset (RX) on: qubit 2 @@ -2863,39 +2875,42 @@ The backward-propagating error sensitivity for L0 was: Circuit stack trace: during TICK layer #1 of 201 - at instruction #2 [which is RX 2])ERROR", - [&] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( - QUBIT_COORDS(1, 2, 3) 0 - RX 2 - REPEAT 10 { - REPEAT 20 { - C_XYZ 0 - R 1 - M 1 - DETECTOR rec[-1] - TICK - } - } - M 0 2 - OBSERVABLE_INCLUDE(0) rec[-1] rec[-2] - )CIRCUIT"), - false, - folding == 1, - false, - 0.0, - false, - true); - })); + at instruction #2 [which is RX 2])ERROR"); ASSERT_EQ( - "", - check_catch( - R"ERROR(The circuit contains non-deterministic observables. -(Error analysis requires deterministic observables.) + expect_catch_message([&](){ + circuit_to_dem(Circuit(R"CIRCUIT( + TICK + SHIFT_COORDS(1000, 2000) + M 0 1 + REPEAT 100 { + RX 0 + DETECTOR rec[-1] + TICK + } + REPEAT 200 { + TICK + } + REPEAT 100 { + M 0 1 + SHIFT_COORDS(0, 100) + DETECTOR(1, 2, 3) rec[-1] rec[-3] + DETECTOR(4, 5, 6) rec[-2] rec[-4] + OBSERVABLE_INCLUDE(0) rec[-1] rec[-2] rec[-3] rec[-4] + TICK + } + REPEAT 1000 { + TICK + } + )CIRCUIT"), {.flatten_loops = folding != 1}); + }), + R"ERROR(The circuit contains non-deterministic observables. The circuit contains non-deterministic detectors. -(To allow non-deterministic detectors, use the `allow_gauge_detectors` option.) + +To make an SVG picture of the problem, you can use the python API like this: + your_circuit.diagram('detslice-with-ops-svg', tick=range(95, 105), filter_coords=['D101', 'L0', ]) +or the command line API like this: + stim diagram --in your_circuit_file.stim --type detslice-with-ops-svg --tick 95:105 --filter_coords D101:L0 > output_image.svg This was discovered while analyzing an X-basis reset (RX) on: qubit 0 @@ -2914,40 +2929,7 @@ The backward-propagating error sensitivity for L0 was: Circuit stack trace: during TICK layer #101 of 1402 at instruction #4 [which is a REPEAT 100 block] - at block's instruction #1 [which is RX 0])ERROR", - [&] { - ErrorAnalyzer::circuit_to_detector_error_model( - Circuit(R"CIRCUIT( - TICK - SHIFT_COORDS(1000, 2000) - M 0 1 - REPEAT 100 { - RX 0 - DETECTOR rec[-1] - TICK - } - REPEAT 200 { - TICK - } - REPEAT 100 { - M 0 1 - SHIFT_COORDS(0, 100) - DETECTOR(1, 2, 3) rec[-1] rec[-3] - DETECTOR(4, 5, 6) rec[-2] rec[-4] - OBSERVABLE_INCLUDE(0) rec[-1] rec[-2] rec[-3] rec[-4] - TICK - } - REPEAT 1000 { - TICK - } - )CIRCUIT"), - false, - folding == 1, - false, - 0.0, - false, - true); - })); + at block's instruction #1 [which is RX 0])ERROR"); } } diff --git a/src/stim/stabilizers/pauli_string_ref.inl b/src/stim/stabilizers/pauli_string_ref.inl index 1be812e70..7fcd5f65d 100644 --- a/src/stim/stabilizers/pauli_string_ref.inl +++ b/src/stim/stabilizers/pauli_string_ref.inl @@ -989,9 +989,9 @@ template void PauliStringRef::do_SWAP(const CircuitInstruction &inst) { const auto &targets = inst.targets; assert((targets.size() & 1) == 0); - for (size_t k = 0; k < inst.targets.size(); k += 2) { - size_t k2 = reverse_order ? inst.targets.size() - 2 - k : k; - size_t q1 = inst.targets[k2].data, q2 = inst.targets[k2 + 1].data; + for (size_t k = 0; k < targets.size(); k += 2) { + size_t k2 = reverse_order ? targets.size() - 2 - k : k; + size_t q1 = targets[k2].data, q2 = targets[k2 + 1].data; zs[q1].swap_with(zs[q2]); xs[q1].swap_with(xs[q2]); } @@ -1195,9 +1195,9 @@ template void PauliStringRef::do_XCZ(const CircuitInstruction &inst) { const auto &targets = inst.targets; assert((targets.size() & 1) == 0); - for (size_t k = 0; k < inst.targets.size(); k += 2) { - size_t k2 = reverse_order ? inst.targets.size() - 2 - k : k; - size_t q1 = inst.targets[k2].data, q2 = inst.targets[k2 + 1].data; + for (size_t k = 0; k < targets.size(); k += 2) { + size_t k2 = reverse_order ? targets.size() - 2 - k : k; + size_t q1 = targets[k2].data, q2 = targets[k2 + 1].data; do_single_cx(inst, q2, q1); } } @@ -1238,9 +1238,9 @@ template void PauliStringRef::do_YCZ(const CircuitInstruction &inst) { const auto &targets = inst.targets; assert((targets.size() & 1) == 0); - for (size_t k = 0; k < inst.targets.size(); k += 2) { - size_t k2 = reverse_order ? inst.targets.size() - 2 - k : k; - size_t q1 = inst.targets[k2].data, q2 = inst.targets[k2 + 1].data; + for (size_t k = 0; k < targets.size(); k += 2) { + size_t k2 = reverse_order ? targets.size() - 2 - k : k; + size_t q1 = targets[k2].data, q2 = targets[k2 + 1].data; do_single_cy(inst, q2, q1); } } diff --git a/src/stim/util_top/export_crumble_url.cc b/src/stim/util_top/export_crumble_url.cc index fcb9765cf..dab4bd927 100644 --- a/src/stim/util_top/export_crumble_url.cc +++ b/src/stim/util_top/export_crumble_url.cc @@ -7,6 +7,8 @@ std::string stim::export_crumble_url(const Circuit &circuit) { std::string_view s_view = s; std::vector> replace_rules{ {"QUBIT_COORDS", "Q"}, + {"DETECTOR", "DT"}, + {"OBSERVABLE_INCLUDE", "OI"}, {", ", ","}, {") ", ")"}, {" ", ""}, diff --git a/src/stim/util_top/export_crumble_url.test.cc b/src/stim/util_top/export_crumble_url.test.cc index 29061aacb..e1436cb9d 100644 --- a/src/stim/util_top/export_crumble_url.test.cc +++ b/src/stim/util_top/export_crumble_url.test.cc @@ -91,8 +91,8 @@ TEST(export_crumble, all_operations) { "X_ERROR(0.1)0;" "MR(0.01)0;" "SHIFT_COORDS(1,2,3);" - "DETECTOR(1,2,3)rec[-1];" - "OBSERVABLE_INCLUDE(0)rec[-1];" + "DT(1,2,3)rec[-1];" + "OI(0)rec[-1];" "MPAD_0_1_0;" "TICK;" "MRX_!0;"