diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 00e8d438..a9a7f926 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -91,6 +91,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.CircuitRepeatBlock.__repr__`](#stim.CircuitRepeatBlock.__repr__) - [`stim.CircuitRepeatBlock.body_copy`](#stim.CircuitRepeatBlock.body_copy) - [`stim.CircuitRepeatBlock.name`](#stim.CircuitRepeatBlock.name) + - [`stim.CircuitRepeatBlock.num_measurements`](#stim.CircuitRepeatBlock.num_measurements) - [`stim.CircuitRepeatBlock.repeat_count`](#stim.CircuitRepeatBlock.repeat_count) - [`stim.CircuitTargetsInsideInstruction`](#stim.CircuitTargetsInsideInstruction) - [`stim.CircuitTargetsInsideInstruction.__init__`](#stim.CircuitTargetsInsideInstruction.__init__) @@ -3762,6 +3763,30 @@ class CircuitErrorLocationStackFrame: The full location of an instruction is a list of these frames, drilling down from the top level circuit to the inner-most loop that the instruction is within. + + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0] + stim.CircuitErrorLocationStackFrame( + instruction_offset=0, + iteration_index=0, + instruction_repetitions_arg=5, + ) + >>> err[0].circuit_error_locations[0].stack_frames[1] + stim.CircuitErrorLocationStackFrame( + instruction_offset=1, + iteration_index=4, + instruction_repetitions_arg=0, + ) """ ``` @@ -3778,6 +3803,14 @@ def __init__( instruction_repetitions_arg: int, ) -> None: """Creates a stim.CircuitErrorLocationStackFrame. + + Examples: + >>> import stim + >>> frame = stim.CircuitErrorLocationStackFrame( + ... instruction_offset=1, + ... iteration_index=2, + ... instruction_repetitions_arg=3, + ... ) """ ``` @@ -3795,6 +3828,18 @@ def instruction_offset( from the line number, because blank lines and commented lines don't count and also because the offset of the first instruction is 0 instead of 1. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0].instruction_offset + 2 """ ``` @@ -3810,6 +3855,23 @@ def instruction_repetitions_arg( """If the instruction being referred to is a REPEAT block, this is the repetition count of that REPEAT block. Otherwise this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.instruction_repetitions_arg + 5 + >>> loop.instruction_repetitions_arg + 0 """ ``` @@ -3825,6 +3887,23 @@ def iteration_index( """Disambiguates which iteration of the loop containing this instruction is being referred to. If the instruction isn't in a REPEAT block, this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.iteration_index + 0 + >>> loop.iteration_index + 4 """ ``` @@ -3873,19 +3952,30 @@ def __eq__( def __init__( self, name: str, - targets: List[object], - gate_args: List[float] = (), + targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, + gate_args: Optional[Iterable[float]] = None, ) -> None: - """Initializes a `stim.CircuitInstruction`. + """Creates or parses a `stim.CircuitInstruction`. Args: name: The name of the instruction being applied. + If `targets` and `gate_args` aren't specified, this can be a full + instruction line from a stim Circuit file, like "CX 0 1". targets: The targets the instruction is being applied to. These can be raw values like `0` and `stim.target_rec(-1)`, or instances of `stim.GateTarget`. gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + + Examples: + >>> import stim + + >>> print(stim.CircuitInstruction('DEPOLARIZE1', [5], [0.25])) + DEPOLARIZE1(0.25) 5 + + >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') + stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) """ ``` @@ -3989,7 +4079,7 @@ def num_measurements( 3 >>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements 2 - >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements + >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 """ ``` @@ -4207,6 +4297,27 @@ def name( """ ``` + +```python +# stim.CircuitRepeatBlock.num_measurements + +# (in class stim.CircuitRepeatBlock) +@property +def num_measurements( + self, +) -> int: + """Returns the number of bits produced when running this loop. + + Examples: + >>> import stim + >>> stim.CircuitRepeatBlock( + ... body=stim.Circuit("M 0 1"), + ... repeat_count=25, + ... ).num_measurements + 50 + """ +``` + ```python # stim.CircuitRepeatBlock.repeat_count @@ -5336,13 +5447,15 @@ def __eq__( def __init__( self, type: str, - args: List[float], - targets: List[object], + args: Optional[Iterable[float]] = None, + targets: Optional[Iterable[stim.DemTarget]] = None, ) -> None: - """Creates a stim.DemInstruction. + """Creates or parses a stim.DemInstruction. Args: type: The name of the instruction type (e.g. "error" or "shift_detectors"). + If `args` and `targets` aren't specified, this can also be set to a + full line of text from a dem file, like "error(0.25) D0". args: Numeric values parameterizing the instruction (e.g. the 0.1 in "error(0.1)"). targets: The objects the instruction involves (e.g. the "D0" and "L1" in @@ -5356,6 +5469,9 @@ def __init__( ... [stim.target_relative_detector_id(5)]) >>> print(instruction) error(0.125) D5 + + >>> print(stim.DemInstruction('error(0.125) D5 L6 ^ D4 # comment')) + error(0.125) D5 L6 ^ D4 """ ``` diff --git a/doc/stim.pyi b/doc/stim.pyi index 83e57b43..370123ac 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -2921,6 +2921,30 @@ class CircuitErrorLocationStackFrame: The full location of an instruction is a list of these frames, drilling down from the top level circuit to the inner-most loop that the instruction is within. + + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0] + stim.CircuitErrorLocationStackFrame( + instruction_offset=0, + iteration_index=0, + instruction_repetitions_arg=5, + ) + >>> err[0].circuit_error_locations[0].stack_frames[1] + stim.CircuitErrorLocationStackFrame( + instruction_offset=1, + iteration_index=4, + instruction_repetitions_arg=0, + ) """ def __init__( self, @@ -2930,6 +2954,14 @@ class CircuitErrorLocationStackFrame: instruction_repetitions_arg: int, ) -> None: """Creates a stim.CircuitErrorLocationStackFrame. + + Examples: + >>> import stim + >>> frame = stim.CircuitErrorLocationStackFrame( + ... instruction_offset=1, + ... iteration_index=2, + ... instruction_repetitions_arg=3, + ... ) """ @property def instruction_offset( @@ -2940,6 +2972,18 @@ class CircuitErrorLocationStackFrame: from the line number, because blank lines and commented lines don't count and also because the offset of the first instruction is 0 instead of 1. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0].instruction_offset + 2 """ @property def instruction_repetitions_arg( @@ -2948,6 +2992,23 @@ class CircuitErrorLocationStackFrame: """If the instruction being referred to is a REPEAT block, this is the repetition count of that REPEAT block. Otherwise this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.instruction_repetitions_arg + 5 + >>> loop.instruction_repetitions_arg + 0 """ @property def iteration_index( @@ -2956,6 +3017,23 @@ class CircuitErrorLocationStackFrame: """Disambiguates which iteration of the loop containing this instruction is being referred to. If the instruction isn't in a REPEAT block, this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.iteration_index + 0 + >>> loop.iteration_index + 4 """ class CircuitInstruction: """An instruction, like `H 0 1` or `CNOT rec[-1] 5`, from a circuit. @@ -2983,19 +3061,30 @@ class CircuitInstruction: def __init__( self, name: str, - targets: List[object], - gate_args: List[float] = (), + targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, + gate_args: Optional[Iterable[float]] = None, ) -> None: - """Initializes a `stim.CircuitInstruction`. + """Creates or parses a `stim.CircuitInstruction`. Args: name: The name of the instruction being applied. + If `targets` and `gate_args` aren't specified, this can be a full + instruction line from a stim Circuit file, like "CX 0 1". targets: The targets the instruction is being applied to. These can be raw values like `0` and `stim.target_rec(-1)`, or instances of `stim.GateTarget`. gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + + Examples: + >>> import stim + + >>> print(stim.CircuitInstruction('DEPOLARIZE1', [5], [0.25])) + DEPOLARIZE1(0.25) 5 + + >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') + stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) """ def __ne__( self, @@ -3057,7 +3146,7 @@ class CircuitInstruction: 3 >>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements 2 - >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements + >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 """ def target_groups( @@ -3211,6 +3300,20 @@ class CircuitRepeatBlock: ['H', 'REPEAT', 'S'] """ @property + def num_measurements( + self, + ) -> int: + """Returns the number of bits produced when running this loop. + + Examples: + >>> import stim + >>> stim.CircuitRepeatBlock( + ... body=stim.Circuit("M 0 1"), + ... repeat_count=25, + ... ).num_measurements + 50 + """ + @property def repeat_count( self, ) -> int: @@ -4194,13 +4297,15 @@ class DemInstruction: def __init__( self, type: str, - args: List[float], - targets: List[object], + args: Optional[Iterable[float]] = None, + targets: Optional[Iterable[stim.DemTarget]] = None, ) -> None: - """Creates a stim.DemInstruction. + """Creates or parses a stim.DemInstruction. Args: type: The name of the instruction type (e.g. "error" or "shift_detectors"). + If `args` and `targets` aren't specified, this can also be set to a + full line of text from a dem file, like "error(0.25) D0". args: Numeric values parameterizing the instruction (e.g. the 0.1 in "error(0.1)"). targets: The objects the instruction involves (e.g. the "D0" and "L1" in @@ -4214,6 +4319,9 @@ class DemInstruction: ... [stim.target_relative_detector_id(5)]) >>> print(instruction) error(0.125) D5 + + >>> print(stim.DemInstruction('error(0.125) D5 L6 ^ D4 # comment')) + error(0.125) D5 L6 ^ D4 """ def __ne__( self, diff --git a/file_lists/pybind_files b/file_lists/pybind_files index 068bbe0f..f29ee5ed 100644 --- a/file_lists/pybind_files +++ b/file_lists/pybind_files @@ -3,8 +3,8 @@ src/stim/circuit/circuit_instruction.pybind.cc src/stim/circuit/circuit_repeat_block.pybind.cc src/stim/circuit/gate_target.pybind.cc src/stim/cmd/command_diagram.pybind.cc +src/stim/dem/dem_instruction.pybind.cc src/stim/dem/detector_error_model.pybind.cc -src/stim/dem/detector_error_model_instruction.pybind.cc src/stim/dem/detector_error_model_repeat_block.pybind.cc src/stim/dem/detector_error_model_target.pybind.cc src/stim/gates/gates.pybind.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index 83e57b43..370123ac 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -2921,6 +2921,30 @@ class CircuitErrorLocationStackFrame: The full location of an instruction is a list of these frames, drilling down from the top level circuit to the inner-most loop that the instruction is within. + + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0] + stim.CircuitErrorLocationStackFrame( + instruction_offset=0, + iteration_index=0, + instruction_repetitions_arg=5, + ) + >>> err[0].circuit_error_locations[0].stack_frames[1] + stim.CircuitErrorLocationStackFrame( + instruction_offset=1, + iteration_index=4, + instruction_repetitions_arg=0, + ) """ def __init__( self, @@ -2930,6 +2954,14 @@ class CircuitErrorLocationStackFrame: instruction_repetitions_arg: int, ) -> None: """Creates a stim.CircuitErrorLocationStackFrame. + + Examples: + >>> import stim + >>> frame = stim.CircuitErrorLocationStackFrame( + ... instruction_offset=1, + ... iteration_index=2, + ... instruction_repetitions_arg=3, + ... ) """ @property def instruction_offset( @@ -2940,6 +2972,18 @@ class CircuitErrorLocationStackFrame: from the line number, because blank lines and commented lines don't count and also because the offset of the first instruction is 0 instead of 1. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0].instruction_offset + 2 """ @property def instruction_repetitions_arg( @@ -2948,6 +2992,23 @@ class CircuitErrorLocationStackFrame: """If the instruction being referred to is a REPEAT block, this is the repetition count of that REPEAT block. Otherwise this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.instruction_repetitions_arg + 5 + >>> loop.instruction_repetitions_arg + 0 """ @property def iteration_index( @@ -2956,6 +3017,23 @@ class CircuitErrorLocationStackFrame: """Disambiguates which iteration of the loop containing this instruction is being referred to. If the instruction isn't in a REPEAT block, this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.iteration_index + 0 + >>> loop.iteration_index + 4 """ class CircuitInstruction: """An instruction, like `H 0 1` or `CNOT rec[-1] 5`, from a circuit. @@ -2983,19 +3061,30 @@ class CircuitInstruction: def __init__( self, name: str, - targets: List[object], - gate_args: List[float] = (), + targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, + gate_args: Optional[Iterable[float]] = None, ) -> None: - """Initializes a `stim.CircuitInstruction`. + """Creates or parses a `stim.CircuitInstruction`. Args: name: The name of the instruction being applied. + If `targets` and `gate_args` aren't specified, this can be a full + instruction line from a stim Circuit file, like "CX 0 1". targets: The targets the instruction is being applied to. These can be raw values like `0` and `stim.target_rec(-1)`, or instances of `stim.GateTarget`. gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + + Examples: + >>> import stim + + >>> print(stim.CircuitInstruction('DEPOLARIZE1', [5], [0.25])) + DEPOLARIZE1(0.25) 5 + + >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') + stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) """ def __ne__( self, @@ -3057,7 +3146,7 @@ class CircuitInstruction: 3 >>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements 2 - >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements + >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 """ def target_groups( @@ -3211,6 +3300,20 @@ class CircuitRepeatBlock: ['H', 'REPEAT', 'S'] """ @property + def num_measurements( + self, + ) -> int: + """Returns the number of bits produced when running this loop. + + Examples: + >>> import stim + >>> stim.CircuitRepeatBlock( + ... body=stim.Circuit("M 0 1"), + ... repeat_count=25, + ... ).num_measurements + 50 + """ + @property def repeat_count( self, ) -> int: @@ -4194,13 +4297,15 @@ class DemInstruction: def __init__( self, type: str, - args: List[float], - targets: List[object], + args: Optional[Iterable[float]] = None, + targets: Optional[Iterable[stim.DemTarget]] = None, ) -> None: - """Creates a stim.DemInstruction. + """Creates or parses a stim.DemInstruction. Args: type: The name of the instruction type (e.g. "error" or "shift_detectors"). + If `args` and `targets` aren't specified, this can also be set to a + full line of text from a dem file, like "error(0.25) D0". args: Numeric values parameterizing the instruction (e.g. the 0.1 in "error(0.1)"). targets: The objects the instruction involves (e.g. the "D0" and "L1" in @@ -4214,6 +4319,9 @@ class DemInstruction: ... [stim.target_relative_detector_id(5)]) >>> print(instruction) error(0.125) D5 + + >>> print(stim.DemInstruction('error(0.125) D5 L6 ^ D4 # comment')) + error(0.125) D5 L6 ^ D4 """ def __ne__( self, diff --git a/src/stim/circuit/circuit_instruction.pybind.cc b/src/stim/circuit/circuit_instruction.pybind.cc index 5c1cce13..fe5bf4a4 100644 --- a/src/stim/circuit/circuit_instruction.pybind.cc +++ b/src/stim/circuit/circuit_instruction.pybind.cc @@ -9,15 +9,38 @@ using namespace stim; using namespace stim_pybind; PyCircuitInstruction::PyCircuitInstruction( - const char *name, const std::vector &init_targets, const std::vector &gate_args) + std::string_view name, const std::vector &init_targets, const std::vector &gate_args) : gate_type(GATE_DATA.at(name).id), gate_args(gate_args) { for (const auto &obj : init_targets) { targets.push_back(obj_to_gate_target(obj)); } + as_operation_ref().validate(); } PyCircuitInstruction::PyCircuitInstruction( GateType gate_type, std::vector targets, std::vector gate_args) : gate_type(gate_type), targets(targets), gate_args(gate_args) { + as_operation_ref().validate(); +} + +PyCircuitInstruction PyCircuitInstruction::from_str(std::string_view text) { + Circuit host; + host.append_from_text(text); + if (host.operations.size() != 1 || host.operations[0].gate_type == GateType::REPEAT) { + throw std::invalid_argument("Given text didn't parse to a single CircuitInstruction."); + } + return PyCircuitInstruction::from_instruction(host.operations[0]); +} + +PyCircuitInstruction PyCircuitInstruction::from_instruction(CircuitInstruction instruction) { + std::vector arguments; + std::vector targets; + arguments.insert(arguments.begin(), instruction.args.begin(), instruction.args.end()); + targets.insert(targets.begin(), instruction.targets.begin(), instruction.targets.end()); + return PyCircuitInstruction{ + instruction.gate_type, + targets, + arguments, + }; } bool PyCircuitInstruction::operator==(const PyCircuitInstruction &other) const { @@ -115,21 +138,46 @@ pybind11::class_ stim_pybind::pybind_circuit_instruction(p } void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind11::class_ &c) { c.def( - pybind11::init, std::vector>(), + pybind11::init([](std::string_view name, pybind11::object targets, pybind11::object gate_args) -> PyCircuitInstruction { + if (targets.is_none() and gate_args.is_none()) { + return PyCircuitInstruction::from_str(name); + } + std::vector conv_args; + std::vector conv_targets; + if (!gate_args.is_none()) { + conv_args = pybind11::cast>(gate_args); + } + if (!targets.is_none()) { + conv_targets = pybind11::cast>(targets); + } + return PyCircuitInstruction(name, conv_targets, conv_args); + }), pybind11::arg("name"), - pybind11::arg("targets"), - pybind11::arg("gate_args") = std::make_tuple(), + pybind11::arg("targets") = pybind11::none(), + pybind11::arg("gate_args") = pybind11::none(), clean_doc_string(R"DOC( - Initializes a `stim.CircuitInstruction`. + @signature def __init__(self, name: str, targets: Optional[Iterable[Union[int, stim.GateTarget]]] = None, gate_args: Optional[Iterable[float]] = None) -> None: + Creates or parses a `stim.CircuitInstruction`. Args: name: The name of the instruction being applied. + If `targets` and `gate_args` aren't specified, this can be a full + instruction line from a stim Circuit file, like "CX 0 1". targets: The targets the instruction is being applied to. These can be raw values like `0` and `stim.target_rec(-1)`, or instances of `stim.GateTarget`. gate_args: The sequence of numeric arguments parameterizing a gate. For noise gates this is their probabilities. For `OBSERVABLE_INCLUDE` instructions it's the index of the logical observable to affect. + + Examples: + >>> import stim + + >>> print(stim.CircuitInstruction('DEPOLARIZE1', [5], [0.25])) + DEPOLARIZE1(0.25) 5 + + >>> stim.CircuitInstruction('CX rec[-1] 5 # comment') + stim.CircuitInstruction('CX', [stim.target_rec(-1), stim.GateTarget(5)], []) )DOC") .data()); @@ -246,7 +294,7 @@ void stim_pybind::pybind_circuit_instruction_methods(pybind11::module &m, pybind 3 >>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements 2 - >>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements + >>> stim.CircuitInstruction('HERALDED_ERASE', [0], [0.25]).num_measurements 1 )DOC") .data()); diff --git a/src/stim/circuit/circuit_instruction.pybind.h b/src/stim/circuit/circuit_instruction.pybind.h index a1a36bf2..69907915 100644 --- a/src/stim/circuit/circuit_instruction.pybind.h +++ b/src/stim/circuit/circuit_instruction.pybind.h @@ -29,9 +29,11 @@ struct PyCircuitInstruction { std::vector gate_args; PyCircuitInstruction( - const char *name, const std::vector &targets, const std::vector &gate_args); + std::string_view name, const std::vector &targets, const std::vector &gate_args); PyCircuitInstruction( stim::GateType gate_type, std::vector targets, std::vector gate_args); + static PyCircuitInstruction from_str(std::string_view text); + static PyCircuitInstruction from_instruction(stim::CircuitInstruction instruction); stim::CircuitInstruction as_operation_ref() const; operator stim::CircuitInstruction() const; diff --git a/src/stim/circuit/circuit_instruction_pybind_test.py b/src/stim/circuit/circuit_instruction_pybind_test.py index 1939d70b..d61e226c 100644 --- a/src/stim/circuit/circuit_instruction_pybind_test.py +++ b/src/stim/circuit/circuit_instruction_pybind_test.py @@ -76,3 +76,29 @@ def test_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)]] + + +def test_eager_validate(): + with pytest.raises(ValueError, match="0, 1, 2"): + stim.CircuitInstruction("CX", [0, 1, 2]) + + +def test_init_from_str(): + assert stim.CircuitInstruction("CX", [0, 1]) == stim.CircuitInstruction("CX 0 1") + + with pytest.raises(ValueError, match="single CircuitInstruction"): + stim.CircuitInstruction("") + + with pytest.raises(ValueError, match="single CircuitInstruction"): + stim.CircuitInstruction(""" + REPEAT 5 { + H 0 + X 1 + } + """) + + with pytest.raises(ValueError, match="single CircuitInstruction"): + stim.CircuitInstruction(""" + H 0 + X 1 + """) diff --git a/src/stim/circuit/circuit_repeat_block.pybind.cc b/src/stim/circuit/circuit_repeat_block.pybind.cc index 4877a091..cb736e6a 100644 --- a/src/stim/circuit/circuit_repeat_block.pybind.cc +++ b/src/stim/circuit/circuit_repeat_block.pybind.cc @@ -131,6 +131,24 @@ void stim_pybind::pybind_circuit_repeat_block_methods(pybind11::module &m, pybin )DOC") .data()); + c.def_property_readonly( + "num_measurements", + [](const CircuitRepeatBlock &self) -> uint64_t { + return self.body.count_measurements() * self.repeat_count; + }, + clean_doc_string(R"DOC( + Returns the number of bits produced when running this loop. + + Examples: + >>> import stim + >>> stim.CircuitRepeatBlock( + ... body=stim.Circuit("M 0 1"), + ... repeat_count=25, + ... ).num_measurements + 50 + )DOC") + .data()); + c.def_readonly( "repeat_count", &CircuitRepeatBlock::repeat_count, diff --git a/src/stim/dem/detector_error_model_instruction.pybind.cc b/src/stim/dem/dem_instruction.pybind.cc similarity index 73% rename from src/stim/dem/detector_error_model_instruction.pybind.cc rename to src/stim/dem/dem_instruction.pybind.cc index ceac444d..7d6c4dad 100644 --- a/src/stim/dem/detector_error_model_instruction.pybind.cc +++ b/src/stim/dem/dem_instruction.pybind.cc @@ -1,4 +1,4 @@ -#include "stim/dem/detector_error_model_instruction.pybind.h" +#include "stim/dem/dem_instruction.pybind.h" #include "stim/dem/detector_error_model_target.pybind.h" #include "stim/py/base.pybind.h" @@ -23,6 +23,27 @@ DemInstruction ExposedDemInstruction::as_dem_instruction() const { return DemInstruction{arguments, targets, type}; } +ExposedDemInstruction ExposedDemInstruction::from_dem_instruction(stim::DemInstruction instruction) { + std::vector arguments; + std::vector targets; + arguments.insert(arguments.begin(), instruction.arg_data.begin(), instruction.arg_data.end()); + targets.insert(targets.begin(), instruction.target_data.begin(), instruction.target_data.end()); + return ExposedDemInstruction{ + arguments, + targets, + instruction.type + }; +} + +ExposedDemInstruction ExposedDemInstruction::from_str(std::string_view text) { + DetectorErrorModel host; + host.append_from_text(text); + if (host.instructions.size() != 1 || host.instructions[0].type == DemInstructionType::DEM_REPEAT_BLOCK) { + throw std::invalid_argument("Given text didn't parse to a single DemInstruction."); + } + return ExposedDemInstruction::from_dem_instruction(host.instructions[0]); +} + std::string ExposedDemInstruction::type_name() const { std::stringstream out; out << type; @@ -108,10 +129,14 @@ void stim_pybind::pybind_detector_error_model_instruction_methods( pybind11::module &m, pybind11::class_ &c) { c.def( pybind11::init( - [](const char *type, const std::vector &arguments, const std::vector &targets) { + [](std::string_view type, pybind11::object &arguments, pybind11::object &targets) -> ExposedDemInstruction { + if (arguments.is_none() && targets.is_none()) { + return ExposedDemInstruction::from_str(type); + } + std::string lower; - for (const char *c = type; *c != '\0'; c++) { - lower.push_back(tolower(*c)); + for (char c : type) { + lower.push_back(tolower(c)); } DemInstructionType conv_type; std::vector conv_targets; @@ -126,41 +151,50 @@ void stim_pybind::pybind_detector_error_model_instruction_methods( } else { throw std::invalid_argument("Unrecognized instruction name '" + lower + "'."); } - if (conv_type == DemInstructionType::DEM_SHIFT_DETECTORS) { - for (const auto &e : targets) { - try { - conv_targets.push_back(DemTarget{pybind11::cast(e)}); - } catch (pybind11::cast_error &ex) { - throw std::invalid_argument( - "Instruction '" + lower + "' only takes unsigned integer targets."); + if (!targets.is_none()) { + if (conv_type == DemInstructionType::DEM_SHIFT_DETECTORS) { + for (const auto &e : targets) { + try { + conv_targets.push_back(DemTarget{pybind11::cast(e)}); + } catch (pybind11::cast_error &ex) { + throw std::invalid_argument( + "Instruction '" + lower + "' only takes unsigned integer targets."); + } } - } - } else { - for (const auto &e : targets) { - try { - conv_targets.push_back(pybind11::cast(e).internal()); - } catch (pybind11::cast_error &ex) { - throw std::invalid_argument( - "Instruction '" + lower + - "' only takes stim.target_relative_detector_id(k), " - "stim.target_logical_observable_id(k), " - "stim.target_separator() targets."); + } else { + for (const auto &e : targets) { + try { + conv_targets.push_back(pybind11::cast(e).internal()); + } catch (pybind11::cast_error &ex) { + throw std::invalid_argument( + "Instruction '" + lower + + "' only takes stim.target_relative_detector_id(k), " + "stim.target_logical_observable_id(k), " + "stim.target_separator() targets."); + } } } } - ExposedDemInstruction result{arguments, std::move(conv_targets), conv_type}; + std::vector conv_args; + if (!arguments.is_none()) { + conv_args = pybind11::cast>(arguments); + } + ExposedDemInstruction result{std::move(conv_args), std::move(conv_targets), conv_type}; result.as_dem_instruction().validate(); return result; }), pybind11::arg("type"), - pybind11::arg("args"), - pybind11::arg("targets"), + pybind11::arg("args") = pybind11::none(), + pybind11::arg("targets") = pybind11::none(), clean_doc_string(R"DOC( - Creates a stim.DemInstruction. + @signature def __init__(self, type: str, args: Optional[Iterable[float]] = None, targets: Optional[Iterable[stim.DemTarget]] = None) -> None: + Creates or parses a stim.DemInstruction. Args: type: The name of the instruction type (e.g. "error" or "shift_detectors"). + If `args` and `targets` aren't specified, this can also be set to a + full line of text from a dem file, like "error(0.25) D0". args: Numeric values parameterizing the instruction (e.g. the 0.1 in "error(0.1)"). targets: The objects the instruction involves (e.g. the "D0" and "L1" in @@ -174,6 +208,9 @@ void stim_pybind::pybind_detector_error_model_instruction_methods( ... [stim.target_relative_detector_id(5)]) >>> print(instruction) error(0.125) D5 + + >>> print(stim.DemInstruction('error(0.125) D5 L6 ^ D4 # comment')) + error(0.125) D5 L6 ^ D4 )DOC") .data()); diff --git a/src/stim/dem/detector_error_model_instruction.pybind.h b/src/stim/dem/dem_instruction.pybind.h similarity index 87% rename from src/stim/dem/detector_error_model_instruction.pybind.h rename to src/stim/dem/dem_instruction.pybind.h index 247a713a..f7f973bf 100644 --- a/src/stim/dem/detector_error_model_instruction.pybind.h +++ b/src/stim/dem/dem_instruction.pybind.h @@ -12,6 +12,9 @@ struct ExposedDemInstruction { std::vector targets; stim::DemInstructionType type; + static ExposedDemInstruction from_str(std::string_view text); + static ExposedDemInstruction from_dem_instruction(stim::DemInstruction instruction); + std::vector> target_groups() const; std::vector args_copy() const; std::vector targets_copy() const; diff --git a/src/stim/dem/detector_error_model_instruction_pybind_test.py b/src/stim/dem/dem_instruction_pybind_test.py similarity index 86% rename from src/stim/dem/detector_error_model_instruction_pybind_test.py rename to src/stim/dem/dem_instruction_pybind_test.py index 2afbe0e6..ba55f504 100644 --- a/src/stim/dem/detector_error_model_instruction_pybind_test.py +++ b/src/stim/dem/dem_instruction_pybind_test.py @@ -78,3 +78,24 @@ def test_hashable(): def test_target_groups(): dem = stim.DetectorErrorModel("detector D0") assert dem[0].target_groups() == [[stim.DemTarget("D0")]] + + +def test_init_from_str(): + assert stim.DemInstruction("detector D0") == stim.DemInstruction("detector", [], [stim.target_relative_detector_id(0)]) + + with pytest.raises(ValueError, match="single DemInstruction"): + stim.DemInstruction("") + + with pytest.raises(ValueError, match="single DemInstruction"): + stim.DemInstruction(""" + repeat 5 { + error(0.25) D0 + shift_detectors 1 + } + """) + + with pytest.raises(ValueError, match="single DemInstruction"): + stim.DemInstruction(""" + detector D0 + detector D1 + """) diff --git a/src/stim/dem/detector_error_model.h b/src/stim/dem/detector_error_model.h index 616c2458..3f8b972c 100644 --- a/src/stim/dem/detector_error_model.h +++ b/src/stim/dem/detector_error_model.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_DEM_DETECTOR_ERROR_MODEL_H #define _STIM_DEM_DETECTOR_ERROR_MODEL_H diff --git a/src/stim/dem/detector_error_model.pybind.cc b/src/stim/dem/detector_error_model.pybind.cc index f823f25f..3292d930 100644 --- a/src/stim/dem/detector_error_model.pybind.cc +++ b/src/stim/dem/detector_error_model.pybind.cc @@ -18,7 +18,7 @@ #include "stim/circuit/circuit.pybind.h" #include "stim/cmd/command_diagram.pybind.h" -#include "stim/dem/detector_error_model_instruction.pybind.h" +#include "stim/dem/dem_instruction.pybind.h" #include "stim/dem/detector_error_model_repeat_block.pybind.h" #include "stim/dem/detector_error_model_target.pybind.h" #include "stim/io/raii_file.h" diff --git a/src/stim/dem/detector_error_model_pybind_test.py b/src/stim/dem/detector_error_model_pybind_test.py index 955937b8..25fc80ad 100644 --- a/src/stim/dem/detector_error_model_pybind_test.py +++ b/src/stim/dem/detector_error_model_pybind_test.py @@ -536,3 +536,7 @@ def test_shortest_graphlike_error_remnant(): assert len(d.shortest_graphlike_error(ignore_ungraphlike_errors=True)) == 8 assert len(c.shortest_graphlike_error()) == 8 assert len(d.shortest_graphlike_error()) == 8 + + +def test_init_parse(): + assert stim.DemInstruction("error(0.125) D0 D1") == stim.DemInstruction("error", [0.125], [stim.DemTarget("D0"), stim.DemTarget("D1")]) diff --git a/src/stim/dem/detector_error_model_repeat_block.pybind.cc b/src/stim/dem/detector_error_model_repeat_block.pybind.cc index 91b2ed98..a032aba6 100644 --- a/src/stim/dem/detector_error_model_repeat_block.pybind.cc +++ b/src/stim/dem/detector_error_model_repeat_block.pybind.cc @@ -14,8 +14,8 @@ #include "stim/dem/detector_error_model_repeat_block.pybind.h" +#include "stim/dem/dem_instruction.pybind.h" #include "stim/dem/detector_error_model.pybind.h" -#include "stim/dem/detector_error_model_instruction.pybind.h" #include "stim/py/base.pybind.h" using namespace stim; diff --git a/src/stim/py/stim.pybind.cc b/src/stim/py/stim.pybind.cc index 44ca33b5..4200d2e0 100644 --- a/src/stim/py/stim.pybind.cc +++ b/src/stim/py/stim.pybind.cc @@ -20,8 +20,8 @@ #include "stim/circuit/circuit_repeat_block.pybind.h" #include "stim/circuit/gate_target.pybind.h" #include "stim/cmd/command_diagram.pybind.h" +#include "stim/dem/dem_instruction.pybind.h" #include "stim/dem/detector_error_model.pybind.h" -#include "stim/dem/detector_error_model_instruction.pybind.h" #include "stim/dem/detector_error_model_repeat_block.pybind.h" #include "stim/dem/detector_error_model_target.pybind.h" #include "stim/gates/gates.pybind.h" diff --git a/src/stim/simulators/matched_error.pybind.cc b/src/stim/simulators/matched_error.pybind.cc index 6587750a..81c2fb7f 100644 --- a/src/stim/simulators/matched_error.pybind.cc +++ b/src/stim/simulators/matched_error.pybind.cc @@ -150,7 +150,31 @@ pybind11::class_ stim_pybind::pybind_circuit_err The full location of an instruction is a list of these frames, drilling down from the top level circuit to the inner-most loop that the instruction is within. - )DOC") + + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0] + stim.CircuitErrorLocationStackFrame( + instruction_offset=0, + iteration_index=0, + instruction_repetitions_arg=5, + ) + >>> err[0].circuit_error_locations[0].stack_frames[1] + stim.CircuitErrorLocationStackFrame( + instruction_offset=1, + iteration_index=4, + instruction_repetitions_arg=0, + ) + )DOC") .data()); } void stim_pybind::pybind_circuit_error_location_stack_frame_methods( @@ -164,6 +188,18 @@ void stim_pybind::pybind_circuit_error_location_stack_frame_methods( from the line number, because blank lines and commented lines don't count and also because the offset of the first instruction is 0 instead of 1. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].stack_frames[0].instruction_offset + 2 )DOC") .data()); @@ -174,6 +210,23 @@ void stim_pybind::pybind_circuit_error_location_stack_frame_methods( Disambiguates which iteration of the loop containing this instruction is being referred to. If the instruction isn't in a REPEAT block, this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.iteration_index + 0 + >>> loop.iteration_index + 4 )DOC") .data()); @@ -184,6 +237,23 @@ void stim_pybind::pybind_circuit_error_location_stack_frame_methods( If the instruction being referred to is a REPEAT block, this is the repetition count of that REPEAT block. Otherwise this field defaults to 0. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... REPEAT 5 { + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... } + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> full = err[0].circuit_error_locations[0].stack_frames[0] + >>> loop = err[0].circuit_error_locations[0].stack_frames[1] + >>> full.instruction_repetitions_arg + 5 + >>> loop.instruction_repetitions_arg + 0 )DOC") .data()); @@ -209,6 +279,14 @@ void stim_pybind::pybind_circuit_error_location_stack_frame_methods( pybind11::arg("instruction_repetitions_arg"), clean_doc_string(R"DOC( Creates a stim.CircuitErrorLocationStackFrame. + + Examples: + >>> import stim + >>> frame = stim.CircuitErrorLocationStackFrame( + ... instruction_offset=1, + ... iteration_index=2, + ... instruction_repetitions_arg=3, + ... ) )DOC") .data()); c.def("__str__", &CircuitErrorLocationStackFrame_repr); diff --git a/src/stim/simulators/tableau_simulator_pybind_test.py b/src/stim/simulators/tableau_simulator_pybind_test.py index 067bc0e1..e9b86af5 100644 --- a/src/stim/simulators/tableau_simulator_pybind_test.py +++ b/src/stim/simulators/tableau_simulator_pybind_test.py @@ -286,9 +286,9 @@ def test_classical_control_cnot(): def test_collision(): s = stim.TableauSimulator() - with pytest.raises(ValueError, match="same qubit"): + with pytest.raises(ValueError, match="same target"): s.cnot(0, 0) - with pytest.raises(ValueError, match="same qubit"): + with pytest.raises(ValueError, match="same target"): s.swap(0, 1, 2, 2) s.swap(0, 2, 2, 1)