diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index 4ba62593..f509eef0 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 cd232b6a..e20cbea0 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 b57d0093..b00287c5 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 cd232b6a..e20cbea0 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 9cc510ed..33caeb81 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 adef47c2..5c1cce13 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 48873543..a1a36bf2 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 00000000..e0339977
--- /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 d0592713..1939d70b 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 a7918ff6..e961bab8 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 9278ae4b..ba7db7f4 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 42361112..39423b58 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 53865e46..f14c2f1e 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 a9f0a5ca..ceac444d 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 facaa8e0..247a713a 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 46c8aa3a..2afbe0e6 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 9d033018..535307ea 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 b71301cc..c95027a0 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 a85efef3..6587750a 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());