diff --git a/dev/doctest_proper.py b/dev/doctest_proper.py index 59311966..2c63f93b 100755 --- a/dev/doctest_proper.py +++ b/dev/doctest_proper.py @@ -89,6 +89,13 @@ def main(): module = __import__(module_name) out = {} gen(obj=module, fullname=module_name, out=out) + for k, v in out.items(): + v = v.__doc__.lower() + if '\n' in v.strip() and 'examples:' not in v and 'example:' not in v and '[deprecated]' not in v: + if k.split('.')[-1] not in ['__next__', '__iter__', '__init_subclass__', '__module__', '__eq__', '__ne__', '__str__', '__repr__']: + if all(not (e.startswith('_') and not e.startswith('__')) for e in k.split('.')): + print(f" Warning: Missing 'examples:' section in docstring of {k!r}", file=sys.stderr) + module.__test__ = {k: v for k, v in out.items()} if doctest.testmod(module, globs=globs).failed: any_failed = True diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index 49dd374d..145d04ac 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -135,6 +135,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.DemRepeatBlock.type`](#stim.DemRepeatBlock.type) - [`stim.DemTarget`](#stim.DemTarget) - [`stim.DemTarget.__eq__`](#stim.DemTarget.__eq__) + - [`stim.DemTarget.__init__`](#stim.DemTarget.__init__) - [`stim.DemTarget.__ne__`](#stim.DemTarget.__ne__) - [`stim.DemTarget.__repr__`](#stim.DemTarget.__repr__) - [`stim.DemTarget.__str__`](#stim.DemTarget.__str__) @@ -217,10 +218,12 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.GateData.aliases`](#stim.GateData.aliases) - [`stim.GateData.flows`](#stim.GateData.flows) - [`stim.GateData.generalized_inverse`](#stim.GateData.generalized_inverse) + - [`stim.GateData.hadamard_conjugated`](#stim.GateData.hadamard_conjugated) - [`stim.GateData.inverse`](#stim.GateData.inverse) - [`stim.GateData.is_noisy_gate`](#stim.GateData.is_noisy_gate) - [`stim.GateData.is_reset`](#stim.GateData.is_reset) - [`stim.GateData.is_single_qubit_gate`](#stim.GateData.is_single_qubit_gate) + - [`stim.GateData.is_symmetric_gate`](#stim.GateData.is_symmetric_gate) - [`stim.GateData.is_two_qubit_gate`](#stim.GateData.is_two_qubit_gate) - [`stim.GateData.is_unitary`](#stim.GateData.is_unitary) - [`stim.GateData.name`](#stim.GateData.name) @@ -2702,8 +2705,8 @@ def reference_sample( Examples: >>> import stim >>> stim.Circuit(''' - ... X 1 - ... M 0 1 + ... X 1 + ... M 0 1 ... ''').reference_sample() array([False, True]) """ @@ -3502,6 +3505,26 @@ def without_noise( # (at top-level in the stim module) class CircuitErrorLocation: """Describes the location of an error mechanism from a stim circuit. + + Examples: + >>> import stim + >>> circuit = stim.Circuit.generated( + ... "repetition_code:memory", + ... distance=5, + ... rounds=5, + ... before_round_data_depolarization=1e-3, + ... ) + >>> logical_error = circuit.shortest_graphlike_error() + >>> error_location = logical_error[0].circuit_error_locations[0] + >>> print(error_location) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ ``` @@ -3520,6 +3543,48 @@ def __init__( stack_frames: List[stim.CircuitErrorLocationStackFrame], ) -> None: """Creates a stim.CircuitErrorLocation. + + Examples: + >>> import stim + >>> err = stim.CircuitErrorLocation( + ... tick_offset=1, + ... flipped_pauli_product=( + ... stim.GateTargetWithCoords( + ... gate_target=stim.target_x(0), + ... coords=[], + ... ), + ... ), + ... flipped_measurement=stim.FlippedMeasurement( + ... record_index=None, + ... observable=(), + ... ), + ... instruction_targets=stim.CircuitTargetsInsideInstruction( + ... gate='DEPOLARIZE1', + ... args=[0.001], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords( + ... gate_target=0, + ... coords=[], + ... ),) + ... ), + ... stack_frames=( + ... stim.CircuitErrorLocationStackFrame( + ... instruction_offset=2, + ... iteration_index=0, + ... instruction_repetitions_arg=0, + ... ), + ... ), + ... ) + >>> print(err) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ ``` @@ -3533,7 +3598,21 @@ def flipped_measurement( self, ) -> Optional[stim.FlippedMeasurement]: """The measurement that was flipped by the error mechanism. + If the error isn't a measurement error, this will be None. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... M(0.125) 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_measurement + stim.FlippedMeasurement( + record_index=0, + observable=(stim.GateTargetWithCoords(stim.target_z(0), []),), + ) """ ``` @@ -3547,7 +3626,19 @@ def flipped_pauli_product( self, ) -> List[stim.GateTargetWithCoords]: """The Pauli errors that the error mechanism applied to qubits. + When the error is a measurement error, this will be an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_pauli_product + [stim.GateTargetWithCoords(stim.target_y(0), [])] """ ``` @@ -3575,8 +3666,26 @@ def instruction_targets( def stack_frames( self, ) -> List[stim.CircuitErrorLocationStackFrame]: - """Where in the circuit's execution does the error mechanism occur, - accounting for things like nested loops that iterate multiple times. + """Describes where in the circuit's execution the error happened. + + Multiple frames are needed because the error may occur within a loop, + or a loop nested inside a loop, or etc. + + 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 + [stim.CircuitErrorLocationStackFrame( + instruction_offset=2, + iteration_index=0, + instruction_repetitions_arg=0, + )] """ ``` @@ -3589,8 +3698,23 @@ def stack_frames( def tick_offset( self, ) -> int: - """The number of TICKs that executed before the error mechanism being discussed, - including TICKs that occurred multiple times during loops. + """The number of TICKs that executed before the error happened. + + This counts TICKs occurring multiple times during loops. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... TICK + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].tick_offset + 3 """ ``` @@ -5310,6 +5434,18 @@ def body_copy( self, ) -> stim.DetectorErrorModel: """Returns a copy of the block's body, as a stim.DetectorErrorModel. + + Examples: + >>> import stim + >>> body = stim.DetectorErrorModel(''' + ... error(0.125) D0 D1 + ... shift_detectors 1 + ... ''') + >>> repeat_block = stim.DemRepeatBlock(100, body) + >>> repeat_block.body_copy() == body + True + >>> repeat_block.body_copy() is repeat_block.body_copy() + False """ ``` @@ -5379,6 +5515,33 @@ def __eq__( """ ``` + +```python +# stim.DemTarget.__init__ + +# (in class stim.DemTarget) +def __init__( + self, + arg: object, + /, +) -> None: + """Creates a stim.DemTarget from the given object. + + Args: + arg: A string to parse as a stim.DemTarget, or some other object to + convert into a stim.DemTarget. + + Examples: + >>> import stim + >>> stim.DemTarget("D5") == stim.target_relative_detector_id(5) + True + >>> stim.DemTarget("L2") == stim.target_logical_observable_id(2) + True + >>> stim.DemTarget("^") == stim.target_separator() + True + """ +``` + ```python # stim.DemTarget.__ne__ @@ -5428,6 +5591,15 @@ def is_logical_observable_id( In a detector error model file, observable targets are prefixed by `L`. For example, in `error(0.25) D0 L1` the `L1` is an observable target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_logical_observable_id() + True + >>> stim.DemTarget("D3").is_logical_observable_id() + False + >>> stim.DemTarget("^").is_logical_observable_id() + False """ ``` @@ -5443,6 +5615,15 @@ def is_relative_detector_id( In a detector error model file, detectors are prefixed by `D`. For example, in `error(0.25) D0 L1` the `D0` is a relative detector target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_relative_detector_id() + False + >>> stim.DemTarget("D3").is_relative_detector_id() + True + >>> stim.DemTarget("^").is_relative_detector_id() + False """ ``` @@ -5458,6 +5639,15 @@ def is_separator( Separates separate the components of a suggested decompositions within an error. For example, the `^` in `error(0.25) D1 D2 ^ D3 D4` is the separator. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_separator() + False + >>> stim.DemTarget("D3").is_separator() + False + >>> stim.DemTarget("^").is_separator() + True """ ``` @@ -5558,11 +5748,10 @@ def val( """Returns the target's integer value. Example: - >>> import stim - >>> stim.target_relative_detector_id(5).val + >>> stim.DemTarget("D5").val 5 - >>> stim.target_logical_observable_id(6).val + >>> stim.DemTarget("L6").val 6 """ ``` @@ -7480,11 +7669,21 @@ class FlippedMeasurement: # (in class stim.FlippedMeasurement) def __init__( self, - *, - record_index: int, - observable: object, -) -> None: + measurement_record_index: Optional[int], + measured_observable: Iterable[stim.GateTargetWithCoords], +): """Creates a stim.FlippedMeasurement. + + Examples: + >>> import stim + >>> print(stim.FlippedMeasurement( + ... record_index=5, + ... observable=[], + ... )) + stim.FlippedMeasurement( + record_index=5, + observable=(), + ) """ ``` @@ -7944,6 +8143,67 @@ def generalized_inverse( """ ``` + +```python +# stim.GateData.hadamard_conjugated + +# (in class stim.GateData) +def hadamard_conjugated( + self, + *, + unsigned: bool = False, +) -> Optional[stim.GateData]: + """Returns a stim gate equivalent to this gate conjugated by Hadamard gates. + + The Hadamard conjugate can be thought of as the XZ dual of the gate; the gate + you get by exchanging the X and Z bases. For example, a SQRT_X will become a + SQRT_Z and a CX gate will switch directions into an XCZ. + + If stim doesn't define a gate equivalent to conjugating this gate by Hadamards, + the value `None` is returned. + + Args: + unsigned: Defaults to False. When False, the returned gate must be *exactly* + the Hadamard conjugation of this gate. When True, the returned gate must + have the same flows but the sign of the flows can be different (i.e. + the returned gate must be the Hadamard conjugate up to Pauli gate + differences). + + Returns: + A stim.GateData instance of the Hadamard conjugate, if it exists in stim. + + None, if stim doesn't define a gate equal to the Hadamard conjugate. + + Examples: + >>> import stim + + >>> stim.gate_data('X').hadamard_conjugated() + stim.gate_data('Z') + >>> stim.gate_data('CX').hadamard_conjugated() + stim.gate_data('XCZ') + >>> stim.gate_data('RY').hadamard_conjugated() is None + True + >>> stim.gate_data('RY').hadamard_conjugated(unsigned=True) + stim.gate_data('RY') + >>> stim.gate_data('ISWAP').hadamard_conjugated(unsigned=True) is None + True + >>> stim.gate_data('SWAP').hadamard_conjugated() + stim.gate_data('SWAP') + >>> stim.gate_data('CXSWAP').hadamard_conjugated() + stim.gate_data('SWAPCX') + >>> stim.gate_data('MXX').hadamard_conjugated() + stim.gate_data('MZZ') + >>> stim.gate_data('DEPOLARIZE1').hadamard_conjugated() + stim.gate_data('DEPOLARIZE1') + >>> stim.gate_data('X_ERROR').hadamard_conjugated() + stim.gate_data('Z_ERROR') + >>> stim.gate_data('H_XY').hadamard_conjugated(unsigned=True) + stim.gate_data('H_YZ') + >>> stim.gate_data('DETECTOR').hadamard_conjugated(unsigned=True) + stim.gate_data('DETECTOR') + """ +``` + ```python # stim.GateData.inverse @@ -8120,6 +8380,62 @@ def is_single_qubit_gate( """ ``` + +```python +# stim.GateData.is_symmetric_gate + +# (in class stim.GateData) +@property +def is_symmetric_gate( + self, +) -> bool: + """Returns whether or not the gate is the same when its targets are swapped. + + A two qubit gate is symmetric if it doesn't matter if you swap its targets. It + is unaffected when conjugated by the SWAP gate. + + Single qubit gates are vacuously symmetric. A multi-qubit gate is symmetric if + swapping any two of its targets has no effect. + + Note that this method is for symmetry *without broadcasting*. For example, SWAP + is symmetric even though SWAP 1 2 3 4 isn't equal to SWAP 1 3 2 4. + + Returns: + True if the gate is symmetric. + False if the gate isn't symmetric. + + Examples: + >>> import stim + + >>> stim.gate_data('CX').is_symmetric_gate + False + >>> stim.gate_data('CZ').is_symmetric_gate + True + >>> stim.gate_data('ISWAP').is_symmetric_gate + True + >>> stim.gate_data('CXSWAP').is_symmetric_gate + False + >>> stim.gate_data('MXX').is_symmetric_gate + True + >>> stim.gate_data('DEPOLARIZE2').is_symmetric_gate + True + >>> stim.gate_data('PAULI_CHANNEL_2').is_symmetric_gate + False + >>> stim.gate_data('H').is_symmetric_gate + True + >>> stim.gate_data('R').is_symmetric_gate + True + >>> stim.gate_data('X_ERROR').is_symmetric_gate + True + >>> stim.gate_data('CORRELATED_ERROR').is_symmetric_gate + False + >>> stim.gate_data('MPP').is_symmetric_gate + False + >>> stim.gate_data('DETECTOR').is_symmetric_gate + False + """ +``` + ```python # stim.GateData.is_two_qubit_gate @@ -8136,6 +8452,10 @@ def is_two_qubit_gate( Variable-qubit gates like CORRELATED_ERROR and MPP are not considered two qubit gates. + Returns: + True if the gate is a two qubit gate. + False if the gate isn't a two qubit gate. + Examples: >>> import stim @@ -8883,7 +9203,6 @@ class GateTargetWithCoords: # (in class stim.GateTargetWithCoords) def __init__( self, - *, gate_target: object, coords: List[float], ) -> None: diff --git a/doc/stim.pyi b/doc/stim.pyi index cd0d0258..bbd39402 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -2001,8 +2001,8 @@ class Circuit: Examples: >>> import stim >>> stim.Circuit(''' - ... X 1 - ... M 0 1 + ... X 1 + ... M 0 1 ... ''').reference_sample() array([False, True]) """ @@ -2717,6 +2717,26 @@ class Circuit: """ class CircuitErrorLocation: """Describes the location of an error mechanism from a stim circuit. + + Examples: + >>> import stim + >>> circuit = stim.Circuit.generated( + ... "repetition_code:memory", + ... distance=5, + ... rounds=5, + ... before_round_data_depolarization=1e-3, + ... ) + >>> logical_error = circuit.shortest_graphlike_error() + >>> error_location = logical_error[0].circuit_error_locations[0] + >>> print(error_location) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ def __init__( self, @@ -2728,20 +2748,88 @@ class CircuitErrorLocation: stack_frames: List[stim.CircuitErrorLocationStackFrame], ) -> None: """Creates a stim.CircuitErrorLocation. + + Examples: + >>> import stim + >>> err = stim.CircuitErrorLocation( + ... tick_offset=1, + ... flipped_pauli_product=( + ... stim.GateTargetWithCoords( + ... gate_target=stim.target_x(0), + ... coords=[], + ... ), + ... ), + ... flipped_measurement=stim.FlippedMeasurement( + ... record_index=None, + ... observable=(), + ... ), + ... instruction_targets=stim.CircuitTargetsInsideInstruction( + ... gate='DEPOLARIZE1', + ... args=[0.001], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords( + ... gate_target=0, + ... coords=[], + ... ),) + ... ), + ... stack_frames=( + ... stim.CircuitErrorLocationStackFrame( + ... instruction_offset=2, + ... iteration_index=0, + ... instruction_repetitions_arg=0, + ... ), + ... ), + ... ) + >>> print(err) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ @property def flipped_measurement( self, ) -> Optional[stim.FlippedMeasurement]: """The measurement that was flipped by the error mechanism. + If the error isn't a measurement error, this will be None. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... M(0.125) 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_measurement + stim.FlippedMeasurement( + record_index=0, + observable=(stim.GateTargetWithCoords(stim.target_z(0), []),), + ) """ @property def flipped_pauli_product( self, ) -> List[stim.GateTargetWithCoords]: """The Pauli errors that the error mechanism applied to qubits. + When the error is a measurement error, this will be an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_pauli_product + [stim.GateTargetWithCoords(stim.target_y(0), [])] """ @property def instruction_targets( @@ -2755,15 +2843,48 @@ class CircuitErrorLocation: def stack_frames( self, ) -> List[stim.CircuitErrorLocationStackFrame]: - """Where in the circuit's execution does the error mechanism occur, - accounting for things like nested loops that iterate multiple times. + """Describes where in the circuit's execution the error happened. + + Multiple frames are needed because the error may occur within a loop, + or a loop nested inside a loop, or etc. + + 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 + [stim.CircuitErrorLocationStackFrame( + instruction_offset=2, + iteration_index=0, + instruction_repetitions_arg=0, + )] """ @property def tick_offset( self, ) -> int: - """The number of TICKs that executed before the error mechanism being discussed, - including TICKs that occurred multiple times during loops. + """The number of TICKs that executed before the error happened. + + This counts TICKs occurring multiple times during loops. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... TICK + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].tick_offset + 3 """ class CircuitErrorLocationStackFrame: """Describes the location of an instruction being executed within a @@ -4098,6 +4219,18 @@ class DemRepeatBlock: self, ) -> stim.DetectorErrorModel: """Returns a copy of the block's body, as a stim.DetectorErrorModel. + + Examples: + >>> import stim + >>> body = stim.DetectorErrorModel(''' + ... error(0.125) D0 D1 + ... shift_detectors 1 + ... ''') + >>> repeat_block = stim.DemRepeatBlock(100, body) + >>> repeat_block.body_copy() == body + True + >>> repeat_block.body_copy() is repeat_block.body_copy() + False """ @property def repeat_count( @@ -4137,6 +4270,26 @@ class DemTarget: ) -> bool: """Determines if two `stim.DemTarget`s are identical. """ + def __init__( + self, + arg: object, + /, + ) -> None: + """Creates a stim.DemTarget from the given object. + + Args: + arg: A string to parse as a stim.DemTarget, or some other object to + convert into a stim.DemTarget. + + Examples: + >>> import stim + >>> stim.DemTarget("D5") == stim.target_relative_detector_id(5) + True + >>> stim.DemTarget("L2") == stim.target_logical_observable_id(2) + True + >>> stim.DemTarget("^") == stim.target_separator() + True + """ def __ne__( self, arg0: stim.DemTarget, @@ -4160,6 +4313,15 @@ class DemTarget: In a detector error model file, observable targets are prefixed by `L`. For example, in `error(0.25) D0 L1` the `L1` is an observable target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_logical_observable_id() + True + >>> stim.DemTarget("D3").is_logical_observable_id() + False + >>> stim.DemTarget("^").is_logical_observable_id() + False """ def is_relative_detector_id( self, @@ -4168,6 +4330,15 @@ class DemTarget: In a detector error model file, detectors are prefixed by `D`. For example, in `error(0.25) D0 L1` the `D0` is a relative detector target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_relative_detector_id() + False + >>> stim.DemTarget("D3").is_relative_detector_id() + True + >>> stim.DemTarget("^").is_relative_detector_id() + False """ def is_separator( self, @@ -4176,6 +4347,15 @@ class DemTarget: Separates separate the components of a suggested decompositions within an error. For example, the `^` in `error(0.25) D1 D2 ^ D3 D4` is the separator. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_separator() + False + >>> stim.DemTarget("D3").is_separator() + False + >>> stim.DemTarget("^").is_separator() + True """ @staticmethod def logical_observable_id( @@ -4248,11 +4428,10 @@ class DemTarget: """Returns the target's integer value. Example: - >>> import stim - >>> stim.target_relative_detector_id(5).val + >>> stim.DemTarget("D5").val 5 - >>> stim.target_logical_observable_id(6).val + >>> stim.DemTarget("L6").val 6 """ class DemTargetWithCoords: @@ -5806,11 +5985,21 @@ class FlippedMeasurement: """ def __init__( self, - *, - record_index: int, - observable: object, - ) -> None: + measurement_record_index: Optional[int], + measured_observable: Iterable[stim.GateTargetWithCoords], + ): """Creates a stim.FlippedMeasurement. + + Examples: + >>> import stim + >>> print(stim.FlippedMeasurement( + ... record_index=5, + ... observable=[], + ... )) + stim.FlippedMeasurement( + record_index=5, + observable=(), + ) """ @property def observable( @@ -6128,6 +6317,60 @@ class GateData: >>> stim.gate_data('TICK').generalized_inverse stim.gate_data('TICK') """ + def hadamard_conjugated( + self, + *, + unsigned: bool = False, + ) -> Optional[stim.GateData]: + """Returns a stim gate equivalent to this gate conjugated by Hadamard gates. + + The Hadamard conjugate can be thought of as the XZ dual of the gate; the gate + you get by exchanging the X and Z bases. For example, a SQRT_X will become a + SQRT_Z and a CX gate will switch directions into an XCZ. + + If stim doesn't define a gate equivalent to conjugating this gate by Hadamards, + the value `None` is returned. + + Args: + unsigned: Defaults to False. When False, the returned gate must be *exactly* + the Hadamard conjugation of this gate. When True, the returned gate must + have the same flows but the sign of the flows can be different (i.e. + the returned gate must be the Hadamard conjugate up to Pauli gate + differences). + + Returns: + A stim.GateData instance of the Hadamard conjugate, if it exists in stim. + + None, if stim doesn't define a gate equal to the Hadamard conjugate. + + Examples: + >>> import stim + + >>> stim.gate_data('X').hadamard_conjugated() + stim.gate_data('Z') + >>> stim.gate_data('CX').hadamard_conjugated() + stim.gate_data('XCZ') + >>> stim.gate_data('RY').hadamard_conjugated() is None + True + >>> stim.gate_data('RY').hadamard_conjugated(unsigned=True) + stim.gate_data('RY') + >>> stim.gate_data('ISWAP').hadamard_conjugated(unsigned=True) is None + True + >>> stim.gate_data('SWAP').hadamard_conjugated() + stim.gate_data('SWAP') + >>> stim.gate_data('CXSWAP').hadamard_conjugated() + stim.gate_data('SWAPCX') + >>> stim.gate_data('MXX').hadamard_conjugated() + stim.gate_data('MZZ') + >>> stim.gate_data('DEPOLARIZE1').hadamard_conjugated() + stim.gate_data('DEPOLARIZE1') + >>> stim.gate_data('X_ERROR').hadamard_conjugated() + stim.gate_data('Z_ERROR') + >>> stim.gate_data('H_XY').hadamard_conjugated(unsigned=True) + stim.gate_data('H_YZ') + >>> stim.gate_data('DETECTOR').hadamard_conjugated(unsigned=True) + stim.gate_data('DETECTOR') + """ @property def inverse( self, @@ -6277,6 +6520,55 @@ class GateData: False """ @property + def is_symmetric_gate( + self, + ) -> bool: + """Returns whether or not the gate is the same when its targets are swapped. + + A two qubit gate is symmetric if it doesn't matter if you swap its targets. It + is unaffected when conjugated by the SWAP gate. + + Single qubit gates are vacuously symmetric. A multi-qubit gate is symmetric if + swapping any two of its targets has no effect. + + Note that this method is for symmetry *without broadcasting*. For example, SWAP + is symmetric even though SWAP 1 2 3 4 isn't equal to SWAP 1 3 2 4. + + Returns: + True if the gate is symmetric. + False if the gate isn't symmetric. + + Examples: + >>> import stim + + >>> stim.gate_data('CX').is_symmetric_gate + False + >>> stim.gate_data('CZ').is_symmetric_gate + True + >>> stim.gate_data('ISWAP').is_symmetric_gate + True + >>> stim.gate_data('CXSWAP').is_symmetric_gate + False + >>> stim.gate_data('MXX').is_symmetric_gate + True + >>> stim.gate_data('DEPOLARIZE2').is_symmetric_gate + True + >>> stim.gate_data('PAULI_CHANNEL_2').is_symmetric_gate + False + >>> stim.gate_data('H').is_symmetric_gate + True + >>> stim.gate_data('R').is_symmetric_gate + True + >>> stim.gate_data('X_ERROR').is_symmetric_gate + True + >>> stim.gate_data('CORRELATED_ERROR').is_symmetric_gate + False + >>> stim.gate_data('MPP').is_symmetric_gate + False + >>> stim.gate_data('DETECTOR').is_symmetric_gate + False + """ + @property def is_two_qubit_gate( self, ) -> bool: @@ -6287,6 +6579,10 @@ class GateData: Variable-qubit gates like CORRELATED_ERROR and MPP are not considered two qubit gates. + Returns: + True if the gate is a two qubit gate. + False if the gate isn't a two qubit gate. + Examples: >>> import stim @@ -6852,7 +7148,6 @@ class GateTargetWithCoords: """ def __init__( self, - *, gate_target: object, coords: List[float], ) -> None: diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index cd0d0258..bbd39402 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -2001,8 +2001,8 @@ class Circuit: Examples: >>> import stim >>> stim.Circuit(''' - ... X 1 - ... M 0 1 + ... X 1 + ... M 0 1 ... ''').reference_sample() array([False, True]) """ @@ -2717,6 +2717,26 @@ class Circuit: """ class CircuitErrorLocation: """Describes the location of an error mechanism from a stim circuit. + + Examples: + >>> import stim + >>> circuit = stim.Circuit.generated( + ... "repetition_code:memory", + ... distance=5, + ... rounds=5, + ... before_round_data_depolarization=1e-3, + ... ) + >>> logical_error = circuit.shortest_graphlike_error() + >>> error_location = logical_error[0].circuit_error_locations[0] + >>> print(error_location) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ def __init__( self, @@ -2728,20 +2748,88 @@ class CircuitErrorLocation: stack_frames: List[stim.CircuitErrorLocationStackFrame], ) -> None: """Creates a stim.CircuitErrorLocation. + + Examples: + >>> import stim + >>> err = stim.CircuitErrorLocation( + ... tick_offset=1, + ... flipped_pauli_product=( + ... stim.GateTargetWithCoords( + ... gate_target=stim.target_x(0), + ... coords=[], + ... ), + ... ), + ... flipped_measurement=stim.FlippedMeasurement( + ... record_index=None, + ... observable=(), + ... ), + ... instruction_targets=stim.CircuitTargetsInsideInstruction( + ... gate='DEPOLARIZE1', + ... args=[0.001], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords( + ... gate_target=0, + ... coords=[], + ... ),) + ... ), + ... stack_frames=( + ... stim.CircuitErrorLocationStackFrame( + ... instruction_offset=2, + ... iteration_index=0, + ... instruction_repetitions_arg=0, + ... ), + ... ), + ... ) + >>> print(err) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } """ @property def flipped_measurement( self, ) -> Optional[stim.FlippedMeasurement]: """The measurement that was flipped by the error mechanism. + If the error isn't a measurement error, this will be None. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... M(0.125) 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_measurement + stim.FlippedMeasurement( + record_index=0, + observable=(stim.GateTargetWithCoords(stim.target_z(0), []),), + ) """ @property def flipped_pauli_product( self, ) -> List[stim.GateTargetWithCoords]: """The Pauli errors that the error mechanism applied to qubits. + When the error is a measurement error, this will be an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_pauli_product + [stim.GateTargetWithCoords(stim.target_y(0), [])] """ @property def instruction_targets( @@ -2755,15 +2843,48 @@ class CircuitErrorLocation: def stack_frames( self, ) -> List[stim.CircuitErrorLocationStackFrame]: - """Where in the circuit's execution does the error mechanism occur, - accounting for things like nested loops that iterate multiple times. + """Describes where in the circuit's execution the error happened. + + Multiple frames are needed because the error may occur within a loop, + or a loop nested inside a loop, or etc. + + 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 + [stim.CircuitErrorLocationStackFrame( + instruction_offset=2, + iteration_index=0, + instruction_repetitions_arg=0, + )] """ @property def tick_offset( self, ) -> int: - """The number of TICKs that executed before the error mechanism being discussed, - including TICKs that occurred multiple times during loops. + """The number of TICKs that executed before the error happened. + + This counts TICKs occurring multiple times during loops. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... TICK + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].tick_offset + 3 """ class CircuitErrorLocationStackFrame: """Describes the location of an instruction being executed within a @@ -4098,6 +4219,18 @@ class DemRepeatBlock: self, ) -> stim.DetectorErrorModel: """Returns a copy of the block's body, as a stim.DetectorErrorModel. + + Examples: + >>> import stim + >>> body = stim.DetectorErrorModel(''' + ... error(0.125) D0 D1 + ... shift_detectors 1 + ... ''') + >>> repeat_block = stim.DemRepeatBlock(100, body) + >>> repeat_block.body_copy() == body + True + >>> repeat_block.body_copy() is repeat_block.body_copy() + False """ @property def repeat_count( @@ -4137,6 +4270,26 @@ class DemTarget: ) -> bool: """Determines if two `stim.DemTarget`s are identical. """ + def __init__( + self, + arg: object, + /, + ) -> None: + """Creates a stim.DemTarget from the given object. + + Args: + arg: A string to parse as a stim.DemTarget, or some other object to + convert into a stim.DemTarget. + + Examples: + >>> import stim + >>> stim.DemTarget("D5") == stim.target_relative_detector_id(5) + True + >>> stim.DemTarget("L2") == stim.target_logical_observable_id(2) + True + >>> stim.DemTarget("^") == stim.target_separator() + True + """ def __ne__( self, arg0: stim.DemTarget, @@ -4160,6 +4313,15 @@ class DemTarget: In a detector error model file, observable targets are prefixed by `L`. For example, in `error(0.25) D0 L1` the `L1` is an observable target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_logical_observable_id() + True + >>> stim.DemTarget("D3").is_logical_observable_id() + False + >>> stim.DemTarget("^").is_logical_observable_id() + False """ def is_relative_detector_id( self, @@ -4168,6 +4330,15 @@ class DemTarget: In a detector error model file, detectors are prefixed by `D`. For example, in `error(0.25) D0 L1` the `D0` is a relative detector target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_relative_detector_id() + False + >>> stim.DemTarget("D3").is_relative_detector_id() + True + >>> stim.DemTarget("^").is_relative_detector_id() + False """ def is_separator( self, @@ -4176,6 +4347,15 @@ class DemTarget: Separates separate the components of a suggested decompositions within an error. For example, the `^` in `error(0.25) D1 D2 ^ D3 D4` is the separator. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_separator() + False + >>> stim.DemTarget("D3").is_separator() + False + >>> stim.DemTarget("^").is_separator() + True """ @staticmethod def logical_observable_id( @@ -4248,11 +4428,10 @@ class DemTarget: """Returns the target's integer value. Example: - >>> import stim - >>> stim.target_relative_detector_id(5).val + >>> stim.DemTarget("D5").val 5 - >>> stim.target_logical_observable_id(6).val + >>> stim.DemTarget("L6").val 6 """ class DemTargetWithCoords: @@ -5806,11 +5985,21 @@ class FlippedMeasurement: """ def __init__( self, - *, - record_index: int, - observable: object, - ) -> None: + measurement_record_index: Optional[int], + measured_observable: Iterable[stim.GateTargetWithCoords], + ): """Creates a stim.FlippedMeasurement. + + Examples: + >>> import stim + >>> print(stim.FlippedMeasurement( + ... record_index=5, + ... observable=[], + ... )) + stim.FlippedMeasurement( + record_index=5, + observable=(), + ) """ @property def observable( @@ -6128,6 +6317,60 @@ class GateData: >>> stim.gate_data('TICK').generalized_inverse stim.gate_data('TICK') """ + def hadamard_conjugated( + self, + *, + unsigned: bool = False, + ) -> Optional[stim.GateData]: + """Returns a stim gate equivalent to this gate conjugated by Hadamard gates. + + The Hadamard conjugate can be thought of as the XZ dual of the gate; the gate + you get by exchanging the X and Z bases. For example, a SQRT_X will become a + SQRT_Z and a CX gate will switch directions into an XCZ. + + If stim doesn't define a gate equivalent to conjugating this gate by Hadamards, + the value `None` is returned. + + Args: + unsigned: Defaults to False. When False, the returned gate must be *exactly* + the Hadamard conjugation of this gate. When True, the returned gate must + have the same flows but the sign of the flows can be different (i.e. + the returned gate must be the Hadamard conjugate up to Pauli gate + differences). + + Returns: + A stim.GateData instance of the Hadamard conjugate, if it exists in stim. + + None, if stim doesn't define a gate equal to the Hadamard conjugate. + + Examples: + >>> import stim + + >>> stim.gate_data('X').hadamard_conjugated() + stim.gate_data('Z') + >>> stim.gate_data('CX').hadamard_conjugated() + stim.gate_data('XCZ') + >>> stim.gate_data('RY').hadamard_conjugated() is None + True + >>> stim.gate_data('RY').hadamard_conjugated(unsigned=True) + stim.gate_data('RY') + >>> stim.gate_data('ISWAP').hadamard_conjugated(unsigned=True) is None + True + >>> stim.gate_data('SWAP').hadamard_conjugated() + stim.gate_data('SWAP') + >>> stim.gate_data('CXSWAP').hadamard_conjugated() + stim.gate_data('SWAPCX') + >>> stim.gate_data('MXX').hadamard_conjugated() + stim.gate_data('MZZ') + >>> stim.gate_data('DEPOLARIZE1').hadamard_conjugated() + stim.gate_data('DEPOLARIZE1') + >>> stim.gate_data('X_ERROR').hadamard_conjugated() + stim.gate_data('Z_ERROR') + >>> stim.gate_data('H_XY').hadamard_conjugated(unsigned=True) + stim.gate_data('H_YZ') + >>> stim.gate_data('DETECTOR').hadamard_conjugated(unsigned=True) + stim.gate_data('DETECTOR') + """ @property def inverse( self, @@ -6277,6 +6520,55 @@ class GateData: False """ @property + def is_symmetric_gate( + self, + ) -> bool: + """Returns whether or not the gate is the same when its targets are swapped. + + A two qubit gate is symmetric if it doesn't matter if you swap its targets. It + is unaffected when conjugated by the SWAP gate. + + Single qubit gates are vacuously symmetric. A multi-qubit gate is symmetric if + swapping any two of its targets has no effect. + + Note that this method is for symmetry *without broadcasting*. For example, SWAP + is symmetric even though SWAP 1 2 3 4 isn't equal to SWAP 1 3 2 4. + + Returns: + True if the gate is symmetric. + False if the gate isn't symmetric. + + Examples: + >>> import stim + + >>> stim.gate_data('CX').is_symmetric_gate + False + >>> stim.gate_data('CZ').is_symmetric_gate + True + >>> stim.gate_data('ISWAP').is_symmetric_gate + True + >>> stim.gate_data('CXSWAP').is_symmetric_gate + False + >>> stim.gate_data('MXX').is_symmetric_gate + True + >>> stim.gate_data('DEPOLARIZE2').is_symmetric_gate + True + >>> stim.gate_data('PAULI_CHANNEL_2').is_symmetric_gate + False + >>> stim.gate_data('H').is_symmetric_gate + True + >>> stim.gate_data('R').is_symmetric_gate + True + >>> stim.gate_data('X_ERROR').is_symmetric_gate + True + >>> stim.gate_data('CORRELATED_ERROR').is_symmetric_gate + False + >>> stim.gate_data('MPP').is_symmetric_gate + False + >>> stim.gate_data('DETECTOR').is_symmetric_gate + False + """ + @property def is_two_qubit_gate( self, ) -> bool: @@ -6287,6 +6579,10 @@ class GateData: Variable-qubit gates like CORRELATED_ERROR and MPP are not considered two qubit gates. + Returns: + True if the gate is a two qubit gate. + False if the gate isn't a two qubit gate. + Examples: >>> import stim @@ -6852,7 +7148,6 @@ class GateTargetWithCoords: """ def __init__( self, - *, gate_target: object, coords: List[float], ) -> None: diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 454b6f91..4c870e61 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -721,8 +721,8 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> import stim >>> stim.Circuit(''' - ... X 1 - ... M 0 1 + ... X 1 + ... M 0 1 ... ''').reference_sample() array([False, True]) )DOC") diff --git a/src/stim/dem/dem_instruction.cc b/src/stim/dem/dem_instruction.cc index b15c47b8..fd3452c5 100644 --- a/src/stim/dem/dem_instruction.cc +++ b/src/stim/dem/dem_instruction.cc @@ -11,9 +11,6 @@ using namespace stim; constexpr uint64_t OBSERVABLE_BIT = uint64_t{1} << 63; constexpr uint64_t SEPARATOR_SYGIL = UINT64_MAX; -constexpr uint64_t MAX_OBS = 0xFFFFFFFF; -constexpr uint64_t MAX_DET = (uint64_t{1} << 62) - 1; - DemTarget DemTarget::observable_id(uint64_t id) { if (id > MAX_OBS) { throw std::invalid_argument("id > 0xFFFFFFFF"); @@ -79,6 +76,9 @@ void DemTarget::shift_if_detector_id(int64_t offset) { } } DemTarget DemTarget::from_text(std::string_view text) { + if (text == "^") { + return DemTarget::separator(); + } if (!text.empty()) { bool is_det = text[0] == 'D'; bool is_obs = text[0] == 'L'; @@ -86,9 +86,9 @@ DemTarget DemTarget::from_text(std::string_view text) { int64_t parsed = 0; if (parse_int64(text.substr(1), &parsed)) { if (parsed >= 0) { - if (is_det && parsed <= (int64_t)MAX_DET) { + if (is_det && (uint64_t)parsed <= MAX_DET) { return DemTarget::relative_detector_id(parsed); - } else if (is_obs && parsed <= (int64_t)MAX_OBS) { + } else if (is_obs && (uint64_t)parsed <= MAX_OBS) { return DemTarget::observable_id(parsed); } } diff --git a/src/stim/dem/dem_instruction.h b/src/stim/dem/dem_instruction.h index d75c4f34..42361112 100644 --- a/src/stim/dem/dem_instruction.h +++ b/src/stim/dem/dem_instruction.h @@ -10,6 +10,9 @@ namespace stim { +constexpr uint64_t MAX_OBS = 0xFFFFFFFF; +constexpr uint64_t MAX_DET = (uint64_t{1} << 62) - 1; + enum class DemInstructionType : uint8_t { DEM_ERROR, DEM_SHIFT_DETECTORS, diff --git a/src/stim/dem/detector_error_model_pybind_test.py b/src/stim/dem/detector_error_model_pybind_test.py index cd479aa9..955937b8 100644 --- a/src/stim/dem/detector_error_model_pybind_test.py +++ b/src/stim/dem/detector_error_model_pybind_test.py @@ -155,7 +155,7 @@ def test_append_bad(): m.append("shift_detectors", [], [5]) m += m * 3 - with pytest.raises(ValueError, match=r"Bad target 'stim.target_relative_detector_id\(0\)' for instruction 'shift_detectors'"): + with pytest.raises(ValueError, match=r"Bad target 'stim.DemTarget\('D0'\)' for instruction 'shift_detectors'"): m.append("shift_detectors", [0.125, 0.25], [stim.target_relative_detector_id(0)]) with pytest.raises(ValueError, match="takes 1 argument"): m.append("error", [0.125, 0.25], [stim.target_relative_detector_id(0)]) 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 cc6f37ca..91b2ed98 100644 --- a/src/stim/dem/detector_error_model_repeat_block.pybind.cc +++ b/src/stim/dem/detector_error_model_repeat_block.pybind.cc @@ -78,6 +78,18 @@ void stim_pybind::pybind_detector_error_model_repeat_block_methods( &ExposedDemRepeatBlock::body_copy, clean_doc_string(R"DOC( Returns a copy of the block's body, as a stim.DetectorErrorModel. + + Examples: + >>> import stim + >>> body = stim.DetectorErrorModel(''' + ... error(0.125) D0 D1 + ... shift_detectors 1 + ... ''') + >>> repeat_block = stim.DemRepeatBlock(100, body) + >>> repeat_block.body_copy() == body + True + >>> repeat_block.body_copy() is repeat_block.body_copy() + False )DOC") .data()); c.def_property_readonly( diff --git a/src/stim/dem/detector_error_model_target.pybind.cc b/src/stim/dem/detector_error_model_target.pybind.cc index cedc4d41..43b442e6 100644 --- a/src/stim/dem/detector_error_model_target.pybind.cc +++ b/src/stim/dem/detector_error_model_target.pybind.cc @@ -16,6 +16,7 @@ #include "stim/dem/detector_error_model.pybind.h" #include "stim/py/base.pybind.h" +#include "stim/util_bot/arg_parse.h" using namespace stim; using namespace stim_pybind; @@ -27,6 +28,42 @@ pybind11::class_ stim_pybind::pybind_detector_error_model_targ void stim_pybind::pybind_detector_error_model_target_methods( pybind11::module &m, pybind11::class_ &c) { + + c.def( + pybind11::init([](const pybind11::object &arg) -> ExposedDemTarget { + if (pybind11::isinstance(arg)) { + return pybind11::cast(arg); + } + if (pybind11::isinstance(arg)) { + std::string_view contents = pybind11::cast(arg); + return DemTarget::from_text(contents); + } + + std::stringstream ss; + ss << "Don't know how to convert this into a stim.DemTarget: "; + ss << pybind11::repr(arg); + throw pybind11::type_error(ss.str()); + }), + pybind11::arg("arg"), + pybind11::pos_only(), + clean_doc_string(R"DOC( + Creates a stim.DemTarget from the given object. + + Args: + arg: A string to parse as a stim.DemTarget, or some other object to + convert into a stim.DemTarget. + + Examples: + >>> import stim + >>> stim.DemTarget("D5") == stim.target_relative_detector_id(5) + True + >>> stim.DemTarget("L2") == stim.target_logical_observable_id(2) + True + >>> stim.DemTarget("^") == stim.target_separator() + True + )DOC") + .data()); + m.def( "target_relative_detector_id", &ExposedDemTarget::relative_detector_id, @@ -52,6 +89,7 @@ void stim_pybind::pybind_detector_error_model_target_methods( ''') )DOC") .data()); + m.def( "target_logical_observable_id", &ExposedDemTarget::observable_id, @@ -188,6 +226,15 @@ void stim_pybind::pybind_detector_error_model_target_methods( In a detector error model file, detectors are prefixed by `D`. For example, in `error(0.25) D0 L1` the `D0` is a relative detector target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_relative_detector_id() + False + >>> stim.DemTarget("D3").is_relative_detector_id() + True + >>> stim.DemTarget("^").is_relative_detector_id() + False )DOC") .data()); @@ -199,6 +246,15 @@ void stim_pybind::pybind_detector_error_model_target_methods( In a detector error model file, observable targets are prefixed by `L`. For example, in `error(0.25) D0 L1` the `L1` is an observable target. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_logical_observable_id() + True + >>> stim.DemTarget("D3").is_logical_observable_id() + False + >>> stim.DemTarget("^").is_logical_observable_id() + False )DOC") .data()); @@ -209,11 +265,10 @@ void stim_pybind::pybind_detector_error_model_target_methods( Returns the target's integer value. Example: - >>> import stim - >>> stim.target_relative_detector_id(5).val + >>> stim.DemTarget("D5").val 5 - >>> stim.target_logical_observable_id(6).val + >>> stim.DemTarget("L6").val 6 )DOC") .data()); @@ -226,6 +281,15 @@ void stim_pybind::pybind_detector_error_model_target_methods( Separates separate the components of a suggested decompositions within an error. For example, the `^` in `error(0.25) D1 D2 ^ D3 D4` is the separator. + + Examples: + >>> import stim + >>> stim.DemTarget("L2").is_separator() + False + >>> stim.DemTarget("D3").is_separator() + False + >>> stim.DemTarget("^").is_separator() + True )DOC") .data()); @@ -237,11 +301,11 @@ void stim_pybind::pybind_detector_error_model_target_methods( std::string ExposedDemTarget::repr() const { std::stringstream out; if (is_relative_detector_id()) { - out << "stim.target_relative_detector_id(" << raw_id() << ")"; + out << "stim.DemTarget('D" << raw_id() << "')"; } else if (is_separator()) { out << "stim.target_separator()"; } else { - out << "stim.target_logical_observable_id(" << raw_id() << ")"; + out << "stim.DemTarget('L" << raw_id() << "')"; } return out.str(); } diff --git a/src/stim/dem/detector_error_model_target_pybind_test.py b/src/stim/dem/detector_error_model_target_pybind_test.py index 1119c74d..743a07f3 100644 --- a/src/stim/dem/detector_error_model_target_pybind_test.py +++ b/src/stim/dem/detector_error_model_target_pybind_test.py @@ -75,3 +75,23 @@ def test_hashable(): c = stim.DemTarget.relative_detector_id(3) assert hash(a) == hash(c) assert len({a, b, c}) == 2 + + +def test_init(): + assert stim.DemTarget("D0") == stim.target_relative_detector_id(0) + assert stim.DemTarget("D5") == stim.target_relative_detector_id(5) + assert stim.DemTarget("L0") == stim.target_logical_observable_id(0) + assert stim.DemTarget("L5") == stim.target_logical_observable_id(5) + assert stim.DemTarget("^") == stim.target_separator() + assert stim.DemTarget(f"D{2**62 - 1}") == stim.target_relative_detector_id(2**62 - 1) + assert stim.DemTarget(f"L{0xFFFFFFFF}") == stim.target_logical_observable_id(0xFFFFFFFF) + with pytest.raises(ValueError, match="Failed to parse"): + _ = stim.DemTarget(f"D{2**62}") + with pytest.raises(ValueError, match="Failed to parse"): + _ = stim.DemTarget(f"L{0x100000000}") + with pytest.raises(ValueError, match="Failed to parse"): + _ = stim.DemTarget(f"L-1") + with pytest.raises(ValueError, match="Failed to parse"): + _ = stim.DemTarget(f"X5") + with pytest.raises(ValueError, match="Failed to parse"): + _ = stim.DemTarget(f"5") diff --git a/src/stim/gates/gates.cc b/src/stim/gates/gates.cc index e374d05f..09f46adf 100644 --- a/src/stim/gates/gates.cc +++ b/src/stim/gates/gates.cc @@ -45,6 +45,148 @@ GateDataMap::GateDataMap() { } } +GateType Gate::hadamard_conjugated(bool ignoring_sign) const { + switch (id) { + case GateType::DETECTOR: + case GateType::OBSERVABLE_INCLUDE: + case GateType::TICK: + case GateType::QUBIT_COORDS: + case GateType::SHIFT_COORDS: + case GateType::MPAD: + case GateType::H: + case GateType::DEPOLARIZE1: + case GateType::DEPOLARIZE2: + case GateType::Y_ERROR: + case GateType::I: + case GateType::Y: + case GateType::SQRT_YY: + case GateType::SQRT_YY_DAG: + case GateType::MYY: + case GateType::SWAP: + return id; + + case GateType::MY: + case GateType::MRY: + case GateType::RY: + case GateType::YCY: + return ignoring_sign ? id : GateType::NOT_A_GATE; + + case GateType::ISWAP: + case GateType::CZSWAP: + case GateType::ISWAP_DAG: + return GateType::NOT_A_GATE; + + case GateType::XCY: + return ignoring_sign ? GateType::CY : GateType::NOT_A_GATE; + case GateType::CY: + return ignoring_sign ? GateType::XCY : GateType::NOT_A_GATE; + case GateType::YCX: + return ignoring_sign ? GateType::YCZ : GateType::NOT_A_GATE; + case GateType::YCZ: + return ignoring_sign ? GateType::YCX : GateType::NOT_A_GATE; + case GateType::C_XYZ: + return ignoring_sign ? GateType::C_ZYX : GateType::NOT_A_GATE; + case GateType::C_ZYX: + return ignoring_sign ? GateType::C_XYZ : GateType::NOT_A_GATE; + case GateType::H_XY: + return ignoring_sign ? GateType::H_YZ : GateType::NOT_A_GATE; + case GateType::H_YZ: + return ignoring_sign ? GateType::H_XY : GateType::NOT_A_GATE; + + case GateType::X: + return GateType::Z; + case GateType::Z: + return GateType::X; + case GateType::SQRT_Y: + return GateType::SQRT_Y_DAG; + case GateType::SQRT_Y_DAG: + return GateType::SQRT_Y; + case GateType::MX: + return GateType::M; + case GateType::M: + return GateType::MX; + case GateType::MRX: + return GateType::MR; + case GateType::MR: + return GateType::MRX; + case GateType::RX: + return GateType::R; + case GateType::R: + return GateType::RX; + case GateType::XCX: + return GateType::CZ; + case GateType::XCZ: + return GateType::CX; + case GateType::CX: + return GateType::XCZ; + case GateType::CZ: + return GateType::XCX; + case GateType::X_ERROR: + return GateType::Z_ERROR; + case GateType::Z_ERROR: + return GateType::X_ERROR; + case GateType::SQRT_X: + return GateType::S; + case GateType::SQRT_X_DAG: + return GateType::S_DAG; + case GateType::S: + return GateType::SQRT_X; + case GateType::S_DAG: + return GateType::SQRT_X_DAG; + case GateType::SQRT_XX: + return GateType::SQRT_ZZ; + case GateType::SQRT_XX_DAG: + return GateType::SQRT_ZZ_DAG; + case GateType::SQRT_ZZ: + return GateType::SQRT_XX; + case GateType::SQRT_ZZ_DAG: + return GateType::SQRT_XX_DAG; + case GateType::CXSWAP: + return GateType::SWAPCX; + case GateType::SWAPCX: + return GateType::CXSWAP; + case GateType::MXX: + return GateType::MZZ; + case GateType::MZZ: + return GateType::MXX; + default: + return GateType::NOT_A_GATE; + } +} + +bool Gate::is_symmetric() const { + if (flags & GATE_IS_SINGLE_QUBIT_GATE) { + return true; + } + + if (flags & GATE_TARGETS_PAIRS) { + switch (id) { + case GateType::XCX: + case GateType::YCY: + case GateType::CZ: + case GateType::DEPOLARIZE2: + case GateType::SWAP: + case GateType::ISWAP: + case GateType::CZSWAP: + case GateType::ISWAP_DAG: + case GateType::MXX: + case GateType::MYY: + case GateType::MZZ: + case GateType::SQRT_XX: + case GateType::SQRT_YY: + case GateType::SQRT_ZZ: + case GateType::SQRT_XX_DAG: + case GateType::SQRT_YY_DAG: + case GateType::SQRT_ZZ_DAG: + return true; + default: + return false; + } + } + + return false; +} + std::array Gate::to_euler_angles() const { if (unitary_data.size() != 2) { throw std::out_of_range(std::string(name) + " doesn't have 1q unitary data."); diff --git a/src/stim/gates/gates.h b/src/stim/gates/gates.h index 291dbd99..32fba97b 100644 --- a/src/stim/gates/gates.h +++ b/src/stim/gates/gates.h @@ -282,6 +282,9 @@ struct Gate { std::vector>> unitary() const; + bool is_symmetric() const; + GateType hadamard_conjugated(bool ignoring_sign) const; + /// Converts a single qubit unitary gate into an euler-angles rotation. /// /// Returns: diff --git a/src/stim/gates/gates.pybind.cc b/src/stim/gates/gates.pybind.cc index ceca99ed..df54e498 100644 --- a/src/stim/gates/gates.pybind.cc +++ b/src/stim/gates/gates.pybind.cc @@ -495,6 +495,123 @@ void stim_pybind::pybind_gate_data_methods(pybind11::module &m, pybind11::class_ )DOC") .data()); + c.def_property_readonly( + "is_symmetric_gate", + [](const Gate &self) -> bool { + return self.is_symmetric(); + }, + clean_doc_string(R"DOC( + Returns whether or not the gate is the same when its targets are swapped. + + A two qubit gate is symmetric if it doesn't matter if you swap its targets. It + is unaffected when conjugated by the SWAP gate. + + Single qubit gates are vacuously symmetric. A multi-qubit gate is symmetric if + swapping any two of its targets has no effect. + + Note that this method is for symmetry *without broadcasting*. For example, SWAP + is symmetric even though SWAP 1 2 3 4 isn't equal to SWAP 1 3 2 4. + + Returns: + True if the gate is symmetric. + False if the gate isn't symmetric. + + Examples: + >>> import stim + + >>> stim.gate_data('CX').is_symmetric_gate + False + >>> stim.gate_data('CZ').is_symmetric_gate + True + >>> stim.gate_data('ISWAP').is_symmetric_gate + True + >>> stim.gate_data('CXSWAP').is_symmetric_gate + False + >>> stim.gate_data('MXX').is_symmetric_gate + True + >>> stim.gate_data('DEPOLARIZE2').is_symmetric_gate + True + >>> stim.gate_data('PAULI_CHANNEL_2').is_symmetric_gate + False + >>> stim.gate_data('H').is_symmetric_gate + True + >>> stim.gate_data('R').is_symmetric_gate + True + >>> stim.gate_data('X_ERROR').is_symmetric_gate + True + >>> stim.gate_data('CORRELATED_ERROR').is_symmetric_gate + False + >>> stim.gate_data('MPP').is_symmetric_gate + False + >>> stim.gate_data('DETECTOR').is_symmetric_gate + False + )DOC") + .data()); + + c.def( + "hadamard_conjugated", + [](const Gate &self, bool ignoring_sign) -> pybind11::object { + GateType g = self.hadamard_conjugated(ignoring_sign); + if (g == GateType::NOT_A_GATE) { + return pybind11::none(); + } + return pybind11::cast(GATE_DATA[g]); + }, + pybind11::kw_only(), + pybind11::arg("unsigned") = false, + clean_doc_string(R"DOC( + @signature def hadamard_conjugated(self, *, unsigned: bool = False) -> Optional[stim.GateData]: + Returns a stim gate equivalent to this gate conjugated by Hadamard gates. + + The Hadamard conjugate can be thought of as the XZ dual of the gate; the gate + you get by exchanging the X and Z bases. For example, a SQRT_X will become a + SQRT_Z and a CX gate will switch directions into an XCZ. + + If stim doesn't define a gate equivalent to conjugating this gate by Hadamards, + the value `None` is returned. + + Args: + unsigned: Defaults to False. When False, the returned gate must be *exactly* + the Hadamard conjugation of this gate. When True, the returned gate must + have the same flows but the sign of the flows can be different (i.e. + the returned gate must be the Hadamard conjugate up to Pauli gate + differences). + + Returns: + A stim.GateData instance of the Hadamard conjugate, if it exists in stim. + + None, if stim doesn't define a gate equal to the Hadamard conjugate. + + Examples: + >>> import stim + + >>> stim.gate_data('X').hadamard_conjugated() + stim.gate_data('Z') + >>> stim.gate_data('CX').hadamard_conjugated() + stim.gate_data('XCZ') + >>> stim.gate_data('RY').hadamard_conjugated() is None + True + >>> stim.gate_data('RY').hadamard_conjugated(unsigned=True) + stim.gate_data('RY') + >>> stim.gate_data('ISWAP').hadamard_conjugated(unsigned=True) is None + True + >>> stim.gate_data('SWAP').hadamard_conjugated() + stim.gate_data('SWAP') + >>> stim.gate_data('CXSWAP').hadamard_conjugated() + stim.gate_data('SWAPCX') + >>> stim.gate_data('MXX').hadamard_conjugated() + stim.gate_data('MZZ') + >>> stim.gate_data('DEPOLARIZE1').hadamard_conjugated() + stim.gate_data('DEPOLARIZE1') + >>> stim.gate_data('X_ERROR').hadamard_conjugated() + stim.gate_data('Z_ERROR') + >>> stim.gate_data('H_XY').hadamard_conjugated(unsigned=True) + stim.gate_data('H_YZ') + >>> stim.gate_data('DETECTOR').hadamard_conjugated(unsigned=True) + stim.gate_data('DETECTOR') + )DOC") + .data()); + c.def_property_readonly( "is_two_qubit_gate", [](const Gate &self) -> bool { @@ -508,6 +625,10 @@ void stim_pybind::pybind_gate_data_methods(pybind11::module &m, pybind11::class_ Variable-qubit gates like CORRELATED_ERROR and MPP are not considered two qubit gates. + Returns: + True if the gate is a two qubit gate. + False if the gate isn't a two qubit gate. + Examples: >>> import stim diff --git a/src/stim/gates/gates.test.cc b/src/stim/gates/gates.test.cc index 8dad3d48..edafb88f 100644 --- a/src/stim/gates/gates.test.cc +++ b/src/stim/gates/gates.test.cc @@ -23,6 +23,7 @@ #include "stim/util_bot/str_util.h" #include "stim/util_bot/test_util.test.h" #include "stim/util_top/has_flow.h" +#include "stim/util_top/circuit_flow_generators.h" using namespace stim; @@ -305,3 +306,77 @@ TEST(gate_data, to_euler_angles_axis_reference) { } } } + +TEST(gate_data, is_symmetric_vs_flow_generators_of_two_qubit_gates) { + for (const auto &g : GATE_DATA.items) { + if ((g.flags & stim::GATE_IS_NOISY) && !(g.flags & stim::GATE_PRODUCES_RESULTS)) { + continue; + } + if (g.flags & GATE_TARGETS_PAIRS) { + Circuit c1; + Circuit c2; + c1.safe_append_u(g.name, {0, 1}, {}); + c2.safe_append_u(g.name, {1, 0}, {}); + auto f1 = circuit_flow_generators<64>(c1); + auto f2 = circuit_flow_generators<64>(c2); + EXPECT_EQ(g.is_symmetric(), f1 == f2) << g.name; + } + } +} + +TEST(gate_data, hadamard_conjugated_vs_flow_generators_of_two_qubit_gates) { + auto flow_key = [](const Circuit &circuit, bool ignore_sign) { + auto f = circuit_flow_generators<64>(circuit); + if (ignore_sign) { + for (auto &e : f) { + e.input.sign = false; + e.output.sign = false; + } + } + std::stringstream ss; + ss << comma_sep(f); + return ss.str(); + }; + std::map known_flows_s; + std::map> known_flows_u; + + for (const auto &g : GATE_DATA.items) { + if (g.arg_count != 0 && g.arg_count != ARG_COUNT_SYGIL_ZERO_OR_ONE && g.arg_count != ARG_COUNT_SYGIL_ANY) { + continue; + } + if ((g.flags & GATE_TARGETS_PAIRS) || (g.flags & GATE_IS_SINGLE_QUBIT_GATE)) { + Circuit c; + c.safe_append_u(g.name, {0, 1}, {}); + auto key_s = flow_key(c, false); + auto key_u = flow_key(c, true); + ASSERT_EQ(known_flows_s.find(key_s), known_flows_s.end()); + known_flows_s[key_s] = g.id; + known_flows_u[key_u].push_back(g.id); + } + } + for (const auto &g : GATE_DATA.items) { + if (g.arg_count != 0 && g.arg_count != ARG_COUNT_SYGIL_ZERO_OR_ONE && g.arg_count != ARG_COUNT_SYGIL_ANY) { + continue; + } + if ((g.flags & GATE_TARGETS_PAIRS) || (g.flags & GATE_IS_SINGLE_QUBIT_GATE)) { + Circuit c; + c.safe_append_u("H", {0, 1}, {}); + c.safe_append_u(g.name, {0, 1}, {}); + c.safe_append_u("H", {0, 1}, {}); + auto key_s = flow_key(c, false); + auto key_u = flow_key(c, true); + auto other_s = known_flows_s.find(key_s); + auto &other_us = known_flows_u[key_u]; + if (other_us.empty()) { + other_us.push_back(GateType::NOT_A_GATE); + } + + GateType expected_s = other_s == known_flows_s.end() ? GateType::NOT_A_GATE : other_s->second; + GateType actual_s = g.hadamard_conjugated(false); + GateType actual_u = g.hadamard_conjugated(true); + bool found = std::find(other_us.begin(), other_us.end(), actual_u) != other_us.end(); + EXPECT_EQ(actual_s, expected_s) << "signed " << g.name << " -> " << GATE_DATA[actual_s].name << " != " << GATE_DATA[expected_s].name; + EXPECT_TRUE(found) << "unsigned " << g.name << " -> " << GATE_DATA[actual_u].name << " not in " << GATE_DATA[other_us[0]].name; + } + } +} diff --git a/src/stim/gates/gates_test.py b/src/stim/gates/gates_test.py index b1cae79b..aae2b00c 100644 --- a/src/stim/gates/gates_test.py +++ b/src/stim/gates/gates_test.py @@ -81,3 +81,20 @@ def test_gate_data_flows(): stim.Flow("X -> Z"), stim.Flow("Z -> X"), ] + + +def test_gate_is_symmetric(): + assert stim.GateData('SWAP').is_symmetric_gate + assert stim.GateData('H').is_symmetric_gate + assert stim.GateData('MYY').is_symmetric_gate + assert stim.GateData('DEPOLARIZE2').is_symmetric_gate + assert not stim.GateData('PAULI_CHANNEL_2').is_symmetric_gate + assert not stim.GateData('DETECTOR').is_symmetric_gate + assert not stim.GateData('TICK').is_symmetric_gate + + +def test_gate_hadamard_conjugated(): + assert stim.GateData('CZSWAP').hadamard_conjugated(unsigned=True) is None + assert stim.GateData('TICK').hadamard_conjugated() == stim.GateData('TICK') + assert stim.GateData('MYY').hadamard_conjugated() == stim.GateData('MYY') + assert stim.GateData('XCZ').hadamard_conjugated() == stim.GateData('CX') diff --git a/src/stim/simulators/matched_error.pybind.cc b/src/stim/simulators/matched_error.pybind.cc index c177283f..984c74c4 100644 --- a/src/stim/simulators/matched_error.pybind.cc +++ b/src/stim/simulators/matched_error.pybind.cc @@ -27,11 +27,11 @@ using namespace stim_pybind; std::string CircuitErrorLocationStackFrame_repr(const CircuitErrorLocationStackFrame &self) { std::stringstream out; - out << "stim.CircuitErrorLocationStackFrame"; - out << "(instruction_offset=" << self.instruction_offset; - out << ", iteration_index=" << self.iteration_index; - out << ", instruction_repetitions_arg=" << self.instruction_repetitions_arg; - out << ")"; + out << "stim.CircuitErrorLocationStackFrame("; + out << "\n instruction_offset=" << self.instruction_offset << ","; + out << "\n iteration_index=" << self.iteration_index << ","; + out << "\n instruction_repetitions_arg=" << self.instruction_repetitions_arg << ","; + out << "\n)"; return out.str(); } @@ -57,21 +57,26 @@ pybind11::ssize_t CircuitTargetsInsideInstruction_hash(const CircuitTargetsInsid std::string GateTargetWithCoords_repr(const GateTargetWithCoords &self) { std::stringstream out; out << "stim.GateTargetWithCoords"; - out << "(gate_target=" << self.gate_target; - out << ", coords=[" << comma_sep(self.coords) << "]"; + out << "(" << self.gate_target; + out << ", [" << comma_sep(self.coords) << "]"; out << ")"; return out.str(); } std::string FlippedMeasurement_repr(const FlippedMeasurement &self) { std::stringstream out; - out << "stim.FlippedMeasurement"; - out << "(record_index=" << self.measurement_record_index; - out << ", observable=("; + out << "stim.FlippedMeasurement("; + out << "\n record_index="; + if (self.measurement_record_index == UINT64_MAX) { + out << "None"; + } else { + out << self.measurement_record_index; + } + out << ",\n observable=("; for (const auto &e : self.measured_observable) { out << GateTargetWithCoords_repr(e) << ","; } - out << "))"; + out << "),\n)"; return out.str(); } @@ -260,7 +265,6 @@ void stim_pybind::pybind_gate_target_with_coords_methods( [](const pybind11::object &gate_target, const std::vector &coords) -> GateTargetWithCoords { return GateTargetWithCoords{obj_to_gate_target(gate_target), coords}; }), - pybind11::kw_only(), pybind11::arg("gate_target"), pybind11::arg("coords"), clean_doc_string(R"DOC( @@ -379,8 +383,14 @@ void stim_pybind::pybind_flipped_measurement_methods( }); c.def( pybind11::init( - [](uint64_t measurement_record_index, const pybind11::object &measured_observable) -> FlippedMeasurement { - FlippedMeasurement result{measurement_record_index, {}}; + [](const pybind11::object &measurement_record_index, const pybind11::object &measured_observable) -> FlippedMeasurement { + uint64_t u; + if (measurement_record_index.is_none()) { + u = UINT64_MAX; + } else { + u = pybind11::cast(measurement_record_index); + } + FlippedMeasurement result{u, {}}; for (const auto &e : measured_observable) { result.measured_observable.push_back(pybind11::cast(e)); } @@ -390,7 +400,19 @@ void stim_pybind::pybind_flipped_measurement_methods( pybind11::arg("record_index"), pybind11::arg("observable"), clean_doc_string(R"DOC( + @signature def __init__(self, measurement_record_index: Optional[int], measured_observable: Iterable[stim.GateTargetWithCoords]): Creates a stim.FlippedMeasurement. + + Examples: + >>> import stim + >>> print(stim.FlippedMeasurement( + ... record_index=5, + ... observable=[], + ... )) + stim.FlippedMeasurement( + record_index=5, + observable=(), + ) )DOC") .data()); c.def("__repr__", &FlippedMeasurement_repr); @@ -494,7 +516,27 @@ pybind11::class_ stim_pybind::pybind_circuit_error_locatio "CircuitErrorLocation", clean_doc_string(R"DOC( Describes the location of an error mechanism from a stim circuit. - )DOC") + + Examples: + >>> import stim + >>> circuit = stim.Circuit.generated( + ... "repetition_code:memory", + ... distance=5, + ... rounds=5, + ... before_round_data_depolarization=1e-3, + ... ) + >>> logical_error = circuit.shortest_graphlike_error() + >>> error_location = logical_error[0].circuit_error_locations[0] + >>> print(error_location) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } + )DOC") .data()); } void stim_pybind::pybind_circuit_error_location_methods( @@ -503,8 +545,23 @@ void stim_pybind::pybind_circuit_error_location_methods( "tick_offset", &CircuitErrorLocation::tick_offset, clean_doc_string(R"DOC( - The number of TICKs that executed before the error mechanism being discussed, - including TICKs that occurred multiple times during loops. + The number of TICKs that executed before the error happened. + + This counts TICKs occurring multiple times during loops. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... TICK + ... TICK + ... TICK + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].tick_offset + 3 )DOC") .data()); @@ -513,7 +570,19 @@ void stim_pybind::pybind_circuit_error_location_methods( &CircuitErrorLocation::flipped_pauli_product, clean_doc_string(R"DOC( The Pauli errors that the error mechanism applied to qubits. + When the error is a measurement error, this will be an empty list. + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... Y_ERROR(0.125) 0 + ... M 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_pauli_product + [stim.GateTargetWithCoords(stim.target_y(0), [])] )DOC") .data()); @@ -526,9 +595,24 @@ void stim_pybind::pybind_circuit_error_location_methods( return pybind11::cast(self.flipped_measurement); }, clean_doc_string(R"DOC( + @signature def flipped_measurement(self) -> Optional[stim.FlippedMeasurement]: The measurement that was flipped by the error mechanism. + If the error isn't a measurement error, this will be None. - @signature def flipped_measurement(self) -> Optional[stim.FlippedMeasurement]: + + Examples: + >>> import stim + >>> err = stim.Circuit(''' + ... R 0 + ... M(0.125) 0 + ... OBSERVABLE_INCLUDE(0) rec[-1] + ... ''').shortest_graphlike_error() + >>> err[0].circuit_error_locations[0].flipped_measurement + stim.FlippedMeasurement( + record_index=0, + observable=(stim.GateTargetWithCoords(stim.target_z(0), []),), + ) + )DOC") .data()); @@ -546,8 +630,26 @@ void stim_pybind::pybind_circuit_error_location_methods( "stack_frames", &CircuitErrorLocation::stack_frames, clean_doc_string(R"DOC( - Where in the circuit's execution does the error mechanism occur, - accounting for things like nested loops that iterate multiple times. + Describes where in the circuit's execution the error happened. + + Multiple frames are needed because the error may occur within a loop, + or a loop nested inside a loop, or etc. + + 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 + [stim.CircuitErrorLocationStackFrame( + instruction_offset=2, + iteration_index=0, + instruction_repetitions_arg=0, + )] )DOC") .data()); @@ -584,6 +686,48 @@ void stim_pybind::pybind_circuit_error_location_methods( pybind11::arg("stack_frames"), clean_doc_string(R"DOC( Creates a stim.CircuitErrorLocation. + + Examples: + >>> import stim + >>> err = stim.CircuitErrorLocation( + ... tick_offset=1, + ... flipped_pauli_product=( + ... stim.GateTargetWithCoords( + ... gate_target=stim.target_x(0), + ... coords=[], + ... ), + ... ), + ... flipped_measurement=stim.FlippedMeasurement( + ... record_index=None, + ... observable=(), + ... ), + ... instruction_targets=stim.CircuitTargetsInsideInstruction( + ... gate='DEPOLARIZE1', + ... args=[0.001], + ... target_range_start=0, + ... target_range_end=1, + ... targets_in_range=(stim.GateTargetWithCoords( + ... gate_target=0, + ... coords=[], + ... ),) + ... ), + ... stack_frames=( + ... stim.CircuitErrorLocationStackFrame( + ... instruction_offset=2, + ... iteration_index=0, + ... instruction_repetitions_arg=0, + ... ), + ... ), + ... ) + >>> print(err) + CircuitErrorLocation { + flipped_pauli_product: X0 + Circuit location stack trace: + (after 1 TICKs) + at instruction #3 (DEPOLARIZE1) in the circuit + at target #1 of the instruction + resolving to DEPOLARIZE1(0.001) 0 + } )DOC") .data()); c.def("__repr__", &CircuitErrorLocation_repr);