Skip to content

Commit

Permalink
Add stim.Circuit.flow_generators (#794)
Browse files Browse the repository at this point in the history
- Fix `MPAD` targets counting towards `circuit.num_qubits`
- Fix `stim::PauliString` move constructor not clearing `num_qubits`
- Fix `stim.has_flow` not ignoring noise
- Add `stim.CircuitInstruction.num_measurements`
- Add `stim::PauliString::operator<`
- Fix `SPP` and `SPP_DAG` not being supported by
`stim.PauliString.after/before`
  • Loading branch information
Strilanc committed Jul 13, 2024
1 parent e0d99eb commit 0685b5a
Show file tree
Hide file tree
Showing 34 changed files with 1,551 additions and 194 deletions.
100 changes: 100 additions & 0 deletions doc/python_api_reference_vDev.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.Circuit.diagram`](#stim.Circuit.diagram)
- [`stim.Circuit.explain_detector_error_model_errors`](#stim.Circuit.explain_detector_error_model_errors)
- [`stim.Circuit.flattened`](#stim.Circuit.flattened)
- [`stim.Circuit.flow_generators`](#stim.Circuit.flow_generators)
- [`stim.Circuit.from_file`](#stim.Circuit.from_file)
- [`stim.Circuit.generated`](#stim.Circuit.generated)
- [`stim.Circuit.get_detector_coordinates`](#stim.Circuit.get_detector_coordinates)
Expand Down Expand Up @@ -80,6 +81,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.CircuitInstruction.__str__`](#stim.CircuitInstruction.__str__)
- [`stim.CircuitInstruction.gate_args_copy`](#stim.CircuitInstruction.gate_args_copy)
- [`stim.CircuitInstruction.name`](#stim.CircuitInstruction.name)
- [`stim.CircuitInstruction.num_measurements`](#stim.CircuitInstruction.num_measurements)
- [`stim.CircuitInstruction.targets_copy`](#stim.CircuitInstruction.targets_copy)
- [`stim.CircuitRepeatBlock`](#stim.CircuitRepeatBlock)
- [`stim.CircuitRepeatBlock.__eq__`](#stim.CircuitRepeatBlock.__eq__)
Expand Down Expand Up @@ -1825,6 +1827,64 @@ def flattened(
"""
```

<a name="stim.Circuit.flow_generators"></a>
```python
# stim.Circuit.flow_generators

# (in class stim.Circuit)
def flow_generators(
self,
) -> List[stim.Flow]:
"""Returns a list of flows that generate all of the circuit's flows.
Every stabilizer flow that the circuit implements is a product of some
subset of the returned generators. Every returned flow will be a flow
of the circuit.
Returns:
A list of flow generators for the circuit.
Examples:
>>> import stim
>>> stim.Circuit("H 0").flow_generators()
[stim.Flow("X -> Z"), stim.Flow("Z -> X")]
>>> stim.Circuit("M 0").flow_generators()
[stim.Flow("1 -> Z xor rec[0]"), stim.Flow("Z -> rec[0]")]
>>> stim.Circuit("RX 0").flow_generators()
[stim.Flow("1 -> X")]
>>> for flow in stim.Circuit("MXX 0 1").flow_generators():
... print(flow)
1 -> XX xor rec[0]
_X -> _X
X_ -> _X xor rec[0]
ZZ -> ZZ
>>> for flow in stim.Circuit.generated(
... "repetition_code:memory",
... rounds=2,
... distance=3,
... after_clifford_depolarization=1e-3,
... ).flow_generators():
... print(flow)
1 -> rec[0]
1 -> rec[1]
1 -> rec[2]
1 -> rec[3]
1 -> rec[4]
1 -> rec[5]
1 -> rec[6]
1 -> ____Z
1 -> ___Z_
1 -> __Z__
1 -> _Z___
1 -> Z____
"""
```

<a name="stim.Circuit.from_file"></a>
```python
# stim.Circuit.from_file
Expand Down Expand Up @@ -2064,6 +2124,8 @@ def has_all_flows(
because, behind the scenes, the circuit can be iterated once instead of once
per flow.
This method ignores any noise in the circuit.
Args:
flows: An iterable of `stim.Flow` instances representing the flows to check.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -2131,6 +2193,8 @@ def has_flow(
A flow like P -> 1 means the circuit measures P.
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
This method ignores any noise in the circuit.
Args:
flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -2171,6 +2235,14 @@ def has_flow(
... ))
True
>>> stim.Circuit('''
... RY 0
... X_ERROR(0.1) 0
... ''').has_flow(stim.Flow(
... output=stim.PauliString("Y"),
... ))
True
>>> stim.Circuit('''
... RY 0
... ''').has_flow(stim.Flow(
Expand Down Expand Up @@ -3727,6 +3799,34 @@ def name(
"""
```

<a name="stim.CircuitInstruction.num_measurements"></a>
```python
# stim.CircuitInstruction.num_measurements

# (in class stim.CircuitInstruction)
@property
def num_measurements(
self,
) -> int:
"""Returns the number of bits produced when running this instruction.
Examples:
>>> import stim
>>> stim.CircuitInstruction('H', [0]).num_measurements
0
>>> stim.CircuitInstruction('M', [0]).num_measurements
1
>>> stim.CircuitInstruction('M', [2, 3, 5, 7, 11]).num_measurements
5
>>> stim.CircuitInstruction('MXX', [0, 1, 4, 5, 11, 13]).num_measurements
3
>>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements
2
>>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements
1
"""
```

<a name="stim.CircuitInstruction.targets_copy"></a>
```python
# stim.CircuitInstruction.targets_copy
Expand Down
84 changes: 84 additions & 0 deletions doc/stim.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,57 @@ class Circuit:
... ''').flattened_operations()
[('H', [6], 0), ('H', [6], 0)]
"""
def flow_generators(
self,
) -> List[stim.Flow]:
"""Returns a list of flows that generate all of the circuit's flows.
Every stabilizer flow that the circuit implements is a product of some
subset of the returned generators. Every returned flow will be a flow
of the circuit.
Returns:
A list of flow generators for the circuit.
Examples:
>>> import stim
>>> stim.Circuit("H 0").flow_generators()
[stim.Flow("X -> Z"), stim.Flow("Z -> X")]
>>> stim.Circuit("M 0").flow_generators()
[stim.Flow("1 -> Z xor rec[0]"), stim.Flow("Z -> rec[0]")]
>>> stim.Circuit("RX 0").flow_generators()
[stim.Flow("1 -> X")]
>>> for flow in stim.Circuit("MXX 0 1").flow_generators():
... print(flow)
1 -> XX xor rec[0]
_X -> _X
X_ -> _X xor rec[0]
ZZ -> ZZ
>>> for flow in stim.Circuit.generated(
... "repetition_code:memory",
... rounds=2,
... distance=3,
... after_clifford_depolarization=1e-3,
... ).flow_generators():
... print(flow)
1 -> rec[0]
1 -> rec[1]
1 -> rec[2]
1 -> rec[3]
1 -> rec[4]
1 -> rec[5]
1 -> rec[6]
1 -> ____Z
1 -> ___Z_
1 -> __Z__
1 -> _Z___
1 -> Z____
"""
@staticmethod
def from_file(
file: Union[io.TextIOBase, str, pathlib.Path],
Expand Down Expand Up @@ -1449,6 +1500,8 @@ class Circuit:
because, behind the scenes, the circuit can be iterated once instead of once
per flow.
This method ignores any noise in the circuit.
Args:
flows: An iterable of `stim.Flow` instances representing the flows to check.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -1509,6 +1562,8 @@ class Circuit:
A flow like P -> 1 means the circuit measures P.
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
This method ignores any noise in the circuit.
Args:
flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -1549,6 +1604,14 @@ class Circuit:
... ))
True
>>> stim.Circuit('''
... RY 0
... X_ERROR(0.1) 0
... ''').has_flow(stim.Flow(
... output=stim.PauliString("Y"),
... ))
True
>>> stim.Circuit('''
... RY 0
... ''').has_flow(stim.Flow(
Expand Down Expand Up @@ -2816,6 +2879,27 @@ class CircuitInstruction:
) -> str:
"""The name of the instruction (e.g. `H` or `X_ERROR` or `DETECTOR`).
"""
@property
def num_measurements(
self,
) -> int:
"""Returns the number of bits produced when running this instruction.
Examples:
>>> import stim
>>> stim.CircuitInstruction('H', [0]).num_measurements
0
>>> stim.CircuitInstruction('M', [0]).num_measurements
1
>>> stim.CircuitInstruction('M', [2, 3, 5, 7, 11]).num_measurements
5
>>> stim.CircuitInstruction('MXX', [0, 1, 4, 5, 11, 13]).num_measurements
3
>>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements
2
>>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements
1
"""
def targets_copy(
self,
) -> List[stim.GateTarget]:
Expand Down
1 change: 1 addition & 0 deletions file_lists/test_files
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ src/stim/util_bot/probability_util.test.cc
src/stim/util_bot/str_util.test.cc
src/stim/util_bot/test_util.test.cc
src/stim/util_bot/twiddle.test.cc
src/stim/util_top/circuit_flow_generators.test.cc
src/stim/util_top/circuit_inverse_qec.test.cc
src/stim/util_top/circuit_inverse_unitary.test.cc
src/stim/util_top/circuit_to_detecting_regions.test.cc
Expand Down
84 changes: 84 additions & 0 deletions glue/python/src/stim/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,57 @@ class Circuit:
... ''').flattened_operations()
[('H', [6], 0), ('H', [6], 0)]
"""
def flow_generators(
self,
) -> List[stim.Flow]:
"""Returns a list of flows that generate all of the circuit's flows.
Every stabilizer flow that the circuit implements is a product of some
subset of the returned generators. Every returned flow will be a flow
of the circuit.
Returns:
A list of flow generators for the circuit.
Examples:
>>> import stim
>>> stim.Circuit("H 0").flow_generators()
[stim.Flow("X -> Z"), stim.Flow("Z -> X")]
>>> stim.Circuit("M 0").flow_generators()
[stim.Flow("1 -> Z xor rec[0]"), stim.Flow("Z -> rec[0]")]
>>> stim.Circuit("RX 0").flow_generators()
[stim.Flow("1 -> X")]
>>> for flow in stim.Circuit("MXX 0 1").flow_generators():
... print(flow)
1 -> XX xor rec[0]
_X -> _X
X_ -> _X xor rec[0]
ZZ -> ZZ
>>> for flow in stim.Circuit.generated(
... "repetition_code:memory",
... rounds=2,
... distance=3,
... after_clifford_depolarization=1e-3,
... ).flow_generators():
... print(flow)
1 -> rec[0]
1 -> rec[1]
1 -> rec[2]
1 -> rec[3]
1 -> rec[4]
1 -> rec[5]
1 -> rec[6]
1 -> ____Z
1 -> ___Z_
1 -> __Z__
1 -> _Z___
1 -> Z____
"""
@staticmethod
def from_file(
file: Union[io.TextIOBase, str, pathlib.Path],
Expand Down Expand Up @@ -1449,6 +1500,8 @@ class Circuit:
because, behind the scenes, the circuit can be iterated once instead of once
per flow.
This method ignores any noise in the circuit.
Args:
flows: An iterable of `stim.Flow` instances representing the flows to check.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -1509,6 +1562,8 @@ class Circuit:
A flow like P -> 1 means the circuit measures P.
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
This method ignores any noise in the circuit.
Args:
flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
Expand Down Expand Up @@ -1549,6 +1604,14 @@ class Circuit:
... ))
True
>>> stim.Circuit('''
... RY 0
... X_ERROR(0.1) 0
... ''').has_flow(stim.Flow(
... output=stim.PauliString("Y"),
... ))
True
>>> stim.Circuit('''
... RY 0
... ''').has_flow(stim.Flow(
Expand Down Expand Up @@ -2816,6 +2879,27 @@ class CircuitInstruction:
) -> str:
"""The name of the instruction (e.g. `H` or `X_ERROR` or `DETECTOR`).
"""
@property
def num_measurements(
self,
) -> int:
"""Returns the number of bits produced when running this instruction.
Examples:
>>> import stim
>>> stim.CircuitInstruction('H', [0]).num_measurements
0
>>> stim.CircuitInstruction('M', [0]).num_measurements
1
>>> stim.CircuitInstruction('M', [2, 3, 5, 7, 11]).num_measurements
5
>>> stim.CircuitInstruction('MXX', [0, 1, 4, 5, 11, 13]).num_measurements
3
>>> stim.Circuit('MPP X0*X1 X0*Z1*Y2')[0].num_measurements
2
>>> stim.CircuitInstruction('HERALDED_ERASE', [0]).num_measurements
1
"""
def targets_copy(
self,
) -> List[stim.GateTarget]:
Expand Down
Loading

0 comments on commit 0685b5a

Please sign in to comment.