From feefe2e7bc42b55c92c1619cb5202f52f81bf10e Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 15:23:12 -0700 Subject: [PATCH 1/7] Add `stim.PauliString.pauli_indices` Fixes https://github.com/quantumlib/Stim/issues/699 --- doc/python_api_reference_vDev.md | 45 +++++++++ doc/stim.pyi | 37 +++++++ glue/python/src/stim/__init__.pyi | 37 +++++++ src/stim/stabilizers/pauli_string.pybind.cc | 97 +++++++++++++++++++ .../stabilizers/pauli_string_pybind_test.py | 29 ++++++ 5 files changed, 245 insertions(+) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 38c116751..177159fe6 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -254,6 +254,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.PauliString.from_numpy`](#stim.PauliString.from_numpy) - [`stim.PauliString.from_unitary_matrix`](#stim.PauliString.from_unitary_matrix) - [`stim.PauliString.iter_all`](#stim.PauliString.iter_all) + - [`stim.PauliString.pauli_indices`](#stim.PauliString.pauli_indices) - [`stim.PauliString.random`](#stim.PauliString.random) - [`stim.PauliString.sign`](#stim.PauliString.sign) - [`stim.PauliString.to_numpy`](#stim.PauliString.to_numpy) @@ -8807,6 +8808,50 @@ def iter_all( """ ``` + +```python +# stim.PauliString.pauli_indices + +# (in class stim.PauliString) +def pauli_indices( + self, + included_paulis: str = "XYZ", +) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ +``` + ```python # stim.PauliString.random diff --git a/doc/stim.pyi b/doc/stim.pyi index 3a8214ccc..db57d62bf 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -6801,6 +6801,43 @@ class PauliString: +_ZX +_ZZ """ + def pauli_indices( + self, + included_paulis: str = "XYZ", + ) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ @staticmethod def random( num_qubits: int, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 3a8214ccc..db57d62bf 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -6801,6 +6801,43 @@ class PauliString: +_ZX +_ZZ """ + def pauli_indices( + self, + included_paulis: str = "XYZ", + ) -> List[int]: + """Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + """ @staticmethod def random( num_qubits: int, diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index 3c335cee0..4d83c9984 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -636,6 +636,103 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla )DOC") .data()); + c.def( + "pauli_indices", + [](const FlexPauliString &self, const std::string &include) { + std::vector result; + size_t n64 = self.value.xs.num_u64_padded(); + bool keep_i = false; + bool keep_x = false; + bool keep_y = false; + bool keep_z = false; + for (char c : include) { + switch (c) { + case '_': + case 'I': + keep_i = true; + break; + case 'x': + case 'X': + keep_x = true; + break; + case 'y': + case 'Y': + keep_y = true; + break; + case 'z': + case 'Z': + keep_z = true; + break; + default: + throw std::invalid_argument("Invalid character in include string: " + std::string(1, c)); + } + } + for (size_t k = 0; k < n64; k++) { + uint64_t x = self.value.xs.u64[k]; + uint64_t z = self.value.zs.u64[k]; + uint64_t u = 0; + if (keep_i) { + u |= ~x & ~z; + } + if (keep_x) { + u |= x & ~z; + } + if (keep_y) { + u |= x & z; + } + if (keep_z) { + u |= ~x & z; + } + while (u) { + uint8_t v = std::countr_zero(u); + uint64_t q = k*64 + v; + if (q >= self.value.num_qubits) { + return result; + } + result.push_back(q); + u &= u - 1; + } + } + return result; + }, + pybind11::arg("included_paulis") = "XYZ", + clean_doc_string(R"DOC( + @signature def pauli_indices(self, included_paulis: str = "XYZ") -> List[int]: + Returns the indices of non-identity Paulis, or of specified Paulis. + + Args: + include: A string containing the Pauli types to include. + X type Pauli indices are included if "X" or "x" is in the string. + Y type Pauli indices are included if "Y" or "y" is in the string. + Z type Pauli indices are included if "Z" or "z" is in the string. + I type Pauli indices are included if "I" or "_" is in the string. + An exception is thrown if other characters are in the string. + + Returns: + A list containing the ascending indices of matching Pauli terms. + + Examples: + >>> import stim + >>> stim.PauliString("_____X___Y____Z___").pauli_indices() + [5, 9, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("XZ") + [5, 14] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("X") + [5] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("Y") + [9] + + >>> stim.PauliString("_____X___Y____Z___").pauli_indices("IY") + [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17] + + >>> stim.PauliString("-X103*Y100").pauli_indices() + [100, 103] + )DOC") + .data()); + c.def( "commutes", [](const FlexPauliString &self, const FlexPauliString &other) { diff --git a/src/stim/stabilizers/pauli_string_pybind_test.py b/src/stim/stabilizers/pauli_string_pybind_test.py index 4efff759e..5058e6a55 100644 --- a/src/stim/stabilizers/pauli_string_pybind_test.py +++ b/src/stim/stabilizers/pauli_string_pybind_test.py @@ -899,3 +899,32 @@ def test_backwards_compatibility_init(): assert stim.PauliString(text="XYZ") == stim.PauliString("+XYZ") # noinspection PyArgumentList assert stim.PauliString(other=stim.PauliString("XYZ")) == stim.PauliString("+XYZ") + + +def test_pauli_indices(): + assert stim.PauliString().pauli_indices() == [] + assert stim.PauliString().pauli_indices("X") == [] + assert stim.PauliString().pauli_indices("I") == [] + assert stim.PauliString(5).pauli_indices() == [] + assert stim.PauliString(5).pauli_indices("X") == [] + assert stim.PauliString(5).pauli_indices("I") == [0, 1, 2, 3, 4] + assert stim.PauliString("X1000").pauli_indices() == [1000] + assert stim.PauliString("Y1000").pauli_indices() == [1000] + assert stim.PauliString("Z1000").pauli_indices() == [1000] + assert stim.PauliString("X1000").pauli_indices("YZ") == [] + assert stim.PauliString("Y1000").pauli_indices("XZ") == [] + assert stim.PauliString("Z1000").pauli_indices("XY") == [] + assert stim.PauliString("X1000").pauli_indices("X") == [1000] + assert stim.PauliString("Y1000").pauli_indices("Y") == [1000] + assert stim.PauliString("Z1000").pauli_indices("Z") == [1000] + + assert stim.PauliString("_XYZ").pauli_indices("x") == [1] + assert stim.PauliString("_XYZ").pauli_indices("X") == [1] + assert stim.PauliString("_XYZ").pauli_indices("y") == [2] + assert stim.PauliString("_XYZ").pauli_indices("Y") == [2] + assert stim.PauliString("_XYZ").pauli_indices("z") == [3] + assert stim.PauliString("_XYZ").pauli_indices("Z") == [3] + assert stim.PauliString("_XYZ").pauli_indices("I") == [0] + assert stim.PauliString("_XYZ").pauli_indices("_") == [0] + with pytest.raises(ValueError, match="Invalid character"): + assert stim.PauliString("_XYZ").pauli_indices("k") From 52694bda14833632b0f365cb967ff9aa61e0f92b Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 15:27:41 -0700 Subject: [PATCH 2/7] regen docs --- doc/python_api_reference_vDev.md | 2 +- doc/stim.pyi | 2 +- glue/python/src/stim/__init__.pyi | 2 +- src/stim/py/stim.pybind.cc | 1 - src/stim/stabilizers/pauli_string.pybind.cc | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 177159fe6..a0db0e71b 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -8828,7 +8828,7 @@ def pauli_indices( An exception is thrown if other characters are in the string. Returns: - A list containing the indices of matching Pauli terms. + A list containing the ascending indices of matching Pauli terms. Examples: >>> import stim diff --git a/doc/stim.pyi b/doc/stim.pyi index db57d62bf..8368e028a 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -6816,7 +6816,7 @@ class PauliString: An exception is thrown if other characters are in the string. Returns: - A list containing the indices of matching Pauli terms. + A list containing the ascending indices of matching Pauli terms. Examples: >>> import stim diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index db57d62bf..8368e028a 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -6816,7 +6816,7 @@ class PauliString: An exception is thrown if other characters are in the string. Returns: - A list containing the indices of matching Pauli terms. + A list containing the ascending indices of matching Pauli terms. Examples: >>> import stim diff --git a/src/stim/py/stim.pybind.cc b/src/stim/py/stim.pybind.cc index df3c8f067..a54490a23 100644 --- a/src/stim/py/stim.pybind.cc +++ b/src/stim/py/stim.pybind.cc @@ -124,7 +124,6 @@ std::vector target_combined_paulis(const pybind11::object &paulis, b result.push_back(GateTarget::combiner()); continue; } - } std::stringstream ss; diff --git a/src/stim/stabilizers/pauli_string.pybind.cc b/src/stim/stabilizers/pauli_string.pybind.cc index 4d83c9984..22160685f 100644 --- a/src/stim/stabilizers/pauli_string.pybind.cc +++ b/src/stim/stabilizers/pauli_string.pybind.cc @@ -685,7 +685,7 @@ void stim_pybind::pybind_pauli_string_methods(pybind11::module &m, pybind11::cla } while (u) { uint8_t v = std::countr_zero(u); - uint64_t q = k*64 + v; + uint64_t q = k * 64 + v; if (q >= self.value.num_qubits) { return result; } From bc0b42260a40ae08b932936542d7854bedf32b39 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 16:49:15 -0700 Subject: [PATCH 3/7] Add `stim.Circuit.detecting_regions` Fixes https://github.com/quantumlib/Stim/issues/349 --- file_lists/test_files | 1 + src/stim/circuit/circuit.pybind.cc | 229 ++++++++++++++++++++++++ src/stim/circuit/circuit_pybind_test.py | 17 ++ src/stim/dem/dem_instruction.cc | 27 ++- src/stim/dem/dem_instruction.h | 2 + src/stim/dem/dem_instruction.test.cc | 31 ++++ src/stim/stabilizers/conversions.cc | 43 +++++ src/stim/stabilizers/conversions.h | 8 + 8 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 src/stim/dem/dem_instruction.test.cc diff --git a/file_lists/test_files b/file_lists/test_files index 6506e3f6a..16b5dca8e 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -14,6 +14,7 @@ src/stim/cmd/command_gen.test.cc src/stim/cmd/command_m2d.test.cc src/stim/cmd/command_sample.test.cc src/stim/cmd/command_sample_dem.test.cc +src/stim/dem/dem_instruction.test.cc src/stim/dem/detector_error_model.test.cc src/stim/diagram/ascii_diagram.test.cc src/stim/diagram/base64.test.cc diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 6ee717ed5..b4707e27e 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -46,6 +46,89 @@ using namespace stim; using namespace stim_pybind; +std::set py_dem_filter_to_dem_target_set( + const Circuit &circuit, const CircuitStats &stats, const pybind11::object &included_targets_filter) { + std::set result; + auto add_all_dets = [&]() { + for (uint64_t k = 0; k < stats.num_detectors; k++) { + result.insert(DemTarget::relative_detector_id(k)); + } + }; + auto add_all_obs = [&]() { + for (uint64_t k = 0; k < stats.num_observables; k++) { + result.insert(DemTarget::observable_id(k)); + } + }; + + bool has_coords = false; + std::map> cached_coords; + auto get_coords_cached = [&]() -> const std::map> & { + std::set all_dets; + for (uint64_t k = 0; k < stats.num_detectors; k++) { + all_dets.insert(k); + } + if (!has_coords) { + cached_coords = circuit.get_detector_coordinates(all_dets); + has_coords = true; + } + return cached_coords; + }; + + if (included_targets_filter.is_none()) { + add_all_dets(); + add_all_obs(); + return result; + } + for (const auto &filter : included_targets_filter) { + bool fail = false; + if (pybind11::isinstance(filter)) { + result.insert(pybind11::cast(filter)); + } else if (pybind11::isinstance(filter)) { + std::string s = pybind11::cast(filter); + if (s == "D") { + add_all_dets(); + } else if (s == "L") { + add_all_obs(); + } else if (s.starts_with("D") || s.starts_with("L")) { + result.insert(DemTarget::from_text(s)); + } else { + fail = true; + } + } else { + std::vector prefix; + for (auto e : filter) { + if (pybind11::isinstance(e) || pybind11::isinstance(e)) { + prefix.push_back(pybind11::cast(e)); + } else { + fail = true; + break; + } + } + if (!fail) { + for (const auto &[target, coord] : get_coords_cached()) { + if (coord.size() >= prefix.size()) { + bool match = true; + for (size_t k = 0; k < prefix.size(); k++) { + match &= prefix[k] == coord[k]; + } + if (match) { + result.insert(DemTarget::relative_detector_id(target)); + } + } + } + } + } + if (fail) { + std::stringstream ss; + ss << "Don't know how to interpret '"; + ss << pybind11::cast(pybind11::repr(filter)); + ss << "' as a dem target filter."; + throw std::invalid_argument(ss.str()); + } + } + return result; +} + std::string circuit_repr(const Circuit &self) { if (self.operations.empty()) { return "stim.Circuit()"; @@ -2118,6 +2201,152 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ std::map> { + auto stats = self.compute_stats(); + auto included_target_set = py_dem_filter_to_dem_target_set(self, stats, included_targets); + std::set included_tick_set; + + if (included_ticks.is_none()) { + for (uint64_t k = 0; k < stats.num_ticks; k++) { + included_tick_set.insert(k); + } + } else { + for (const auto &t : included_ticks) { + included_tick_set.insert(pybind11::cast(t)); + } + } + auto result = circuit_to_detecting_regions( + self, included_target_set, included_tick_set, ignore_anticommutation_errors); + std::map> exposed_result; + for (const auto &[k, v] : result) { + exposed_result.insert({ExposedDemTarget(k), std::move(v)}); + } + return exposed_result; + }, + pybind11::kw_only(), + pybind11::arg("targets") = pybind11::none(), + pybind11::arg("ticks") = pybind11::none(), + pybind11::arg("ignore_anticommutation_errors") = false, + clean_doc_string(R"DOC( + @signature def detecting_regions(self, *, included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: + Explains how detector error model errors are produced by circuit errors. + + Args: + targets: Defaults to everything (None). + + When specified, this should be an iterable of filters where items + matching any one filter are included. + + A variety of filters are supported: + stim.DemTarget: Includes the targeted detector or observable. + Iterable[float]: Coordinate prefix match. Includes detectors whose + coordinate data begins with the same floats. + "D": Includes all detectors. + "L": Includes all observables. + "D#" (e.g. "D5"): Includes the detector with the specified index. + "L#" (e.g. "L5"): Includes the observable with the specified index. + + ticks: Defaults to everything (None). + When specified, this should be a list of integers corresponding to + the tick indices to report sensitivities for. + + ignore_anticommutation_errors: Defaults to False. + When set to False, invalid detecting regions that anticommute with a + reset will cause the method to raise an exception. When set to True, + the offending component will simply be silently dropped. This can + result in broken detectors having apparently enormous detecting + regions. + + Returns: + Nested dictionaries keyed first by a `stim.DemTarget` identifying the + detector or observable, then by the index of the tick, leading to a + PauliString with that target's error sensitivity at that tick. + + Note you can use `stim.PauliString.pauli_indices` to quickly get to the + non-identity terms in the sensitivity. + + Examples: + >>> import stim + + >>> detecting_regions = stim.Circuit(''' + ... R 0 + ... TICK + ... H 0 + ... TICK + ... CX 0 1 + ... TICK + ... MX 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''').detecting_regions() + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D0 + tick 0 = +Z_ + tick 1 = +X_ + tick 2 = +XX + + >>> circuit = stim.Circuit.generated( + ... "surface_code:rotated_memory_x", + ... rounds=5, + ... distance=4, + ... ) + + >>> detecting_regions = circuit.detecting_regions( + ... targets=["L0", (2, 4), stim.DemTarget.relative_detector_id(5)], + ... ticks=range(5, 15), + ... ) + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D1 + tick 5 = +____________________X______________________ + tick 6 = +____________________Z______________________ + target D5 + tick 5 = +______X____________________________________ + tick 6 = +______Z____________________________________ + target D14 + tick 5 = +__________X_X______XXX_____________________ + tick 6 = +__________X_X______XZX_____________________ + tick 7 = +__________X_X______XZX_____________________ + tick 8 = +__________X_X______XXX_____________________ + tick 9 = +__________XXX_____XXX______________________ + tick 10 = +__________XXX_______X______________________ + tick 11 = +__________X_________X______________________ + tick 12 = +____________________X______________________ + tick 13 = +____________________Z______________________ + target D29 + tick 7 = +____________________Z______________________ + tick 8 = +____________________X______________________ + tick 9 = +____________________XX_____________________ + tick 10 = +___________________XXX_______X_____________ + tick 11 = +____________X______XXXX______X_____________ + tick 12 = +__________X_X______XXX_____________________ + tick 13 = +__________X_X______XZX_____________________ + tick 14 = +__________X_X______XZX_____________________ + target D44 + tick 14 = +____________________Z______________________ + target L0 + tick 5 = +_X________X________X________X______________ + tick 6 = +_X________X________X________X______________ + tick 7 = +_X________X________X________X______________ + tick 8 = +_X________X________X________X______________ + tick 9 = +_X________X_______XX________X______________ + tick 10 = +_X________X________X________X______________ + tick 11 = +_X________XX_______X________XX_____________ + tick 12 = +_X________X________X________X______________ + tick 13 = +_X________X________X________X______________ + tick 14 = +_X________X________X________X______________ + )DOC") + .data()); + c.def( "without_noise", &Circuit::without_noise, diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index 31fe6510f..129802093 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -1688,3 +1688,20 @@ def test_has_flow_shorthands(): assert c.has_flow("-iX_ -> -iXX xor rec[1] xor rec[3]") with pytest.raises(ValueError): c.has_flow("iX_ -> XX") + + +def test_detecting_regions(): + assert stim.Circuit(''' + R 0 + TICK + H 0 + TICK + CX 0 1 + TICK + MX 0 1 + DETECTOR rec[-1] rec[-2] + ''').detecting_regions() == {stim.DemTarget.relative_detector_id(0): { + 0: stim.PauliString("Z_"), + 1: stim.PauliString("X_"), + 2: stim.PauliString("XX"), + }} diff --git a/src/stim/dem/dem_instruction.cc b/src/stim/dem/dem_instruction.cc index b240ff14c..6ddff59d7 100644 --- a/src/stim/dem/dem_instruction.cc +++ b/src/stim/dem/dem_instruction.cc @@ -2,6 +2,7 @@ #include +#include "stim/arg_parse.h" #include "stim/dem/detector_error_model.h" #include "stim/simulators/error_analyzer.h" #include "stim/str_util.h" @@ -11,14 +12,17 @@ using namespace stim; constexpr uint64_t OBSERVABLE_BIT = uint64_t{1} << 63; constexpr uint64_t SEPARATOR_SYGIL = UINT64_MAX; +constexpr uint64_t MAX_OBS = 0xFFFFFFFF; +constexpr uint64_t MAX_DET = (uint64_t{1} << 62) - 1; + DemTarget DemTarget::observable_id(uint64_t id) { - if (id > 0xFFFFFFFF) { + if (id > MAX_OBS) { throw std::invalid_argument("id > 0xFFFFFFFF"); } return {OBSERVABLE_BIT | id}; } DemTarget DemTarget::relative_detector_id(uint64_t id) { - if (id >= (uint64_t{1} << 62)) { + if (id > MAX_DET) { throw std::invalid_argument("Relative detector id too large."); } return {id}; @@ -75,6 +79,25 @@ void DemTarget::shift_if_detector_id(int64_t offset) { data = (uint64_t)((int64_t)data + offset); } } +DemTarget DemTarget::from_text(std::string_view text) { + if (!text.empty()) { + bool is_det = text[0] == 'D'; + bool is_obs = text[0] == 'L'; + if (is_det || is_obs) { + int64_t parsed = 0; + if (parse_int64(text.substr(1), &parsed)) { + if (parsed >= 0) { + if (is_det && parsed <= (int64_t)MAX_DET) { + return DemTarget::relative_detector_id(parsed); + } else if (is_obs && parsed <= (int64_t)MAX_OBS) { + return DemTarget::observable_id(parsed); + } + } + } + } + } + throw std::invalid_argument("Failed to parse as a stim.DemTarget: '" + std::string(text) + "'"); +} bool DemInstruction::operator<(const DemInstruction &other) const { if (type != other.type) { diff --git a/src/stim/dem/dem_instruction.h b/src/stim/dem/dem_instruction.h index cba775605..d75c4f349 100644 --- a/src/stim/dem/dem_instruction.h +++ b/src/stim/dem/dem_instruction.h @@ -37,6 +37,8 @@ struct DemTarget { bool operator!=(const DemTarget &other) const; bool operator<(const DemTarget &other) const; std::string str() const; + + static DemTarget from_text(std::string_view text); }; struct DetectorErrorModel; diff --git a/src/stim/dem/dem_instruction.test.cc b/src/stim/dem/dem_instruction.test.cc new file mode 100644 index 000000000..53865e465 --- /dev/null +++ b/src/stim/dem/dem_instruction.test.cc @@ -0,0 +1,31 @@ +#include "stim/dem/dem_instruction.h" + +#include "gtest/gtest.h" + +using namespace stim; + +TEST(dem_instruction, from_str) { + ASSERT_EQ(DemTarget::from_text("D5"), DemTarget::relative_detector_id(5)); + ASSERT_EQ(DemTarget::from_text("D0"), DemTarget::relative_detector_id(0)); + ASSERT_EQ(DemTarget::from_text("D4611686018427387903"), DemTarget::relative_detector_id(4611686018427387903)); + + ASSERT_EQ(DemTarget::from_text("L5"), DemTarget::observable_id(5)); + ASSERT_EQ(DemTarget::from_text("L0"), DemTarget::observable_id(0)); + ASSERT_EQ(DemTarget::from_text("L4294967295"), DemTarget::observable_id(4294967295)); + + ASSERT_THROW({ DemTarget::from_text("D4611686018427387904"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("L4294967296"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("L-1"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("L-1"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("D-1"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("Da"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("Da "); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text(" Da"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("X"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text(""); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("1"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("-1"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("0"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text("'"); }, std::invalid_argument); + ASSERT_THROW({ DemTarget::from_text(" "); }, std::invalid_argument); +} diff --git a/src/stim/stabilizers/conversions.cc b/src/stim/stabilizers/conversions.cc index caf7eba86..44c18119f 100644 --- a/src/stim/stabilizers/conversions.cc +++ b/src/stim/stabilizers/conversions.cc @@ -1,5 +1,7 @@ #include "stim/stabilizers/conversions.h" +#include "stim/simulators/sparse_rev_frame_tracker.h" + using namespace stim; void stim::independent_to_disjoint_xyz_errors( @@ -137,3 +139,44 @@ double stim::independent_per_channel_probability_to_depolarize2_probability(doub q *= q; return 15.0 / 16.0 * (1.0 - q); } + +std::map> stim::circuit_to_detecting_regions( + const Circuit &circuit, + std::set included_targets, + std::set included_ticks, + bool ignore_anticommutation_errors) { + CircuitStats stats = circuit.compute_stats(); + uint64_t tick_index = stats.num_ticks; + SparseUnsignedRevFrameTracker tracker( + stats.num_qubits, stats.num_measurements, stats.num_detectors, !ignore_anticommutation_errors); + std::map> result; + circuit.for_each_operation_reverse([&](const CircuitInstruction &inst) { + if (inst.gate_type == GateType::TICK) { + tick_index -= 1; + if (included_ticks.contains(tick_index)) { + for (size_t q = 0; q < stats.num_qubits; q++) { + for (auto target : tracker.xs[q]) { + if (included_targets.contains(target)) { + auto &m = result[target]; + if (!m.contains(tick_index)) { + m.insert({tick_index, FlexPauliString(stats.num_qubits)}); + } + m.at(tick_index).value.xs[q] ^= 1; + } + } + for (auto target : tracker.zs[q]) { + if (included_targets.contains(target)) { + auto &m = result[target]; + if (!m.contains(tick_index)) { + m.insert({tick_index, FlexPauliString(stats.num_qubits)}); + } + m.at(tick_index).value.zs[q] ^= 1; + } + } + } + } + } + tracker.undo_gate(inst); + }); + return result; +} diff --git a/src/stim/stabilizers/conversions.h b/src/stim/stabilizers/conversions.h index a1fb9fc4d..4349693fd 100644 --- a/src/stim/stabilizers/conversions.h +++ b/src/stim/stabilizers/conversions.h @@ -18,6 +18,8 @@ #define _STIM_STABILIZERS_CONVERSIONS_H #include "stim/circuit/circuit.h" +#include "stim/dem/dem_instruction.h" +#include "stim/stabilizers/flex_pauli_string.h" #include "stim/stabilizers/tableau.h" namespace stim { @@ -179,6 +181,12 @@ double depolarize2_probability_to_independent_per_channel_probability(double p); double independent_per_channel_probability_to_depolarize1_probability(double p); double independent_per_channel_probability_to_depolarize2_probability(double p); +std::map> circuit_to_detecting_regions( + const Circuit &circuit, + std::set included_targets, + std::set included_ticks, + bool ignore_anticommutation_errors); + } // namespace stim #include "stim/stabilizers/conversions.inl" From e275662880e086df0f9b93024135487d0ee03dd0 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 17:32:03 -0700 Subject: [PATCH 4/7] regen --- doc/python_api_reference_vDev.md | 125 ++++++++++++++++++++++++++++++ doc/stim.pyi | 117 ++++++++++++++++++++++++++++ glue/python/src/stim/__init__.pyi | 117 ++++++++++++++++++++++++++++ 3 files changed, 359 insertions(+) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 373736162..504d1b5d7 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -27,6 +27,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.Circuit.compile_sampler`](#stim.Circuit.compile_sampler) - [`stim.Circuit.copy`](#stim.Circuit.copy) - [`stim.Circuit.count_determined_measurements`](#stim.Circuit.count_determined_measurements) + - [`stim.Circuit.detecting_regions`](#stim.Circuit.detecting_regions) - [`stim.Circuit.detector_error_model`](#stim.Circuit.detector_error_model) - [`stim.Circuit.diagram`](#stim.Circuit.diagram) - [`stim.Circuit.explain_detector_error_model_errors`](#stim.Circuit.explain_detector_error_model_errors) @@ -1272,6 +1273,130 @@ def count_determined_measurements( """ ``` + +```python +# stim.Circuit.detecting_regions + +# (in class stim.Circuit) +def detecting_regions( + self, + *, + included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, + included_ticks: None | Iterable[int] = None, +) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: + """Explains how detector error model errors are produced by circuit errors. + + Args: + targets: Defaults to everything (None). + + When specified, this should be an iterable of filters where items + matching any one filter are included. + + A variety of filters are supported: + stim.DemTarget: Includes the targeted detector or observable. + Iterable[float]: Coordinate prefix match. Includes detectors whose + coordinate data begins with the same floats. + "D": Includes all detectors. + "L": Includes all observables. + "D#" (e.g. "D5"): Includes the detector with the specified index. + "L#" (e.g. "L5"): Includes the observable with the specified index. + + ticks: Defaults to everything (None). + When specified, this should be a list of integers corresponding to + the tick indices to report sensitivities for. + + ignore_anticommutation_errors: Defaults to False. + When set to False, invalid detecting regions that anticommute with a + reset will cause the method to raise an exception. When set to True, + the offending component will simply be silently dropped. This can + result in broken detectors having apparently enormous detecting + regions. + + Returns: + Nested dictionaries keyed first by a `stim.DemTarget` identifying the + detector or observable, then by the index of the tick, leading to a + PauliString with that target's error sensitivity at that tick. + + Note you can use `stim.PauliString.pauli_indices` to quickly get to the + non-identity terms in the sensitivity. + + Examples: + >>> import stim + + >>> detecting_regions = stim.Circuit(''' + ... R 0 + ... TICK + ... H 0 + ... TICK + ... CX 0 1 + ... TICK + ... MX 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''').detecting_regions() + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D0 + tick 0 = +Z_ + tick 1 = +X_ + tick 2 = +XX + + >>> circuit = stim.Circuit.generated( + ... "surface_code:rotated_memory_x", + ... rounds=5, + ... distance=4, + ... ) + + >>> detecting_regions = circuit.detecting_regions( + ... targets=["L0", (2, 4), stim.DemTarget.relative_detector_id(5)], + ... ticks=range(5, 15), + ... ) + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D1 + tick 5 = +____________________X______________________ + tick 6 = +____________________Z______________________ + target D5 + tick 5 = +______X____________________________________ + tick 6 = +______Z____________________________________ + target D14 + tick 5 = +__________X_X______XXX_____________________ + tick 6 = +__________X_X______XZX_____________________ + tick 7 = +__________X_X______XZX_____________________ + tick 8 = +__________X_X______XXX_____________________ + tick 9 = +__________XXX_____XXX______________________ + tick 10 = +__________XXX_______X______________________ + tick 11 = +__________X_________X______________________ + tick 12 = +____________________X______________________ + tick 13 = +____________________Z______________________ + target D29 + tick 7 = +____________________Z______________________ + tick 8 = +____________________X______________________ + tick 9 = +____________________XX_____________________ + tick 10 = +___________________XXX_______X_____________ + tick 11 = +____________X______XXXX______X_____________ + tick 12 = +__________X_X______XXX_____________________ + tick 13 = +__________X_X______XZX_____________________ + tick 14 = +__________X_X______XZX_____________________ + target D44 + tick 14 = +____________________Z______________________ + target L0 + tick 5 = +_X________X________X________X______________ + tick 6 = +_X________X________X________X______________ + tick 7 = +_X________X________X________X______________ + tick 8 = +_X________X________X________X______________ + tick 9 = +_X________X_______XX________X______________ + tick 10 = +_X________X________X________X______________ + tick 11 = +_X________XX_______X________XX_____________ + tick 12 = +_X________X________X________X______________ + tick 13 = +_X________X________X________X______________ + tick 14 = +_X________X________X________X______________ + """ +``` + ```python # stim.Circuit.detector_error_model diff --git a/doc/stim.pyi b/doc/stim.pyi index aa2c065b5..aa3f34553 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -716,6 +716,123 @@ class Circuit: >>> circuit.num_detectors + circuit.num_observables 217 """ + def detecting_regions( + self, + *, + included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, + included_ticks: None | Iterable[int] = None, + ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: + """Explains how detector error model errors are produced by circuit errors. + + Args: + targets: Defaults to everything (None). + + When specified, this should be an iterable of filters where items + matching any one filter are included. + + A variety of filters are supported: + stim.DemTarget: Includes the targeted detector or observable. + Iterable[float]: Coordinate prefix match. Includes detectors whose + coordinate data begins with the same floats. + "D": Includes all detectors. + "L": Includes all observables. + "D#" (e.g. "D5"): Includes the detector with the specified index. + "L#" (e.g. "L5"): Includes the observable with the specified index. + + ticks: Defaults to everything (None). + When specified, this should be a list of integers corresponding to + the tick indices to report sensitivities for. + + ignore_anticommutation_errors: Defaults to False. + When set to False, invalid detecting regions that anticommute with a + reset will cause the method to raise an exception. When set to True, + the offending component will simply be silently dropped. This can + result in broken detectors having apparently enormous detecting + regions. + + Returns: + Nested dictionaries keyed first by a `stim.DemTarget` identifying the + detector or observable, then by the index of the tick, leading to a + PauliString with that target's error sensitivity at that tick. + + Note you can use `stim.PauliString.pauli_indices` to quickly get to the + non-identity terms in the sensitivity. + + Examples: + >>> import stim + + >>> detecting_regions = stim.Circuit(''' + ... R 0 + ... TICK + ... H 0 + ... TICK + ... CX 0 1 + ... TICK + ... MX 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''').detecting_regions() + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D0 + tick 0 = +Z_ + tick 1 = +X_ + tick 2 = +XX + + >>> circuit = stim.Circuit.generated( + ... "surface_code:rotated_memory_x", + ... rounds=5, + ... distance=4, + ... ) + + >>> detecting_regions = circuit.detecting_regions( + ... targets=["L0", (2, 4), stim.DemTarget.relative_detector_id(5)], + ... ticks=range(5, 15), + ... ) + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D1 + tick 5 = +____________________X______________________ + tick 6 = +____________________Z______________________ + target D5 + tick 5 = +______X____________________________________ + tick 6 = +______Z____________________________________ + target D14 + tick 5 = +__________X_X______XXX_____________________ + tick 6 = +__________X_X______XZX_____________________ + tick 7 = +__________X_X______XZX_____________________ + tick 8 = +__________X_X______XXX_____________________ + tick 9 = +__________XXX_____XXX______________________ + tick 10 = +__________XXX_______X______________________ + tick 11 = +__________X_________X______________________ + tick 12 = +____________________X______________________ + tick 13 = +____________________Z______________________ + target D29 + tick 7 = +____________________Z______________________ + tick 8 = +____________________X______________________ + tick 9 = +____________________XX_____________________ + tick 10 = +___________________XXX_______X_____________ + tick 11 = +____________X______XXXX______X_____________ + tick 12 = +__________X_X______XXX_____________________ + tick 13 = +__________X_X______XZX_____________________ + tick 14 = +__________X_X______XZX_____________________ + target D44 + tick 14 = +____________________Z______________________ + target L0 + tick 5 = +_X________X________X________X______________ + tick 6 = +_X________X________X________X______________ + tick 7 = +_X________X________X________X______________ + tick 8 = +_X________X________X________X______________ + tick 9 = +_X________X_______XX________X______________ + tick 10 = +_X________X________X________X______________ + tick 11 = +_X________XX_______X________XX_____________ + tick 12 = +_X________X________X________X______________ + tick 13 = +_X________X________X________X______________ + tick 14 = +_X________X________X________X______________ + """ def detector_error_model( self, *, diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index aa2c065b5..aa3f34553 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -716,6 +716,123 @@ class Circuit: >>> circuit.num_detectors + circuit.num_observables 217 """ + def detecting_regions( + self, + *, + included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, + included_ticks: None | Iterable[int] = None, + ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: + """Explains how detector error model errors are produced by circuit errors. + + Args: + targets: Defaults to everything (None). + + When specified, this should be an iterable of filters where items + matching any one filter are included. + + A variety of filters are supported: + stim.DemTarget: Includes the targeted detector or observable. + Iterable[float]: Coordinate prefix match. Includes detectors whose + coordinate data begins with the same floats. + "D": Includes all detectors. + "L": Includes all observables. + "D#" (e.g. "D5"): Includes the detector with the specified index. + "L#" (e.g. "L5"): Includes the observable with the specified index. + + ticks: Defaults to everything (None). + When specified, this should be a list of integers corresponding to + the tick indices to report sensitivities for. + + ignore_anticommutation_errors: Defaults to False. + When set to False, invalid detecting regions that anticommute with a + reset will cause the method to raise an exception. When set to True, + the offending component will simply be silently dropped. This can + result in broken detectors having apparently enormous detecting + regions. + + Returns: + Nested dictionaries keyed first by a `stim.DemTarget` identifying the + detector or observable, then by the index of the tick, leading to a + PauliString with that target's error sensitivity at that tick. + + Note you can use `stim.PauliString.pauli_indices` to quickly get to the + non-identity terms in the sensitivity. + + Examples: + >>> import stim + + >>> detecting_regions = stim.Circuit(''' + ... R 0 + ... TICK + ... H 0 + ... TICK + ... CX 0 1 + ... TICK + ... MX 0 1 + ... DETECTOR rec[-1] rec[-2] + ... ''').detecting_regions() + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D0 + tick 0 = +Z_ + tick 1 = +X_ + tick 2 = +XX + + >>> circuit = stim.Circuit.generated( + ... "surface_code:rotated_memory_x", + ... rounds=5, + ... distance=4, + ... ) + + >>> detecting_regions = circuit.detecting_regions( + ... targets=["L0", (2, 4), stim.DemTarget.relative_detector_id(5)], + ... ticks=range(5, 15), + ... ) + >>> for target, tick_regions in detecting_regions.items(): + ... print("target", target) + ... for tick, sensitivity in tick_regions.items(): + ... print(" tick", tick, "=", sensitivity) + target D1 + tick 5 = +____________________X______________________ + tick 6 = +____________________Z______________________ + target D5 + tick 5 = +______X____________________________________ + tick 6 = +______Z____________________________________ + target D14 + tick 5 = +__________X_X______XXX_____________________ + tick 6 = +__________X_X______XZX_____________________ + tick 7 = +__________X_X______XZX_____________________ + tick 8 = +__________X_X______XXX_____________________ + tick 9 = +__________XXX_____XXX______________________ + tick 10 = +__________XXX_______X______________________ + tick 11 = +__________X_________X______________________ + tick 12 = +____________________X______________________ + tick 13 = +____________________Z______________________ + target D29 + tick 7 = +____________________Z______________________ + tick 8 = +____________________X______________________ + tick 9 = +____________________XX_____________________ + tick 10 = +___________________XXX_______X_____________ + tick 11 = +____________X______XXXX______X_____________ + tick 12 = +__________X_X______XXX_____________________ + tick 13 = +__________X_X______XZX_____________________ + tick 14 = +__________X_X______XZX_____________________ + target D44 + tick 14 = +____________________Z______________________ + target L0 + tick 5 = +_X________X________X________X______________ + tick 6 = +_X________X________X________X______________ + tick 7 = +_X________X________X________X______________ + tick 8 = +_X________X________X________X______________ + tick 9 = +_X________X_______XX________X______________ + tick 10 = +_X________X________X________X______________ + tick 11 = +_X________XX_______X________XX_____________ + tick 12 = +_X________X________X________X______________ + tick 13 = +_X________X________X________X______________ + tick 14 = +_X________X________X________X______________ + """ def detector_error_model( self, *, From 0bbd2327c627057ffe82a5a90813df143065220b Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 17:41:23 -0700 Subject: [PATCH 5/7] Fix doc --- doc/python_api_reference_vDev.md | 13 ++++++++++++- doc/stim.pyi | 13 ++++++++++++- glue/python/src/stim/__init__.pyi | 13 ++++++++++++- src/stim/circuit/circuit.pybind.cc | 13 ++++++++++++- src/stim/circuit/circuit_pybind_test.py | 2 -- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 504d1b5d7..cec5b1f0a 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -1284,7 +1284,18 @@ def detecting_regions( included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Explains how detector error model errors are produced by circuit errors. + """Records where detectors are sensitive to errors over time. + + The result of this method is a nested dictionary, mapping detectors/observables + and ticks to Pauli sensitivities for that detector/observable at that time. + + For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during + tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + `stim.PauliString("Z5*Z6")`. + + If you want sensitivities from more places in the circuit, besides just at the + TICK instructions, you can work around this by making a version of the circuit + with more TICKs. Args: targets: Defaults to everything (None). diff --git a/doc/stim.pyi b/doc/stim.pyi index aa3f34553..aec46559d 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -722,7 +722,18 @@ class Circuit: included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Explains how detector error model errors are produced by circuit errors. + """Records where detectors are sensitive to errors over time. + + The result of this method is a nested dictionary, mapping detectors/observables + and ticks to Pauli sensitivities for that detector/observable at that time. + + For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during + tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + `stim.PauliString("Z5*Z6")`. + + If you want sensitivities from more places in the circuit, besides just at the + TICK instructions, you can work around this by making a version of the circuit + with more TICKs. Args: targets: Defaults to everything (None). diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index aa3f34553..aec46559d 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -722,7 +722,18 @@ class Circuit: included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Explains how detector error model errors are produced by circuit errors. + """Records where detectors are sensitive to errors over time. + + The result of this method is a nested dictionary, mapping detectors/observables + and ticks to Pauli sensitivities for that detector/observable at that time. + + For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during + tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + `stim.PauliString("Z5*Z6")`. + + If you want sensitivities from more places in the circuit, besides just at the + TICK instructions, you can work around this by making a version of the circuit + with more TICKs. Args: targets: Defaults to everything (None). diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index c7bf9b622..1a380fac0 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -2150,7 +2150,18 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - Explains how detector error model errors are produced by circuit errors. + Records where detectors are sensitive to errors over time. + + The result of this method is a nested dictionary, mapping detectors/observables + and ticks to Pauli sensitivities for that detector/observable at that time. + + For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during + tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + `stim.PauliString("Z5*Z6")`. + + If you want sensitivities from more places in the circuit, besides just at the + TICK instructions, you can work around this by making a version of the circuit + with more TICKs. Args: targets: Defaults to everything (None). diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index fac124e5d..28dfa9dec 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -1695,8 +1695,6 @@ def test_has_flow_shorthands(): assert c.has_flow(stim.Flow("iX_ -> iXX xor rec[1] xor rec[3]")) assert not c.has_flow(stim.Flow("-iX_ -> iXX xor rec[1] xor rec[3]")) assert c.has_flow(stim.Flow("-iX_ -> -iXX xor rec[1] xor rec[3]")) - with pytest.raises(ValueError): - c.has_flow("iX_ -> XX") with pytest.raises(ValueError): stim.Flow("iX_ -> XX") From f5d53cb4d48449eb7cb7bbbf658f89e2cbab3ba4 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 17:43:20 -0700 Subject: [PATCH 6/7] typo --- doc/python_api_reference_vDev.md | 4 ++-- doc/stim.pyi | 4 ++-- glue/python/src/stim/__init__.pyi | 4 ++-- src/stim/circuit/circuit.pybind.cc | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index cec5b1f0a..b09e70049 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -1284,13 +1284,13 @@ def detecting_regions( included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Records where detectors are sensitive to errors over time. + """Records where detectors and observables are sensitive to errors over time. The result of this method is a nested dictionary, mapping detectors/observables and ticks to Pauli sensitivities for that detector/observable at that time. For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during - tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + tick 3, then `result[stim.target_logical_observable_id(2)][3]` will be equal to `stim.PauliString("Z5*Z6")`. If you want sensitivities from more places in the circuit, besides just at the diff --git a/doc/stim.pyi b/doc/stim.pyi index aec46559d..9175418de 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -722,13 +722,13 @@ class Circuit: included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Records where detectors are sensitive to errors over time. + """Records where detectors and observables are sensitive to errors over time. The result of this method is a nested dictionary, mapping detectors/observables and ticks to Pauli sensitivities for that detector/observable at that time. For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during - tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + tick 3, then `result[stim.target_logical_observable_id(2)][3]` will be equal to `stim.PauliString("Z5*Z6")`. If you want sensitivities from more places in the circuit, besides just at the diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index aec46559d..9175418de 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -722,13 +722,13 @@ class Circuit: included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, included_ticks: None | Iterable[int] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - """Records where detectors are sensitive to errors over time. + """Records where detectors and observables are sensitive to errors over time. The result of this method is a nested dictionary, mapping detectors/observables and ticks to Pauli sensitivities for that detector/observable at that time. For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during - tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + tick 3, then `result[stim.target_logical_observable_id(2)][3]` will be equal to `stim.PauliString("Z5*Z6")`. If you want sensitivities from more places in the circuit, besides just at the diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 1a380fac0..a29042f8e 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -2150,13 +2150,13 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ Dict[stim.DemTarget, Dict[int, stim.PauliString]]: - Records where detectors are sensitive to errors over time. + Records where detectors and observables are sensitive to errors over time. The result of this method is a nested dictionary, mapping detectors/observables and ticks to Pauli sensitivities for that detector/observable at that time. For example, if observable 2 has Z-type sensitivity on qubits 5 and 6 during - tick 3, then `result[stim.DemTarget.observable(2)][3]` will be equal to + tick 3, then `result[stim.target_logical_observable_id(2)][3]` will be equal to `stim.PauliString("Z5*Z6")`. If you want sensitivities from more places in the circuit, besides just at the From 1f056636724a70d677212651bbd243563839377f Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 11 Mar 2024 17:50:19 -0700 Subject: [PATCH 7/7] type fix --- doc/python_api_reference_vDev.md | 4 ++-- doc/stim.pyi | 4 ++-- glue/python/src/stim/__init__.pyi | 4 ++-- src/stim/circuit/circuit.pybind.cc | 2 +- src/stim/circuit/circuit_pybind_test.py | 10 ++++++++++ 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index b09e70049..d6a474d01 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -1281,8 +1281,8 @@ def count_determined_measurements( def detecting_regions( self, *, - included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, - included_ticks: None | Iterable[int] = None, + targets: Optional[Iterable[stim.DemTarget | str | Iterable[float]]] = None, + ticks: Optional[Iterable[int]] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: """Records where detectors and observables are sensitive to errors over time. diff --git a/doc/stim.pyi b/doc/stim.pyi index 9175418de..fffa1d476 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -719,8 +719,8 @@ class Circuit: def detecting_regions( self, *, - included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, - included_ticks: None | Iterable[int] = None, + targets: Optional[Iterable[stim.DemTarget | str | Iterable[float]]] = None, + ticks: Optional[Iterable[int]] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: """Records where detectors and observables are sensitive to errors over time. diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 9175418de..fffa1d476 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -719,8 +719,8 @@ class Circuit: def detecting_regions( self, *, - included_targets: Iterable[Iterable[float] | str | stim.DemTarget] | None = None, - included_ticks: None | Iterable[int] = None, + targets: Optional[Iterable[stim.DemTarget | str | Iterable[float]]] = None, + ticks: Optional[Iterable[int]] = None, ) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: """Records where detectors and observables are sensitive to errors over time. diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index a29042f8e..7eb911298 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -2149,7 +2149,7 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ Dict[stim.DemTarget, Dict[int, stim.PauliString]]: + @signature def detecting_regions(self, *, targets: Optional[Iterable[stim.DemTarget | str | Iterable[float]]] = None, ticks: Optional[Iterable[int]] = None) -> Dict[stim.DemTarget, Dict[int, stim.PauliString]]: Records where detectors and observables are sensitive to errors over time. The result of this method is a nested dictionary, mapping detectors/observables diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index 28dfa9dec..82dd5f86b 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -1714,3 +1714,13 @@ def test_detecting_regions(): 1: stim.PauliString("X_"), 2: stim.PauliString("XX"), }} + + +def test_detecting_region_filters(): + c = stim.Circuit.generated("repetition_code:memory", distance=3, rounds=3) + assert len(c.detecting_regions(targets=["D"])) == c.num_detectors + assert len(c.detecting_regions(targets=["L"])) == c.num_observables + assert len(c.detecting_regions()) == c.num_observables + c.num_detectors + assert len(c.detecting_regions(targets=["D0"])) == 1 + assert len(c.detecting_regions(targets=["D0", "L0"])) == 2 + assert len(c.detecting_regions(targets=[stim.target_relative_detector_id(0), "D0"])) == 1