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);