From 1efe91b6aeebd420f8e7c5bbfa3c2dfff3dfd44e Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 5 Aug 2024 03:43:20 -0700 Subject: [PATCH] Add `stim.{CircuitInstruction,DemInstruction}.target_groups` (#812) Fixes https://github.com/quantumlib/Stim/issues/811 --- doc/python_api_reference_vDev.md | 127 +++++++++++++++++- doc/stim.pyi | 111 ++++++++++++++- file_lists/test_files | 1 + glue/python/src/stim/__init__.pyi | 111 ++++++++++++++- src/stim/circuit/circuit_instruction.h | 32 +++++ .../circuit/circuit_instruction.pybind.cc | 92 +++++++++++-- src/stim/circuit/circuit_instruction.pybind.h | 1 + src/stim/circuit/circuit_instruction.test.cc | 78 +++++++++++ .../circuit_instruction_pybind_test.py | 19 +++ src/stim/circuit/gate_target.cc | 9 +- src/stim/circuit/gate_target.pybind.cc | 2 +- src/stim/dem/dem_instruction.h | 14 ++ src/stim/dem/dem_instruction.test.cc | 45 +++++++ ...detector_error_model_instruction.pybind.cc | 59 ++++++-- .../detector_error_model_instruction.pybind.h | 17 +-- ...tor_error_model_instruction_pybind_test.py | 19 +-- .../diagram/graph/match_graph_3d_drawer.h | 16 --- src/stim/gates/gate_data_annotations.cc | 14 -- src/stim/simulators/matched_error.pybind.cc | 19 +++ 19 files changed, 693 insertions(+), 93 deletions(-) create mode 100644 src/stim/circuit/circuit_instruction.test.cc diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 4ba62593f..f509eef04 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -82,6 +82,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.CircuitInstruction.gate_args_copy`](#stim.CircuitInstruction.gate_args_copy) - [`stim.CircuitInstruction.name`](#stim.CircuitInstruction.name) - [`stim.CircuitInstruction.num_measurements`](#stim.CircuitInstruction.num_measurements) + - [`stim.CircuitInstruction.target_groups`](#stim.CircuitInstruction.target_groups) - [`stim.CircuitInstruction.targets_copy`](#stim.CircuitInstruction.targets_copy) - [`stim.CircuitRepeatBlock`](#stim.CircuitRepeatBlock) - [`stim.CircuitRepeatBlock.__eq__`](#stim.CircuitRepeatBlock.__eq__) @@ -123,6 +124,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.DemInstruction.__repr__`](#stim.DemInstruction.__repr__) - [`stim.DemInstruction.__str__`](#stim.DemInstruction.__str__) - [`stim.DemInstruction.args_copy`](#stim.DemInstruction.args_copy) + - [`stim.DemInstruction.target_groups`](#stim.DemInstruction.target_groups) - [`stim.DemInstruction.targets_copy`](#stim.DemInstruction.targets_copy) - [`stim.DemInstruction.type`](#stim.DemInstruction.type) - [`stim.DemRepeatBlock`](#stim.DemRepeatBlock) @@ -3664,6 +3666,25 @@ def instruction_targets( """Within the error instruction, which may have hundreds of targets, which specific targets were being executed to produce the error. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> targets = err[0].circuit_error_locations[0].instruction_targets + >>> targets == stim.CircuitTargetsInsideInstruction( + ... gate='Y_ERROR', + ... args=[0.125], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords(0, []),), + ... ) + True """ ``` @@ -3917,6 +3938,17 @@ def gate_args_copy( For noisy gates this typically a list of probabilities. For OBSERVABLE_INCLUDE it's a singleton list containing the logical observable index. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.gate_args_copy() + [0.125] + + >>> instruction.gate_args_copy() == instruction.gate_args_copy() + True + >>> instruction.gate_args_copy() is instruction.gate_args_copy() + False """ ``` @@ -3961,6 +3993,52 @@ def num_measurements( """ ``` + +```python +# stim.CircuitInstruction.target_groups + +# (in class stim.CircuitInstruction) +def target_groups( + self, +) -> List[List[stim.GateTarget]]: + """Splits the instruction's targets into groups depending on the type of gate. + + Single qubit gates like H get one group per target. + Two qubit gates like CX get one group per pair of targets. + Pauli product gates like MPP get one group per combined product. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> for g in stim.Circuit('H 0 1 2')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0)] + [stim.GateTarget(1)] + [stim.GateTarget(2)] + + >>> for g in stim.Circuit('CX 0 1 2 3')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0), stim.GateTarget(1)] + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> for g in stim.Circuit('MPP X0*Y1*Z2 X5*X6')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1), stim.target_z(2)] + [stim.target_x(5), stim.target_x(6)] + + >>> for g in stim.Circuit('DETECTOR rec[-1] rec[-2]')[0].target_groups(): + ... print(repr(g)) + [stim.target_rec(-1)] + [stim.target_rec(-2)] + + >>> for g in stim.Circuit('CORRELATED_ERROR(0.1) X0 Y1')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1)] + """ +``` + ```python # stim.CircuitInstruction.targets_copy @@ -3970,6 +4048,17 @@ def targets_copy( self, ) -> List[stim.GateTarget]: """Returns a copy of the targets of the instruction. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.targets_copy() + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> instruction.targets_copy() == instruction.targets_copy() + True + >>> instruction.targets_copy() is instruction.targets_copy() + False """ ``` @@ -5335,6 +5424,42 @@ def args_copy( """ ``` + +```python +# stim.DemInstruction.target_groups + +# (in class stim.DemInstruction) +def target_groups( + self, +) -> List[List[stim.DemTarget]]: + """Returns a copy of the instruction's targets, split by target separators. + + When a detector error model instruction contains a suggested decomposition, + its targets contain separators (`stim.DemTarget("^")`). This method splits the + targets into groups based the separators, similar to how `str.split` works. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.01) D0 D1 ^ D2 + ... error(0.01) D0 L0 + ... error(0.01) + ... ''') + + >>> dem[0].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('D1')], [stim.DemTarget('D2')]] + + >>> dem[1].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('L0')]] + + >>> dem[2].target_groups() + [[]] + """ +``` + ```python # stim.DemInstruction.targets_copy @@ -8928,7 +9053,7 @@ class GateTarget: >>> circuit[0].targets_copy()[0] stim.GateTarget(0) >>> circuit[0].targets_copy()[1] - stim.GateTarget(stim.target_inv(1)) + stim.target_inv(1) """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index cd232b6a7..e20cbea04 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -2846,6 +2846,25 @@ class CircuitErrorLocation: """Within the error instruction, which may have hundreds of targets, which specific targets were being executed to produce the error. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> targets = err[0].circuit_error_locations[0].instruction_targets + >>> targets == stim.CircuitTargetsInsideInstruction( + ... gate='Y_ERROR', + ... args=[0.125], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords(0, []),), + ... ) + True """ @property def stack_frames( @@ -3001,6 +3020,17 @@ class CircuitInstruction: For noisy gates this typically a list of probabilities. For OBSERVABLE_INCLUDE it's a singleton list containing the logical observable index. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.gate_args_copy() + [0.125] + + >>> instruction.gate_args_copy() == instruction.gate_args_copy() + True + >>> instruction.gate_args_copy() is instruction.gate_args_copy() + False """ @property def name( @@ -3029,10 +3059,60 @@ class CircuitInstruction: >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements 1 """ + def target_groups( + self, + ) -> List[List[stim.GateTarget]]: + """Splits the instruction's targets into groups depending on the type of gate. + + Single qubit gates like H get one group per target. + Two qubit gates like CX get one group per pair of targets. + Pauli product gates like MPP get one group per combined product. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> for g in stim.Circuit('H 0 1 2')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0)] + [stim.GateTarget(1)] + [stim.GateTarget(2)] + + >>> for g in stim.Circuit('CX 0 1 2 3')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0), stim.GateTarget(1)] + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> for g in stim.Circuit('MPP X0*Y1*Z2 X5*X6')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1), stim.target_z(2)] + [stim.target_x(5), stim.target_x(6)] + + >>> for g in stim.Circuit('DETECTOR rec[-1] rec[-2]')[0].target_groups(): + ... print(repr(g)) + [stim.target_rec(-1)] + [stim.target_rec(-2)] + + >>> for g in stim.Circuit('CORRELATED_ERROR(0.1) X0 Y1')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1)] + """ def targets_copy( self, ) -> List[stim.GateTarget]: """Returns a copy of the targets of the instruction. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.targets_copy() + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> instruction.targets_copy() == instruction.targets_copy() + True + >>> instruction.targets_copy() is instruction.targets_copy() + False """ class CircuitRepeatBlock: """A REPEAT block from a circuit. @@ -4172,6 +4252,35 @@ class DemInstruction: >>> instruction.args_copy() is instruction.args_copy() False """ + def target_groups( + self, + ) -> List[List[stim.DemTarget]]: + """Returns a copy of the instruction's targets, split by target separators. + + When a detector error model instruction contains a suggested decomposition, + its targets contain separators (`stim.DemTarget("^")`). This method splits the + targets into groups based the separators, similar to how `str.split` works. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.01) D0 D1 ^ D2 + ... error(0.01) D0 L0 + ... error(0.01) + ... ''') + + >>> dem[0].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('D1')], [stim.DemTarget('D2')]] + + >>> dem[1].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('L0')]] + + >>> dem[2].target_groups() + [[]] + """ def targets_copy( self, ) -> List[Union[int, stim.DemTarget]]: @@ -6976,7 +7085,7 @@ class GateTarget: >>> circuit[0].targets_copy()[0] stim.GateTarget(0) >>> circuit[0].targets_copy()[1] - stim.GateTarget(stim.target_inv(1)) + stim.target_inv(1) """ def __eq__( self, diff --git a/file_lists/test_files b/file_lists/test_files index b57d00935..b00287c50 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -1,5 +1,6 @@ src/stim.test.cc src/stim/circuit/circuit.test.cc +src/stim/circuit/circuit_instruction.test.cc src/stim/circuit/gate_decomposition.test.cc src/stim/circuit/gate_target.test.cc src/stim/cmd/command_analyze_errors.test.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index cd232b6a7..e20cbea04 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -2846,6 +2846,25 @@ class CircuitErrorLocation: """Within the error instruction, which may have hundreds of targets, which specific targets were being executed to produce the error. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> targets = err[0].circuit_error_locations[0].instruction_targets + >>> targets == stim.CircuitTargetsInsideInstruction( + ... gate='Y_ERROR', + ... args=[0.125], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords(0, []),), + ... ) + True """ @property def stack_frames( @@ -3001,6 +3020,17 @@ class CircuitInstruction: For noisy gates this typically a list of probabilities. For OBSERVABLE_INCLUDE it's a singleton list containing the logical observable index. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.gate_args_copy() + [0.125] + + >>> instruction.gate_args_copy() == instruction.gate_args_copy() + True + >>> instruction.gate_args_copy() is instruction.gate_args_copy() + False """ @property def name( @@ -3029,10 +3059,60 @@ class CircuitInstruction: >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements 1 """ + def target_groups( + self, + ) -> List[List[stim.GateTarget]]: + """Splits the instruction's targets into groups depending on the type of gate. + + Single qubit gates like H get one group per target. + Two qubit gates like CX get one group per pair of targets. + Pauli product gates like MPP get one group per combined product. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> for g in stim.Circuit('H 0 1 2')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0)] + [stim.GateTarget(1)] + [stim.GateTarget(2)] + + >>> for g in stim.Circuit('CX 0 1 2 3')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0), stim.GateTarget(1)] + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> for g in stim.Circuit('MPP X0*Y1*Z2 X5*X6')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1), stim.target_z(2)] + [stim.target_x(5), stim.target_x(6)] + + >>> for g in stim.Circuit('DETECTOR rec[-1] rec[-2]')[0].target_groups(): + ... print(repr(g)) + [stim.target_rec(-1)] + [stim.target_rec(-2)] + + >>> for g in stim.Circuit('CORRELATED_ERROR(0.1) X0 Y1')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1)] + """ def targets_copy( self, ) -> List[stim.GateTarget]: """Returns a copy of the targets of the instruction. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.targets_copy() + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> instruction.targets_copy() == instruction.targets_copy() + True + >>> instruction.targets_copy() is instruction.targets_copy() + False """ class CircuitRepeatBlock: """A REPEAT block from a circuit. @@ -4172,6 +4252,35 @@ class DemInstruction: >>> instruction.args_copy() is instruction.args_copy() False """ + def target_groups( + self, + ) -> List[List[stim.DemTarget]]: + """Returns a copy of the instruction's targets, split by target separators. + + When a detector error model instruction contains a suggested decomposition, + its targets contain separators (`stim.DemTarget("^")`). This method splits the + targets into groups based the separators, similar to how `str.split` works. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.01) D0 D1 ^ D2 + ... error(0.01) D0 L0 + ... error(0.01) + ... ''') + + >>> dem[0].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('D1')], [stim.DemTarget('D2')]] + + >>> dem[1].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('L0')]] + + >>> dem[2].target_groups() + [[]] + """ def targets_copy( self, ) -> List[Union[int, stim.DemTarget]]: @@ -6976,7 +7085,7 @@ class GateTarget: >>> circuit[0].targets_copy()[0] stim.GateTarget(0) >>> circuit[0].targets_copy()[1] - stim.GateTarget(stim.target_inv(1)) + stim.target_inv(1) """ def __eq__( self, diff --git a/src/stim/circuit/circuit_instruction.h b/src/stim/circuit/circuit_instruction.h index 9cc510ed3..33caeb81a 100644 --- a/src/stim/circuit/circuit_instruction.h +++ b/src/stim/circuit/circuit_instruction.h @@ -109,6 +109,38 @@ struct CircuitInstruction { /// Raises: /// std::invalid_argument: Validation failed. void validate() const; + + template + inline void for_combined_target_groups(CALLBACK callback) const { + auto flags = GATE_DATA[gate_type].flags; + size_t start = 0; + while (start < targets.size()) { + size_t end; + if (flags & stim::GateFlags::GATE_TARGETS_COMBINERS) { + end = start + 1; + while (end < targets.size() && targets[end].is_combiner()) { + end += 2; + } + } else if (flags & stim::GateFlags::GATE_IS_SINGLE_QUBIT_GATE) { + end = start + 1; + } else if (flags & stim::GateFlags::GATE_TARGETS_PAIRS) { + end = start + 2; + } else if ((flags & stim::GateFlags::GATE_TARGETS_PAULI_STRING) && !(flags & stim::GateFlags::GATE_TARGETS_COMBINERS)) { + // like CORRELATED_ERROR + end = targets.size(); + } else if (flags & stim::GateFlags::GATE_ONLY_TARGETS_MEASUREMENT_RECORD) { + // like DETECTOR + end = start + 1; + } else if (gate_type == GateType::MPAD || gate_type == GateType::QUBIT_COORDS) { + end = start + 1; + } else { + throw std::invalid_argument("Not implemented: splitting " + str()); + } + std::span group = targets.sub(start, end); + callback(group); + start = end; + } + } }; } // namespace stim diff --git a/src/stim/circuit/circuit_instruction.pybind.cc b/src/stim/circuit/circuit_instruction.pybind.cc index adef47c2d..5c1cce139 100644 --- a/src/stim/circuit/circuit_instruction.pybind.cc +++ b/src/stim/circuit/circuit_instruction.pybind.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/circuit/circuit_instruction.pybind.h" #include "stim/circuit/gate_target.pybind.h" @@ -90,6 +76,19 @@ std::vector PyCircuitInstruction::targets_copy() const { std::vector PyCircuitInstruction::gate_args_copy() const { return gate_args; } +std::vector> PyCircuitInstruction::target_groups() const { + std::vector> results; + as_operation_ref().for_combined_target_groups([&](std::span group) { + std::vector copy; + for (auto g : group) { + if (!g.is_combiner()) { + copy.push_back(g); + } + } + results.push_back(std::move(copy)); + }); + return results; +} pybind11::class_ stim_pybind::pybind_circuit_instruction(pybind11::module &m) { return pybind11::class_( @@ -142,11 +141,65 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind )DOC") .data()); + c.def( + "target_groups", + &PyCircuitInstruction::target_groups, + clean_doc_string(R"DOC( + @signature def target_groups(self) -> List[List[stim.GateTarget]]: + Splits the instruction's targets into groups depending on the type of gate. + + Single qubit gates like H get one group per target. + Two qubit gates like CX get one group per pair of targets. + Pauli product gates like MPP get one group per combined product. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> for g in stim.Circuit('H 0 1 2')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0)] + [stim.GateTarget(1)] + [stim.GateTarget(2)] + + >>> for g in stim.Circuit('CX 0 1 2 3')[0].target_groups(): + ... print(repr(g)) + [stim.GateTarget(0), stim.GateTarget(1)] + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> for g in stim.Circuit('MPP X0*Y1*Z2 X5*X6')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1), stim.target_z(2)] + [stim.target_x(5), stim.target_x(6)] + + >>> for g in stim.Circuit('DETECTOR rec[-1] rec[-2]')[0].target_groups(): + ... print(repr(g)) + [stim.target_rec(-1)] + [stim.target_rec(-2)] + + >>> for g in stim.Circuit('CORRELATED_ERROR(0.1) X0 Y1')[0].target_groups(): + ... print(repr(g)) + [stim.target_x(0), stim.target_y(1)] + )DOC") + .data()); + c.def( "targets_copy", &PyCircuitInstruction::targets_copy, clean_doc_string(R"DOC( Returns a copy of the targets of the instruction. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.targets_copy() + [stim.GateTarget(2), stim.GateTarget(3)] + + >>> instruction.targets_copy() == instruction.targets_copy() + True + >>> instruction.targets_copy() is instruction.targets_copy() + False )DOC") .data()); @@ -159,6 +212,17 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind For noisy gates this typically a list of probabilities. For OBSERVABLE_INCLUDE it's a singleton list containing the logical observable index. + + Examples: + >>> import stim + >>> instruction = stim.CircuitInstruction('X_ERROR', [2, 3], [0.125]) + >>> instruction.gate_args_copy() + [0.125] + + >>> instruction.gate_args_copy() == instruction.gate_args_copy() + True + >>> instruction.gate_args_copy() is instruction.gate_args_copy() + False )DOC") .data()); diff --git a/src/stim/circuit/circuit_instruction.pybind.h b/src/stim/circuit/circuit_instruction.pybind.h index 48873543b..a1a36bf28 100644 --- a/src/stim/circuit/circuit_instruction.pybind.h +++ b/src/stim/circuit/circuit_instruction.pybind.h @@ -39,6 +39,7 @@ struct PyCircuitInstruction { std::vector targets_copy() const; std::vector gate_args_copy() const; std::vector raw_targets() const; + std::vector> target_groups() const; bool operator==(const PyCircuitInstruction &other) const; bool operator!=(const PyCircuitInstruction &other) const; diff --git a/src/stim/circuit/circuit_instruction.test.cc b/src/stim/circuit/circuit_instruction.test.cc new file mode 100644 index 000000000..e03399774 --- /dev/null +++ b/src/stim/circuit/circuit_instruction.test.cc @@ -0,0 +1,78 @@ +#include "stim/circuit/circuit_instruction.h" + +#include "gtest/gtest.h" + +#include "stim/circuit/circuit.h" +#include "stim/circuit/circuit.test.h" + +using namespace stim; + +TEST(circuit_instruction, for_combined_targets) { + Circuit circuit(R"CIRCUIT( + X + CX + S 1 + H 0 2 + TICK + CX 0 1 2 3 + CY 3 5 + SPP + MPP X0*X1*Z2 Z7 X5*X9 + SPP Z5 + )CIRCUIT"); + auto get_k = [&](size_t k) { + std::vector> results; + circuit.operations[k].for_combined_target_groups([&](std::span group) { + std::vector items; + for (auto g : group) { + items.push_back(g); + } + results.push_back(items); + }); + return results; + }; + ASSERT_EQ(get_k(0), (std::vector>{ + })); + ASSERT_EQ(get_k(1), (std::vector>{ + })); + ASSERT_EQ(get_k(2), (std::vector>{ + {GateTarget::qubit(1)}, + })); + ASSERT_EQ(get_k(3), (std::vector>{ + {GateTarget::qubit(0)}, + {GateTarget::qubit(2)}, + })); + ASSERT_EQ(get_k(4), (std::vector>{ + })); + ASSERT_EQ(get_k(5), (std::vector>{ + {GateTarget::qubit(0), GateTarget::qubit(1)}, + {GateTarget::qubit(2), GateTarget::qubit(3)}, + })); + ASSERT_EQ(get_k(6), (std::vector>{ + {GateTarget::qubit(3), GateTarget::qubit(5)}, + })); + ASSERT_EQ(get_k(7), (std::vector>{ + })); + ASSERT_EQ(get_k(8), (std::vector>{ + {GateTarget::x(0), GateTarget::combiner(), GateTarget::x(1), GateTarget::combiner(), GateTarget::z(2)}, + {GateTarget::z(7)}, + {GateTarget::x(5), GateTarget::combiner(), GateTarget::x(9)}, + })); + ASSERT_EQ(get_k(9), (std::vector>{ + {GateTarget::z(5)}, + })); +} + +TEST(circuit_instruction, for_combined_targets_works_on_all) { + Circuit c = generate_test_circuit_with_all_operations(); + size_t count = 0; + for (const auto &e : c.operations) { + if (e.gate_type == GateType::REPEAT) { + continue; + } + e.for_combined_target_groups([&](std::span group) { + count += group.size(); + }); + } + ASSERT_TRUE(count > 0); +} diff --git a/src/stim/circuit/circuit_instruction_pybind_test.py b/src/stim/circuit/circuit_instruction_pybind_test.py index d0592713b..1939d70bf 100644 --- a/src/stim/circuit/circuit_instruction_pybind_test.py +++ b/src/stim/circuit/circuit_instruction_pybind_test.py @@ -57,3 +57,22 @@ def test_num_measurements(): assert stim.CircuitInstruction("MXX", [1, 2]).num_measurements == 1 assert stim.CircuitInstruction("M", [1, 2]).num_measurements == 2 assert stim.CircuitInstruction("MPAD", [0, 1, 0]).num_measurements == 3 + + +def test_target_groups(): + assert stim.CircuitInstruction("MPAD", [0, 1, 0]).target_groups() == [ + [stim.GateTarget(0)], + [stim.GateTarget(1)], + [stim.GateTarget(0)], + ] + assert stim.CircuitInstruction("H", []).target_groups() == [] + assert stim.CircuitInstruction("H", [1]).target_groups() == [[stim.GateTarget(1)]] + assert stim.CircuitInstruction("H", [2, 3]).target_groups() == [[stim.GateTarget(2)], [stim.GateTarget(3)]] + assert stim.CircuitInstruction("CX", []).target_groups() == [] + assert stim.CircuitInstruction("CX", [0, 1]).target_groups() == [[stim.GateTarget(0), stim.GateTarget(1)]] + assert stim.CircuitInstruction("CX", [2, 3, 5, 7]).target_groups() == [[stim.GateTarget(2), stim.GateTarget(3)], [stim.GateTarget(5), stim.GateTarget(7)]] + assert stim.CircuitInstruction("DETECTOR", []).target_groups() == [] + assert stim.CircuitInstruction("CORRELATED_ERROR", [], [0.001]).target_groups() == [] + assert stim.CircuitInstruction("MPP", []).target_groups() == [] + assert stim.CircuitInstruction("MPAD", []).target_groups() == [] + assert stim.CircuitInstruction("QUBIT_COORDS", [1, 2]).target_groups() == [[stim.GateTarget(1)], [stim.GateTarget(2)]] diff --git a/src/stim/circuit/gate_target.cc b/src/stim/circuit/gate_target.cc index a7918ff60..e961bab83 100644 --- a/src/stim/circuit/gate_target.cc +++ b/src/stim/circuit/gate_target.cc @@ -178,9 +178,14 @@ std::string GateTarget::str() const { std::string GateTarget::repr() const { std::stringstream ss; - ss << "stim.GateTarget("; + bool need_wrap = is_qubit_target() && !is_inverted_result_target(); + if (need_wrap) { + ss << "stim.GateTarget("; + } ss << *this; - ss << ")"; + if (need_wrap) { + ss << ")"; + } return ss.str(); } diff --git a/src/stim/circuit/gate_target.pybind.cc b/src/stim/circuit/gate_target.pybind.cc index 9278ae4b8..ba7db7f43 100644 --- a/src/stim/circuit/gate_target.pybind.cc +++ b/src/stim/circuit/gate_target.pybind.cc @@ -52,7 +52,7 @@ pybind11::class_ stim_pybind::pybind_circuit_gate_target(pybin >>> circuit[0].targets_copy()[0] stim.GateTarget(0) >>> circuit[0].targets_copy()[1] - stim.GateTarget(stim.target_inv(1)) + stim.target_inv(1) )DOC") .data()); } diff --git a/src/stim/dem/dem_instruction.h b/src/stim/dem/dem_instruction.h index 423611126..39423b586 100644 --- a/src/stim/dem/dem_instruction.h +++ b/src/stim/dem/dem_instruction.h @@ -61,6 +61,20 @@ struct DemInstruction { uint64_t repeat_block_rep_count() const; const DetectorErrorModel &repeat_block_body(const DetectorErrorModel &host) const; DetectorErrorModel &repeat_block_body(DetectorErrorModel &host) const; + + template + inline void for_separated_targets(CALLBACK callback) const { + size_t start = 0; + do { + size_t end = start + 1; + while (end < target_data.size() && !target_data[end].is_separator()) { + end++; + } + std::span group = target_data.sub(start, std::min(end, target_data.size())); + callback(group); + start = end + 1; + } while (start < target_data.size()); + } }; std::ostream &operator<<(std::ostream &out, const DemInstructionType &type); diff --git a/src/stim/dem/dem_instruction.test.cc b/src/stim/dem/dem_instruction.test.cc index 53865e465..f14c2f1e1 100644 --- a/src/stim/dem/dem_instruction.test.cc +++ b/src/stim/dem/dem_instruction.test.cc @@ -2,6 +2,8 @@ #include "gtest/gtest.h" +#include "stim/dem/detector_error_model.h" + using namespace stim; TEST(dem_instruction, from_str) { @@ -29,3 +31,46 @@ TEST(dem_instruction, from_str) { ASSERT_THROW({ DemTarget::from_text("'"); }, std::invalid_argument); ASSERT_THROW({ DemTarget::from_text(" "); }, std::invalid_argument); } + +TEST(dem_instruction, for_separated_targets) { + DetectorErrorModel dem("error(0.1) D0 ^ D2 L0 ^ D1 D2 D3"); + std::vector> results; + dem.instructions[0].for_separated_targets([&](std::span group) { + std::vector items; + for (auto g : group) { + items.push_back(g); + } + results.push_back(items); + }); + ASSERT_EQ(results, (std::vector>{ + {DemTarget::relative_detector_id(0)}, + {DemTarget::relative_detector_id(2), DemTarget::observable_id(0)}, + {DemTarget::relative_detector_id(1), DemTarget::relative_detector_id(2), DemTarget::relative_detector_id(3)}, + })); + + dem = DetectorErrorModel("error(0.1) D0"); + results.clear(); + dem.instructions[0].for_separated_targets([&](std::span group) { + std::vector items; + for (auto g : group) { + items.push_back(g); + } + results.push_back(items); + }); + ASSERT_EQ(results, (std::vector>{ + {DemTarget::relative_detector_id(0)}, + })); + + dem = DetectorErrorModel("error(0.1)"); + results.clear(); + dem.instructions[0].for_separated_targets([&](std::span group) { + std::vector items; + for (auto g : group) { + items.push_back(g); + } + results.push_back(items); + }); + ASSERT_EQ(results, (std::vector>{ + {}, + })); +} diff --git a/src/stim/dem/detector_error_model_instruction.pybind.cc b/src/stim/dem/detector_error_model_instruction.pybind.cc index a9f0a5cad..ceac444de 100644 --- a/src/stim/dem/detector_error_model_instruction.pybind.cc +++ b/src/stim/dem/detector_error_model_instruction.pybind.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/dem/detector_error_model_instruction.pybind.h" #include "stim/dem/detector_error_model_target.pybind.h" @@ -21,6 +7,18 @@ using namespace stim; using namespace stim_pybind; +std::vector> ExposedDemInstruction::target_groups() const { + std::vector> result; + as_dem_instruction().for_separated_targets([&](std::span group) { + std::vector copy; + for (auto e : group) { + copy.push_back(e); + } + result.push_back(copy); + }); + return result; +} + DemInstruction ExposedDemInstruction::as_dem_instruction() const { return DemInstruction{arguments, targets, type}; } @@ -205,6 +203,39 @@ void stim_pybind::pybind_detector_error_model_instruction_methods( )DOC") .data()); + c.def( + "target_groups", + &ExposedDemInstruction::target_groups, + clean_doc_string(R"DOC( + @signature def target_groups(self) -> List[List[stim.DemTarget]]: + Returns a copy of the instruction's targets, split by target separators. + + When a detector error model instruction contains a suggested decomposition, + its targets contain separators (`stim.DemTarget("^")`). This method splits the + targets into groups based the separators, similar to how `str.split` works. + + Returns: + A list of groups of targets. + + Examples: + >>> import stim + >>> dem = stim.DetectorErrorModel(''' + ... error(0.01) D0 D1 ^ D2 + ... error(0.01) D0 L0 + ... error(0.01) + ... ''') + + >>> dem[0].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('D1')], [stim.DemTarget('D2')]] + + >>> dem[1].target_groups() + [[stim.DemTarget('D0'), stim.DemTarget('L0')]] + + >>> dem[2].target_groups() + [[]] + )DOC") + .data()); + c.def( "targets_copy", &ExposedDemInstruction::targets_copy, diff --git a/src/stim/dem/detector_error_model_instruction.pybind.h b/src/stim/dem/detector_error_model_instruction.pybind.h index facaa8e06..247a713a5 100644 --- a/src/stim/dem/detector_error_model_instruction.pybind.h +++ b/src/stim/dem/detector_error_model_instruction.pybind.h @@ -1,23 +1,9 @@ -// 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. - #ifndef _STIM_DEM_DETECTOR_ERROR_MODEL_INSTRUCTION_PYBIND_H #define _STIM_DEM_DETECTOR_ERROR_MODEL_INSTRUCTION_PYBIND_H #include -#include "stim/dem/detector_error_model.h" +#include "stim/dem/detector_error_model_target.pybind.h" namespace stim_pybind { @@ -26,6 +12,7 @@ struct ExposedDemInstruction { std::vector targets; stim::DemInstructionType type; + std::vector> target_groups() const; std::vector args_copy() const; std::vector targets_copy() const; stim::DemInstruction as_dem_instruction() const; diff --git a/src/stim/dem/detector_error_model_instruction_pybind_test.py b/src/stim/dem/detector_error_model_instruction_pybind_test.py index 46c8aa3ad..2afbe0e6d 100644 --- a/src/stim/dem/detector_error_model_instruction_pybind_test.py +++ b/src/stim/dem/detector_error_model_instruction_pybind_test.py @@ -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. - import stim import pytest @@ -87,3 +73,8 @@ def test_hashable(): c = stim.DemInstruction("error", [0.25], [stim.target_relative_detector_id(3)]) assert hash(a) == hash(c) assert len({a, b, c}) == 2 + + +def test_target_groups(): + dem = stim.DetectorErrorModel("detector D0") + assert dem[0].target_groups() == [[stim.DemTarget("D0")]] diff --git a/src/stim/diagram/graph/match_graph_3d_drawer.h b/src/stim/diagram/graph/match_graph_3d_drawer.h index 9d0330188..535307ea4 100644 --- a/src/stim/diagram/graph/match_graph_3d_drawer.h +++ b/src/stim/diagram/graph/match_graph_3d_drawer.h @@ -1,19 +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. - */ - #ifndef _STIM_DIAGRAM_GRAPH_MATCH_GRAPH_DRAWER_H #define _STIM_DIAGRAM_GRAPH_MATCH_GRAPH_DRAWER_H diff --git a/src/stim/gates/gate_data_annotations.cc b/src/stim/gates/gate_data_annotations.cc index b71301ccc..c95027a00 100644 --- a/src/stim/gates/gate_data_annotations.cc +++ b/src/stim/gates/gate_data_annotations.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/gates/gates.h" using namespace stim; diff --git a/src/stim/simulators/matched_error.pybind.cc b/src/stim/simulators/matched_error.pybind.cc index a85efef3d..6587750aa 100644 --- a/src/stim/simulators/matched_error.pybind.cc +++ b/src/stim/simulators/matched_error.pybind.cc @@ -624,6 +624,25 @@ void stim_pybind::pybind_circuit_error_location_methods( Within the error instruction, which may have hundreds of targets, which specific targets were being executed to produce the error. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> targets = err[0].circuit_error_locations[0].instruction_targets + >>> targets == stim.CircuitTargetsInsideInstruction( + ... gate='Y_ERROR', + ... args=[0.125], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords(0, []),), + ... ) + True )DOC") .data());