diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index 38c116751..4a197b909 100644
--- a/doc/python_api_reference_vDev.md
+++ b/doc/python_api_reference_vDev.md
@@ -35,6 +35,7 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.Circuit.generated`](#stim.Circuit.generated)
- [`stim.Circuit.get_detector_coordinates`](#stim.Circuit.get_detector_coordinates)
- [`stim.Circuit.get_final_qubit_coordinates`](#stim.Circuit.get_final_qubit_coordinates)
+ - [`stim.Circuit.has_all_flows`](#stim.Circuit.has_all_flows)
- [`stim.Circuit.has_flow`](#stim.Circuit.has_flow)
- [`stim.Circuit.inverse`](#stim.Circuit.inverse)
- [`stim.Circuit.num_detectors`](#stim.Circuit.num_detectors)
@@ -188,6 +189,15 @@ API references for stable versions are kept on the [stim github wiki](https://gi
- [`stim.FlippedMeasurement.__init__`](#stim.FlippedMeasurement.__init__)
- [`stim.FlippedMeasurement.observable`](#stim.FlippedMeasurement.observable)
- [`stim.FlippedMeasurement.record_index`](#stim.FlippedMeasurement.record_index)
+- [`stim.Flow`](#stim.Flow)
+ - [`stim.Flow.__eq__`](#stim.Flow.__eq__)
+ - [`stim.Flow.__init__`](#stim.Flow.__init__)
+ - [`stim.Flow.__ne__`](#stim.Flow.__ne__)
+ - [`stim.Flow.__repr__`](#stim.Flow.__repr__)
+ - [`stim.Flow.__str__`](#stim.Flow.__str__)
+ - [`stim.Flow.input_copy`](#stim.Flow.input_copy)
+ - [`stim.Flow.measurements_copy`](#stim.Flow.measurements_copy)
+ - [`stim.Flow.output_copy`](#stim.Flow.output_copy)
- [`stim.GateData`](#stim.GateData)
- [`stim.GateData.__eq__`](#stim.GateData.__eq__)
- [`stim.GateData.__init__`](#stim.GateData.__init__)
@@ -1887,6 +1897,66 @@ def get_final_qubit_coordinates(
"""
```
+
+```python
+# stim.Circuit.has_all_flows
+
+# (in class stim.Circuit)
+def has_all_flows(
+ self,
+ flows: Iterable[stim.Flow],
+ *,
+ unsigned: bool = False,
+) -> bool:
+ """Determines if the circuit has all the given stabilizer flow or not.
+
+ This is a faster version of `all(c.has_flow(f) for f in flows)`. It's faster
+ because, behind the scenes, the circuit can be iterated once instead of once
+ per flow.
+
+ 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
+ the sign of the Pauli strings. When True, only the Pauli terms need to
+ be correct; the signs are permitted to be inverted. In effect, this
+ requires the circuit to be correct up to Pauli gates.
+
+ Returns:
+ True if the circuit has the given flow; False otherwise.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ False
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> -Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ True
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ], unsigned=True)
+ True
+
+ Caveats:
+ Currently, the unsigned=False version of this method is implemented by
+ performing 256 randomized tests. Each test has a 50% chance of a false
+ positive, and a 0% chance of a false negative. So, when the method returns
+ True, there is technically still a 2^-256 chance the circuit doesn't have
+ the flow. This is lower than the chance of a cosmic ray flipping the result.
+ """
+```
+
```python
# stim.Circuit.has_flow
@@ -1894,14 +1964,11 @@ def get_final_qubit_coordinates(
# (in class stim.Circuit)
def has_flow(
self,
- shorthand: Optional[str] = None,
+ flow: stim.Flow,
*,
- start: Union[None, str, stim.PauliString] = None,
- end: Union[None, str, stim.PauliString] = None,
- measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
- """Determines if the circuit has a stabilizer flow or not.
+ """Determines if the circuit has the given stabilizer flow or not.
A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
P at the start of the circuit to the instantaneous stabilizer Q at the end of
@@ -1915,26 +1982,7 @@ def has_flow(
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
Args:
- shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]".
- The text must contain "->" to separate the input pauli string from the
- output pauli string. Measurements are included by appending
- " xor rec[k]" for each measurement index k. Indexing uses the python
- convention where non-negative indices index from the start and negative
- indices index from the end. The pauli strings are parsed as if by
- `stim.PauliString.__init__`.
- start: The input into the flow at the start of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- end: The output from the flow at the end of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- measurements: Defaults to None (empty). The indices of measurements to
- include in the flow. This should be a collection of integers and/or
- stim.GateTarget instances. Indexing uses the python convention where
- non-negative indices index from the start and negative indices index
- from the end.
+ flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
the sign of the Pauli strings. When True, only the Pauli terms need to
be correct; the signs are permitted to be inverted. In effect, this
@@ -1943,56 +1991,49 @@ def has_flow(
Returns:
True if the circuit has the given flow; False otherwise.
- References:
- Stim's gate documentation includes the stabilizer flows of each gate.
-
- Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
- defined and provides a circuit construction for experimentally verifying
- their presence.
-
Examples:
>>> import stim
>>> m = stim.Circuit('M 0')
- >>> m.has_flow('Z -> Z')
+ >>> m.has_flow(stim.Flow('Z -> Z'))
True
- >>> m.has_flow('X -> X')
+ >>> m.has_flow(stim.Flow('X -> X'))
False
- >>> m.has_flow('Z -> I')
+ >>> m.has_flow(stim.Flow('Z -> I'))
False
- >>> m.has_flow('Z -> I xor rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> I xor rec[-1]'))
True
- >>> m.has_flow('Z -> rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> rec[-1]'))
True
>>> cx58 = stim.Circuit('CX 5 8')
- >>> cx58.has_flow('X5 -> X5*X8')
+ >>> cx58.has_flow(stim.Flow('X5 -> X5*X8'))
True
- >>> cx58.has_flow('X_ -> XX')
+ >>> cx58.has_flow(stim.Flow('X_ -> XX'))
False
- >>> cx58.has_flow('_____X___ -> _____X__X')
+ >>> cx58.has_flow(stim.Flow('_____X___ -> _____X__X'))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("Y"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("Y"),
+ ... ))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("X"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("X"),
+ ... ))
False
>>> stim.Circuit('''
... CX 0 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_"),
- ... end=stim.PauliString("+XX"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_"),
+ ... output=stim.PauliString("+XX"),
+ ... ))
True
>>> stim.Circuit('''
@@ -2001,22 +2042,29 @@ def has_flow(
... MXX 0 1
... MZZ 1 2
... MX 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_X"),
- ... end=stim.PauliString("+__X"),
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_X"),
+ ... output=stim.PauliString("+__X"),
... measurements=[0, 2],
- ... )
+ ... ))
True
>>> stim.Circuit('''
... H 0
... ''').has_flow(
- ... start=stim.PauliString("Y"),
- ... end=stim.PauliString("Y"),
+ ... stim.Flow("Y -> Y"),
... unsigned=True,
... )
True
+ >>> stim.Circuit('''
+ ... H 0
+ ... ''').has_flow(
+ ... stim.Flow("Y -> Y"),
+ ... unsigned=False,
+ ... )
+ False
+
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
@@ -6750,6 +6798,219 @@ def record_index(
"""
```
+
+```python
+# stim.Flow
+
+# (at top-level in the stim module)
+class Flow:
+ """A stabilizer flow (e.g. "XI -> XX xor rec[-1]").
+
+ Stabilizer circuits implement, and can be defined by, how they turn input
+ stabilizers into output stabilizers mediated by measurements. These
+ relationships are called stabilizer flows, and `stim.Flow` is a representation
+ of such a flow. For example, a `stim.Flow` can be given to
+ `stim.Circuit.has_flow` to verify that a circuit implements the flow.
+
+ A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
+ P at the start of the circuit to the instantaneous stabilizer Q at the end of
+ the circuit. The flow may be mediated by certain measurements. For example,
+ a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and
+ the CNOT flows implemented by the circuit involve these measurements.
+
+ A flow like P -> Q means the circuit transforms P into Q.
+ A flow like 1 -> P means the circuit prepares P.
+ 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).
+
+ References:
+ Stim's gate documentation includes the stabilizer flows of each gate.
+
+ Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
+ defined and provides a circuit construction for experimentally verifying
+ their presence.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit("CNOT 2 4")
+
+ >>> c.has_flow(stim.Flow("__X__ -> __X_X"))
+ True
+
+ >>> c.has_flow(stim.Flow("X2*X4 -> X2"))
+ True
+
+ >>> c.has_flow(stim.Flow("Z4 -> Z4"))
+ False
+ """
+```
+
+
+```python
+# stim.Flow.__eq__
+
+# (in class stim.Flow)
+def __eq__(
+ self,
+ arg0: stim.Flow,
+) -> bool:
+ """Determines if two flows have identical contents.
+ """
+```
+
+
+```python
+# stim.Flow.__init__
+
+# (in class stim.Flow)
+def __init__(
+ self,
+ arg: Union[None, str, stim.Flow] = None,
+ /,
+ *,
+ input: Optional[stim.PauliString] = None,
+ output: Optional[stim.PauliString] = None,
+ measurements: Optional[Iterable[Union[int, GateTarget]]] = None,
+) -> None:
+ """Initializes a stim.Flow.
+
+ When given a string, the string is parsed as flow shorthand. For example,
+ the string "X_ -> ZZ xor rec[-1]" will result in a flow with input pauli string
+ "X_", output pauli string "ZZ", and measurement indices [-1].
+
+ Arguments:
+ arg [position-only]: Defaults to None. Must be specified by itself if used.
+ str: Initializes a flow by parsing the given shorthand text.
+ stim.Flow: Initializes a copy of the given flow.
+ None (default): Initializes an empty flow.
+ input: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's input stabilizer.
+ output: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's output stabilizer.
+ measurements: Can be set to a list of integers or gate targets like
+ `stim.target_rec(-1)`, to specify the measurements that mediate the
+ flow. Negative and positive measurement indices are allowed. Indexes
+ follow the python convention where -1 is the last measurement in a
+ circuit and 0 is the first measurement in a circuit.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Flow("X2 -> -Y2*Z4 xor rec[-1]")
+ stim.Flow("__X -> -__Y_Z xor rec[-1]")
+
+ >>> stim.Flow("Z -> 1 xor rec[-1]")
+ stim.Flow("Z -> rec[-1]")
+
+ >>> stim.Flow(
+ ... input=stim.PauliString("XX"),
+ ... output=stim.PauliString("_X"),
+ ... measurements=[],
+ ... )
+ stim.Flow("XX -> _X")
+ """
+```
+
+
+```python
+# stim.Flow.__ne__
+
+# (in class stim.Flow)
+def __ne__(
+ self,
+ arg0: stim.Flow,
+) -> bool:
+ """Determines if two flows have non-identical contents.
+ """
+```
+
+
+```python
+# stim.Flow.__repr__
+
+# (in class stim.Flow)
+def __repr__(
+ self,
+) -> str:
+ """Returns valid python code evaluating to an equivalent `stim.Flow`.
+ """
+```
+
+
+```python
+# stim.Flow.__str__
+
+# (in class stim.Flow)
+def __str__(
+ self,
+) -> str:
+ """Returns a shorthand description of the flow.
+ """
+```
+
+
+```python
+# stim.Flow.input_copy
+
+# (in class stim.Flow)
+def input_copy(
+ self,
+) -> stim.PauliString:
+ """Returns a copy of the flow's input stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(input=stim.PauliString('XX'))
+ >>> f.input_copy()
+ stim.PauliString("+XX")
+
+ >>> f.input_copy() is f.input_copy()
+ False
+ """
+```
+
+
+```python
+# stim.Flow.measurements_copy
+
+# (in class stim.Flow)
+def measurements_copy(
+ self,
+) -> List[int]:
+ """Returns a copy of the flow's measurement indices.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(measurements=[-1, 2])
+ >>> f.measurements_copy()
+ [-1, 2]
+
+ >>> f.measurements_copy() is f.measurements_copy()
+ False
+ """
+```
+
+
+```python
+# stim.Flow.output_copy
+
+# (in class stim.Flow)
+def output_copy(
+ self,
+) -> stim.PauliString:
+ """Returns a copy of the flow's output stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(output=stim.PauliString('XX'))
+ >>> f.output_copy()
+ stim.PauliString("+XX")
+
+ >>> f.output_copy() is f.output_copy()
+ False
+ """
+```
+
```python
# stim.GateData
diff --git a/doc/stim.pyi b/doc/stim.pyi
index 3a8214ccc..7e476efcc 100644
--- a/doc/stim.pyi
+++ b/doc/stim.pyi
@@ -1311,16 +1311,66 @@ class Circuit:
>>> circuit.get_final_qubit_coordinates()
{1: [1.0, 2.0, 3.0]}
"""
+ def has_all_flows(
+ self,
+ flows: Iterable[stim.Flow],
+ *,
+ unsigned: bool = False,
+ ) -> bool:
+ """Determines if the circuit has all the given stabilizer flow or not.
+
+ This is a faster version of `all(c.has_flow(f) for f in flows)`. It's faster
+ because, behind the scenes, the circuit can be iterated once instead of once
+ per flow.
+
+ 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
+ the sign of the Pauli strings. When True, only the Pauli terms need to
+ be correct; the signs are permitted to be inverted. In effect, this
+ requires the circuit to be correct up to Pauli gates.
+
+ Returns:
+ True if the circuit has the given flow; False otherwise.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ False
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> -Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ True
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ], unsigned=True)
+ True
+
+ Caveats:
+ Currently, the unsigned=False version of this method is implemented by
+ performing 256 randomized tests. Each test has a 50% chance of a false
+ positive, and a 0% chance of a false negative. So, when the method returns
+ True, there is technically still a 2^-256 chance the circuit doesn't have
+ the flow. This is lower than the chance of a cosmic ray flipping the result.
+ """
def has_flow(
self,
- shorthand: Optional[str] = None,
+ flow: stim.Flow,
*,
- start: Union[None, str, stim.PauliString] = None,
- end: Union[None, str, stim.PauliString] = None,
- measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
- """Determines if the circuit has a stabilizer flow or not.
+ """Determines if the circuit has the given stabilizer flow or not.
A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
P at the start of the circuit to the instantaneous stabilizer Q at the end of
@@ -1334,26 +1384,7 @@ class Circuit:
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
Args:
- shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]".
- The text must contain "->" to separate the input pauli string from the
- output pauli string. Measurements are included by appending
- " xor rec[k]" for each measurement index k. Indexing uses the python
- convention where non-negative indices index from the start and negative
- indices index from the end. The pauli strings are parsed as if by
- `stim.PauliString.__init__`.
- start: The input into the flow at the start of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- end: The output from the flow at the end of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- measurements: Defaults to None (empty). The indices of measurements to
- include in the flow. This should be a collection of integers and/or
- stim.GateTarget instances. Indexing uses the python convention where
- non-negative indices index from the start and negative indices index
- from the end.
+ flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
the sign of the Pauli strings. When True, only the Pauli terms need to
be correct; the signs are permitted to be inverted. In effect, this
@@ -1362,56 +1393,49 @@ class Circuit:
Returns:
True if the circuit has the given flow; False otherwise.
- References:
- Stim's gate documentation includes the stabilizer flows of each gate.
-
- Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
- defined and provides a circuit construction for experimentally verifying
- their presence.
-
Examples:
>>> import stim
>>> m = stim.Circuit('M 0')
- >>> m.has_flow('Z -> Z')
+ >>> m.has_flow(stim.Flow('Z -> Z'))
True
- >>> m.has_flow('X -> X')
+ >>> m.has_flow(stim.Flow('X -> X'))
False
- >>> m.has_flow('Z -> I')
+ >>> m.has_flow(stim.Flow('Z -> I'))
False
- >>> m.has_flow('Z -> I xor rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> I xor rec[-1]'))
True
- >>> m.has_flow('Z -> rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> rec[-1]'))
True
>>> cx58 = stim.Circuit('CX 5 8')
- >>> cx58.has_flow('X5 -> X5*X8')
+ >>> cx58.has_flow(stim.Flow('X5 -> X5*X8'))
True
- >>> cx58.has_flow('X_ -> XX')
+ >>> cx58.has_flow(stim.Flow('X_ -> XX'))
False
- >>> cx58.has_flow('_____X___ -> _____X__X')
+ >>> cx58.has_flow(stim.Flow('_____X___ -> _____X__X'))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("Y"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("Y"),
+ ... ))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("X"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("X"),
+ ... ))
False
>>> stim.Circuit('''
... CX 0 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_"),
- ... end=stim.PauliString("+XX"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_"),
+ ... output=stim.PauliString("+XX"),
+ ... ))
True
>>> stim.Circuit('''
@@ -1420,22 +1444,29 @@ class Circuit:
... MXX 0 1
... MZZ 1 2
... MX 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_X"),
- ... end=stim.PauliString("+__X"),
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_X"),
+ ... output=stim.PauliString("+__X"),
... measurements=[0, 2],
- ... )
+ ... ))
True
>>> stim.Circuit('''
... H 0
... ''').has_flow(
- ... start=stim.PauliString("Y"),
- ... end=stim.PauliString("Y"),
+ ... stim.Flow("Y -> Y"),
... unsigned=True,
... )
True
+ >>> stim.Circuit('''
+ ... H 0
+ ... ''').has_flow(
+ ... stim.Flow("Y -> Y"),
+ ... unsigned=False,
+ ... )
+ False
+
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
@@ -5159,6 +5190,156 @@ class FlippedMeasurement:
For example, the fifth measurement in a circuit has a measurement
record index of 4.
"""
+class Flow:
+ """A stabilizer flow (e.g. "XI -> XX xor rec[-1]").
+
+ Stabilizer circuits implement, and can be defined by, how they turn input
+ stabilizers into output stabilizers mediated by measurements. These
+ relationships are called stabilizer flows, and `stim.Flow` is a representation
+ of such a flow. For example, a `stim.Flow` can be given to
+ `stim.Circuit.has_flow` to verify that a circuit implements the flow.
+
+ A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
+ P at the start of the circuit to the instantaneous stabilizer Q at the end of
+ the circuit. The flow may be mediated by certain measurements. For example,
+ a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and
+ the CNOT flows implemented by the circuit involve these measurements.
+
+ A flow like P -> Q means the circuit transforms P into Q.
+ A flow like 1 -> P means the circuit prepares P.
+ 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).
+
+ References:
+ Stim's gate documentation includes the stabilizer flows of each gate.
+
+ Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
+ defined and provides a circuit construction for experimentally verifying
+ their presence.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit("CNOT 2 4")
+
+ >>> c.has_flow(stim.Flow("__X__ -> __X_X"))
+ True
+
+ >>> c.has_flow(stim.Flow("X2*X4 -> X2"))
+ True
+
+ >>> c.has_flow(stim.Flow("Z4 -> Z4"))
+ False
+ """
+ def __eq__(
+ self,
+ arg0: stim.Flow,
+ ) -> bool:
+ """Determines if two flows have identical contents.
+ """
+ def __init__(
+ self,
+ arg: Union[None, str, stim.Flow] = None,
+ /,
+ *,
+ input: Optional[stim.PauliString] = None,
+ output: Optional[stim.PauliString] = None,
+ measurements: Optional[Iterable[Union[int, GateTarget]]] = None,
+ ) -> None:
+ """Initializes a stim.Flow.
+
+ When given a string, the string is parsed as flow shorthand. For example,
+ the string "X_ -> ZZ xor rec[-1]" will result in a flow with input pauli string
+ "X_", output pauli string "ZZ", and measurement indices [-1].
+
+ Arguments:
+ arg [position-only]: Defaults to None. Must be specified by itself if used.
+ str: Initializes a flow by parsing the given shorthand text.
+ stim.Flow: Initializes a copy of the given flow.
+ None (default): Initializes an empty flow.
+ input: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's input stabilizer.
+ output: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's output stabilizer.
+ measurements: Can be set to a list of integers or gate targets like
+ `stim.target_rec(-1)`, to specify the measurements that mediate the
+ flow. Negative and positive measurement indices are allowed. Indexes
+ follow the python convention where -1 is the last measurement in a
+ circuit and 0 is the first measurement in a circuit.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Flow("X2 -> -Y2*Z4 xor rec[-1]")
+ stim.Flow("__X -> -__Y_Z xor rec[-1]")
+
+ >>> stim.Flow("Z -> 1 xor rec[-1]")
+ stim.Flow("Z -> rec[-1]")
+
+ >>> stim.Flow(
+ ... input=stim.PauliString("XX"),
+ ... output=stim.PauliString("_X"),
+ ... measurements=[],
+ ... )
+ stim.Flow("XX -> _X")
+ """
+ def __ne__(
+ self,
+ arg0: stim.Flow,
+ ) -> bool:
+ """Determines if two flows have non-identical contents.
+ """
+ def __repr__(
+ self,
+ ) -> str:
+ """Returns valid python code evaluating to an equivalent `stim.Flow`.
+ """
+ def __str__(
+ self,
+ ) -> str:
+ """Returns a shorthand description of the flow.
+ """
+ def input_copy(
+ self,
+ ) -> stim.PauliString:
+ """Returns a copy of the flow's input stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(input=stim.PauliString('XX'))
+ >>> f.input_copy()
+ stim.PauliString("+XX")
+
+ >>> f.input_copy() is f.input_copy()
+ False
+ """
+ def measurements_copy(
+ self,
+ ) -> List[int]:
+ """Returns a copy of the flow's measurement indices.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(measurements=[-1, 2])
+ >>> f.measurements_copy()
+ [-1, 2]
+
+ >>> f.measurements_copy() is f.measurements_copy()
+ False
+ """
+ def output_copy(
+ self,
+ ) -> stim.PauliString:
+ """Returns a copy of the flow's output stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(output=stim.PauliString('XX'))
+ >>> f.output_copy()
+ stim.PauliString("+XX")
+
+ >>> f.output_copy() is f.output_copy()
+ False
+ """
class GateData:
"""Details about a gate supported by stim.
@@ -5235,22 +5416,22 @@ class GateData:
>>> for e in stim.gate_data('H').__unstable_flows:
... print(e)
- +X -> +Z
- +Z -> +X
+ X -> Z
+ Z -> X
>>> for e in stim.gate_data('ISWAP').__unstable_flows:
... print(e)
- +X_ -> +ZY
- +Z_ -> +_Z
- +_X -> +YZ
- +_Z -> +Z_
+ X_ -> ZY
+ Z_ -> _Z
+ _X -> YZ
+ _Z -> Z_
>>> for e in stim.gate_data('MXX').__unstable_flows:
... print(e)
- +X_ -> +X_
- +_X -> +_X
- +ZZ -> +ZZ
- +XX -> rec[-1]
+ X_ -> X_
+ _X -> _X
+ ZZ -> ZZ
+ XX -> rec[-1]
"""
@property
def aliases(
diff --git a/file_lists/python_api_files b/file_lists/python_api_files
index 25a80a28d..068bbe0fe 100644
--- a/file_lists/python_api_files
+++ b/file_lists/python_api_files
@@ -20,6 +20,7 @@ src/stim/simulators/frame_simulator.pybind.cc
src/stim/simulators/matched_error.pybind.cc
src/stim/simulators/measurements_to_detection_events.pybind.cc
src/stim/simulators/tableau_simulator.pybind.cc
+src/stim/stabilizers/flow.pybind.cc
src/stim/stabilizers/pauli_string.pybind.cc
src/stim/stabilizers/pauli_string_iter.pybind.cc
src/stim/stabilizers/tableau.pybind.cc
diff --git a/file_lists/test_files b/file_lists/test_files
index 6506e3f6a..b08819aff 100644
--- a/file_lists/test_files
+++ b/file_lists/test_files
@@ -4,7 +4,6 @@ src/stim/circuit/circuit.test.cc
src/stim/circuit/export_qasm.test.cc
src/stim/circuit/gate_decomposition.test.cc
src/stim/circuit/gate_target.test.cc
-src/stim/circuit/stabilizer_flow.test.cc
src/stim/cmd/command_analyze_errors.test.cc
src/stim/cmd/command_convert.test.cc
src/stim/cmd/command_detect.test.cc
@@ -72,6 +71,7 @@ src/stim/simulators/transform_without_feedback.test.cc
src/stim/simulators/vector_simulator.test.cc
src/stim/stabilizers/conversions.test.cc
src/stim/stabilizers/flex_pauli_string.test.cc
+src/stim/stabilizers/flow.test.cc
src/stim/stabilizers/pauli_string.test.cc
src/stim/stabilizers/pauli_string_iter.test.cc
src/stim/stabilizers/pauli_string_ref.test.cc
diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi
index 3a8214ccc..7e476efcc 100644
--- a/glue/python/src/stim/__init__.pyi
+++ b/glue/python/src/stim/__init__.pyi
@@ -1311,16 +1311,66 @@ class Circuit:
>>> circuit.get_final_qubit_coordinates()
{1: [1.0, 2.0, 3.0]}
"""
+ def has_all_flows(
+ self,
+ flows: Iterable[stim.Flow],
+ *,
+ unsigned: bool = False,
+ ) -> bool:
+ """Determines if the circuit has all the given stabilizer flow or not.
+
+ This is a faster version of `all(c.has_flow(f) for f in flows)`. It's faster
+ because, behind the scenes, the circuit can be iterated once instead of once
+ per flow.
+
+ 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
+ the sign of the Pauli strings. When True, only the Pauli terms need to
+ be correct; the signs are permitted to be inverted. In effect, this
+ requires the circuit to be correct up to Pauli gates.
+
+ Returns:
+ True if the circuit has the given flow; False otherwise.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ False
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> -Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ True
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ], unsigned=True)
+ True
+
+ Caveats:
+ Currently, the unsigned=False version of this method is implemented by
+ performing 256 randomized tests. Each test has a 50% chance of a false
+ positive, and a 0% chance of a false negative. So, when the method returns
+ True, there is technically still a 2^-256 chance the circuit doesn't have
+ the flow. This is lower than the chance of a cosmic ray flipping the result.
+ """
def has_flow(
self,
- shorthand: Optional[str] = None,
+ flow: stim.Flow,
*,
- start: Union[None, str, stim.PauliString] = None,
- end: Union[None, str, stim.PauliString] = None,
- measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None,
unsigned: bool = False,
) -> bool:
- """Determines if the circuit has a stabilizer flow or not.
+ """Determines if the circuit has the given stabilizer flow or not.
A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
P at the start of the circuit to the instantaneous stabilizer Q at the end of
@@ -1334,26 +1384,7 @@ class Circuit:
A flow like 1 -> 1 means the circuit contains a check (could be a DETECTOR).
Args:
- shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]".
- The text must contain "->" to separate the input pauli string from the
- output pauli string. Measurements are included by appending
- " xor rec[k]" for each measurement index k. Indexing uses the python
- convention where non-negative indices index from the start and negative
- indices index from the end. The pauli strings are parsed as if by
- `stim.PauliString.__init__`.
- start: The input into the flow at the start of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- end: The output from the flow at the end of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- measurements: Defaults to None (empty). The indices of measurements to
- include in the flow. This should be a collection of integers and/or
- stim.GateTarget instances. Indexing uses the python convention where
- non-negative indices index from the start and negative indices index
- from the end.
+ flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
the sign of the Pauli strings. When True, only the Pauli terms need to
be correct; the signs are permitted to be inverted. In effect, this
@@ -1362,56 +1393,49 @@ class Circuit:
Returns:
True if the circuit has the given flow; False otherwise.
- References:
- Stim's gate documentation includes the stabilizer flows of each gate.
-
- Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
- defined and provides a circuit construction for experimentally verifying
- their presence.
-
Examples:
>>> import stim
>>> m = stim.Circuit('M 0')
- >>> m.has_flow('Z -> Z')
+ >>> m.has_flow(stim.Flow('Z -> Z'))
True
- >>> m.has_flow('X -> X')
+ >>> m.has_flow(stim.Flow('X -> X'))
False
- >>> m.has_flow('Z -> I')
+ >>> m.has_flow(stim.Flow('Z -> I'))
False
- >>> m.has_flow('Z -> I xor rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> I xor rec[-1]'))
True
- >>> m.has_flow('Z -> rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> rec[-1]'))
True
>>> cx58 = stim.Circuit('CX 5 8')
- >>> cx58.has_flow('X5 -> X5*X8')
+ >>> cx58.has_flow(stim.Flow('X5 -> X5*X8'))
True
- >>> cx58.has_flow('X_ -> XX')
+ >>> cx58.has_flow(stim.Flow('X_ -> XX'))
False
- >>> cx58.has_flow('_____X___ -> _____X__X')
+ >>> cx58.has_flow(stim.Flow('_____X___ -> _____X__X'))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("Y"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("Y"),
+ ... ))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("X"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("X"),
+ ... ))
False
>>> stim.Circuit('''
... CX 0 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_"),
- ... end=stim.PauliString("+XX"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_"),
+ ... output=stim.PauliString("+XX"),
+ ... ))
True
>>> stim.Circuit('''
@@ -1420,22 +1444,29 @@ class Circuit:
... MXX 0 1
... MZZ 1 2
... MX 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_X"),
- ... end=stim.PauliString("+__X"),
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_X"),
+ ... output=stim.PauliString("+__X"),
... measurements=[0, 2],
- ... )
+ ... ))
True
>>> stim.Circuit('''
... H 0
... ''').has_flow(
- ... start=stim.PauliString("Y"),
- ... end=stim.PauliString("Y"),
+ ... stim.Flow("Y -> Y"),
... unsigned=True,
... )
True
+ >>> stim.Circuit('''
+ ... H 0
+ ... ''').has_flow(
+ ... stim.Flow("Y -> Y"),
+ ... unsigned=False,
+ ... )
+ False
+
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
@@ -5159,6 +5190,156 @@ class FlippedMeasurement:
For example, the fifth measurement in a circuit has a measurement
record index of 4.
"""
+class Flow:
+ """A stabilizer flow (e.g. "XI -> XX xor rec[-1]").
+
+ Stabilizer circuits implement, and can be defined by, how they turn input
+ stabilizers into output stabilizers mediated by measurements. These
+ relationships are called stabilizer flows, and `stim.Flow` is a representation
+ of such a flow. For example, a `stim.Flow` can be given to
+ `stim.Circuit.has_flow` to verify that a circuit implements the flow.
+
+ A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
+ P at the start of the circuit to the instantaneous stabilizer Q at the end of
+ the circuit. The flow may be mediated by certain measurements. For example,
+ a lattice surgery CNOT involves an MXX measurement and an MZZ measurement, and
+ the CNOT flows implemented by the circuit involve these measurements.
+
+ A flow like P -> Q means the circuit transforms P into Q.
+ A flow like 1 -> P means the circuit prepares P.
+ 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).
+
+ References:
+ Stim's gate documentation includes the stabilizer flows of each gate.
+
+ Appendix A of https://arxiv.org/abs/2302.02192 describes how flows are
+ defined and provides a circuit construction for experimentally verifying
+ their presence.
+
+ Examples:
+ >>> import stim
+ >>> c = stim.Circuit("CNOT 2 4")
+
+ >>> c.has_flow(stim.Flow("__X__ -> __X_X"))
+ True
+
+ >>> c.has_flow(stim.Flow("X2*X4 -> X2"))
+ True
+
+ >>> c.has_flow(stim.Flow("Z4 -> Z4"))
+ False
+ """
+ def __eq__(
+ self,
+ arg0: stim.Flow,
+ ) -> bool:
+ """Determines if two flows have identical contents.
+ """
+ def __init__(
+ self,
+ arg: Union[None, str, stim.Flow] = None,
+ /,
+ *,
+ input: Optional[stim.PauliString] = None,
+ output: Optional[stim.PauliString] = None,
+ measurements: Optional[Iterable[Union[int, GateTarget]]] = None,
+ ) -> None:
+ """Initializes a stim.Flow.
+
+ When given a string, the string is parsed as flow shorthand. For example,
+ the string "X_ -> ZZ xor rec[-1]" will result in a flow with input pauli string
+ "X_", output pauli string "ZZ", and measurement indices [-1].
+
+ Arguments:
+ arg [position-only]: Defaults to None. Must be specified by itself if used.
+ str: Initializes a flow by parsing the given shorthand text.
+ stim.Flow: Initializes a copy of the given flow.
+ None (default): Initializes an empty flow.
+ input: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's input stabilizer.
+ output: Defaults to None. Can be set to a stim.PauliString to directly
+ specify the flow's output stabilizer.
+ measurements: Can be set to a list of integers or gate targets like
+ `stim.target_rec(-1)`, to specify the measurements that mediate the
+ flow. Negative and positive measurement indices are allowed. Indexes
+ follow the python convention where -1 is the last measurement in a
+ circuit and 0 is the first measurement in a circuit.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Flow("X2 -> -Y2*Z4 xor rec[-1]")
+ stim.Flow("__X -> -__Y_Z xor rec[-1]")
+
+ >>> stim.Flow("Z -> 1 xor rec[-1]")
+ stim.Flow("Z -> rec[-1]")
+
+ >>> stim.Flow(
+ ... input=stim.PauliString("XX"),
+ ... output=stim.PauliString("_X"),
+ ... measurements=[],
+ ... )
+ stim.Flow("XX -> _X")
+ """
+ def __ne__(
+ self,
+ arg0: stim.Flow,
+ ) -> bool:
+ """Determines if two flows have non-identical contents.
+ """
+ def __repr__(
+ self,
+ ) -> str:
+ """Returns valid python code evaluating to an equivalent `stim.Flow`.
+ """
+ def __str__(
+ self,
+ ) -> str:
+ """Returns a shorthand description of the flow.
+ """
+ def input_copy(
+ self,
+ ) -> stim.PauliString:
+ """Returns a copy of the flow's input stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(input=stim.PauliString('XX'))
+ >>> f.input_copy()
+ stim.PauliString("+XX")
+
+ >>> f.input_copy() is f.input_copy()
+ False
+ """
+ def measurements_copy(
+ self,
+ ) -> List[int]:
+ """Returns a copy of the flow's measurement indices.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(measurements=[-1, 2])
+ >>> f.measurements_copy()
+ [-1, 2]
+
+ >>> f.measurements_copy() is f.measurements_copy()
+ False
+ """
+ def output_copy(
+ self,
+ ) -> stim.PauliString:
+ """Returns a copy of the flow's output stabilizer.
+
+ Examples:
+ >>> import stim
+ >>> f = stim.Flow(output=stim.PauliString('XX'))
+ >>> f.output_copy()
+ stim.PauliString("+XX")
+
+ >>> f.output_copy() is f.output_copy()
+ False
+ """
class GateData:
"""Details about a gate supported by stim.
@@ -5235,22 +5416,22 @@ class GateData:
>>> for e in stim.gate_data('H').__unstable_flows:
... print(e)
- +X -> +Z
- +Z -> +X
+ X -> Z
+ Z -> X
>>> for e in stim.gate_data('ISWAP').__unstable_flows:
... print(e)
- +X_ -> +ZY
- +Z_ -> +_Z
- +_X -> +YZ
- +_Z -> +Z_
+ X_ -> ZY
+ Z_ -> _Z
+ _X -> YZ
+ _Z -> Z_
>>> for e in stim.gate_data('MXX').__unstable_flows:
... print(e)
- +X_ -> +X_
- +_X -> +_X
- +ZZ -> +ZZ
- +XX -> rec[-1]
+ X_ -> X_
+ _X -> _X
+ ZZ -> ZZ
+ XX -> rec[-1]
"""
@property
def aliases(
diff --git a/src/stim.h b/src/stim.h
index c2777add7..3e6930bf4 100644
--- a/src/stim.h
+++ b/src/stim.h
@@ -9,7 +9,6 @@
#include "stim/circuit/export_qasm.h"
#include "stim/circuit/gate_decomposition.h"
#include "stim/circuit/gate_target.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/cmd/command_analyze_errors.h"
#include "stim/cmd/command_convert.h"
#include "stim/cmd/command_detect.h"
@@ -99,6 +98,7 @@
#include "stim/simulators/vector_simulator.h"
#include "stim/stabilizers/conversions.h"
#include "stim/stabilizers/flex_pauli_string.h"
+#include "stim/stabilizers/flow.h"
#include "stim/stabilizers/pauli_string.h"
#include "stim/stabilizers/pauli_string_iter.h"
#include "stim/stabilizers/pauli_string_ref.h"
diff --git a/src/stim/arg_parse.cc b/src/stim/arg_parse.cc
index 24e943da6..e751bc45f 100644
--- a/src/stim/arg_parse.cc
+++ b/src/stim/arg_parse.cc
@@ -389,8 +389,8 @@ ostream_else_cout stim::find_output_stream_argument(
return {std::move(f)};
}
-std::vector stim::split(char splitter, const std::string &text) {
- std::vector result;
+std::vector stim::split_view(char splitter, std::string_view text) {
+ std::vector result;
size_t start = 0;
for (size_t k = 0; k < text.size(); k++) {
if (text[k] == splitter) {
@@ -402,31 +402,69 @@ std::vector stim::split(char splitter, const std::string &text) {
return result;
}
-double stim::parse_exact_double_from_string(const std::string &text) {
+static double parse_exact_double_from_null_terminated(const char *c, size_t size) {
char *end = nullptr;
- const char *c = text.c_str();
double d = strtod(c, &end);
- if (text.size() > 0 && !isspace(*c)) {
- if (end == c + text.size() && !std::isinf(d) && !std::isnan(d)) {
+ if (size > 0 && !isspace(*c)) {
+ if (end == c + size && !std::isinf(d) && !std::isnan(d)) {
return d;
}
}
- throw std::invalid_argument("Not an exact double: '" + text + "'");
+ std::stringstream ss;
+ ss << "Not an exact finite double: '" << c << "'";
+ throw std::invalid_argument(ss.str());
}
-uint64_t stim::parse_exact_uint64_t_from_string(const std::string &text) {
- char *end = nullptr;
- const char *c = text.c_str();
- auto v = strtoull(c, &end, 10);
- if (end == c + text.size()) {
- // strtoull silently accepts spaces and negative signs and overflowing
- // values. The only guaranteed way I've found to ensure it actually
- // worked is to recreate the string and check that it's the same.
- std::stringstream ss;
- ss << v;
- if (ss.str() == text) {
- return v;
+double stim::parse_exact_double_from_string(std::string_view text) {
+ if (text.size() + 1 < 15) {
+ char buf[16];
+ memcpy(buf, text.data(), text.size());
+ buf[text.size()] = '\0';
+ return parse_exact_double_from_null_terminated(&buf[0], text.size());
+ } else {
+ std::string s(text);
+ return parse_exact_double_from_null_terminated(s.c_str(), text.size());
+ }
+}
+
+static bool try_parse_exact_uint64_t_from_string(std::string_view text, uint64_t *out) {
+ if (text.empty()) {
+ return false;
+ }
+ if (text[0] == '-') {
+ return false;
+ }
+ size_t k = 0;
+ if (text[k] == '+') {
+ k += 1;
+ }
+ uint64_t acc = 0;
+ while (k < text.size()) {
+ char c = text[k];
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ if (acc > UINT64_MAX / 10) {
+ return false;
+ }
+ acc *= 10;
+ uint8_t d = c - '0';
+ if (acc > UINT64_MAX - d) {
+ return false;
}
+ acc += d;
+ k++;
}
- throw std::invalid_argument("Not an integer that can be stored in a uint64_t: '" + text + "'");
+ *out = acc;
+ return true;
+}
+
+uint64_t stim::parse_exact_uint64_t_from_string(std::string_view text) {
+ uint64_t result = 0;
+ if (try_parse_exact_uint64_t_from_string(text, &result)) {
+ return result;
+ }
+ std::stringstream ss;
+ ss << "Not an exact integer that can be stored in a uint64_t: '" << text << "'";
+ throw std::invalid_argument(ss.str());
}
diff --git a/src/stim/arg_parse.h b/src/stim/arg_parse.h
index 68baf9d4d..b246340d6 100644
--- a/src/stim/arg_parse.h
+++ b/src/stim/arg_parse.h
@@ -260,10 +260,10 @@ struct ostream_else_cout {
/// Command line argument isn't present and default_std_out is false.
ostream_else_cout find_output_stream_argument(const char *name, bool default_std_out, int argc, const char **argv);
-std::vector split(char splitter, const std::string &text);
+std::vector split_view(char splitter, std::string_view text);
-double parse_exact_double_from_string(const std::string &text);
-uint64_t parse_exact_uint64_t_from_string(const std::string &text);
+double parse_exact_double_from_string(std::string_view text);
+uint64_t parse_exact_uint64_t_from_string(std::string_view text);
bool parse_int64(std::string_view data, int64_t *out);
} // namespace stim
diff --git a/src/stim/arg_parse.test.cc b/src/stim/arg_parse.test.cc
index 5d8a1b593..8e72b6682 100644
--- a/src/stim/arg_parse.test.cc
+++ b/src/stim/arg_parse.test.cc
@@ -295,14 +295,14 @@ TEST(arg_parse, find_open_file_argument) {
fclose(tmp);
}
-TEST(arg_parse, split) {
- ASSERT_EQ(split(',', ""), (std::vector{""}));
- ASSERT_EQ(split(',', "abc"), (std::vector{"abc"}));
- ASSERT_EQ(split(',', ","), (std::vector{"", ""}));
- ASSERT_EQ(split(',', "abc,"), (std::vector{"abc", ""}));
- ASSERT_EQ(split(',', ",abc"), (std::vector{"", "abc"}));
- ASSERT_EQ(split(',', "abc,def,ghi"), (std::vector{"abc", "def", "ghi"}));
- ASSERT_EQ(split(',', "abc,def,ghi,"), (std::vector{"abc", "def", "ghi", ""}));
+TEST(arg_parse, split_view) {
+ ASSERT_EQ(split_view(',', ""), (std::vector{""}));
+ ASSERT_EQ(split_view(',', "abc"), (std::vector{"abc"}));
+ ASSERT_EQ(split_view(',', ","), (std::vector{"", ""}));
+ ASSERT_EQ(split_view(',', "abc,"), (std::vector{"abc", ""}));
+ ASSERT_EQ(split_view(',', ",abc"), (std::vector{"", "abc"}));
+ ASSERT_EQ(split_view(',', "abc,def,ghi"), (std::vector{"abc", "def", "ghi"}));
+ ASSERT_EQ(split_view(',', "abc,def,ghi,"), (std::vector{"abc", "def", "ghi", ""}));
}
TEST(arg_parse, parse_exact_double_from_string) {
@@ -320,6 +320,7 @@ TEST(arg_parse, parse_exact_double_from_string) {
ASSERT_EQ(parse_exact_double_from_string("0"), 0);
ASSERT_EQ(parse_exact_double_from_string("1"), 1);
ASSERT_EQ(parse_exact_double_from_string("+1"), 1);
+ ASSERT_EQ(parse_exact_double_from_string("-1"), -1);
ASSERT_EQ(parse_exact_double_from_string("1e3"), 1000);
ASSERT_EQ(parse_exact_double_from_string("1.5"), 1.5);
ASSERT_EQ(parse_exact_double_from_string("-1.5"), -1.5);
@@ -343,7 +344,9 @@ TEST(arg_parse, parse_exact_uint64_t_from_string) {
ASSERT_EQ(parse_exact_uint64_t_from_string("0"), 0);
ASSERT_EQ(parse_exact_uint64_t_from_string("1"), 1);
+ ASSERT_EQ(parse_exact_uint64_t_from_string("+1"), 1);
ASSERT_EQ(parse_exact_uint64_t_from_string("2"), 2);
+ ASSERT_EQ(parse_exact_uint64_t_from_string("13"), 13);
ASSERT_EQ(parse_exact_uint64_t_from_string("18446744073709551615"), UINT64_MAX);
}
diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc
index 6ee717ed5..d0e0c6349 100644
--- a/src/stim/circuit/circuit.pybind.cc
+++ b/src/stim/circuit/circuit.pybind.cc
@@ -20,7 +20,6 @@
#include "stim/circuit/circuit_repeat_block.pybind.h"
#include "stim/circuit/export_qasm.h"
#include "stim/circuit/gate_target.pybind.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/cmd/command_diagram.pybind.h"
#include "stim/dem/detector_error_model_target.pybind.h"
#include "stim/diagram/detector_slice/detector_slice_set.h"
@@ -41,6 +40,7 @@
#include "stim/simulators/tableau_simulator.h"
#include "stim/simulators/transform_without_feedback.h"
#include "stim/stabilizers/conversions.h"
+#include "stim/stabilizers/flow.h"
#include "stim/stabilizers/pauli_string.pybind.h"
using namespace stim;
@@ -219,90 +219,6 @@ uint64_t obj_to_abs_detector_id(const pybind11::handle &obj, bool fail) {
throw std::invalid_argument(ss.str());
}
-FlexPauliString arg_to_pauli_string(const pybind11::object &arg) {
- if (arg.is_none()) {
- return FlexPauliString(PauliString(0));
- } else if (pybind11::isinstance(arg)) {
- return pybind11::cast(arg);
- } else if (pybind11::isinstance(arg)) {
- return FlexPauliString::from_text(pybind11::cast(arg).c_str());
- } else {
- throw std::invalid_argument(
- "Don't know how to get a stim.PauliString from " + pybind11::cast(pybind11::repr(arg)));
- }
-}
-
-void append_measurements_from_args(
- uint64_t num_circuit_measurements,
- const pybind11::object &arg_measurements,
- std::vector &out_measurements) {
- if (arg_measurements.is_none()) {
- return;
- }
- for (const pybind11::handle &e : arg_measurements) {
- if (pybind11::isinstance(e)) {
- auto d = pybind11::cast(e);
- if (d.is_measurement_record_target()) {
- out_measurements.push_back(d);
- continue;
- }
- } else {
- try {
- int64_t s = pybind11::cast(e);
- if (s >= 0 && s < (int64_t)num_circuit_measurements) {
- s -= num_circuit_measurements;
- }
- if (s < 0 && -s <= (int64_t)num_circuit_measurements) {
- out_measurements.push_back(GateTarget::rec(s));
- continue;
- }
- } catch (const pybind11::cast_error &) {
- }
- }
- throw std::invalid_argument(
- "Each measurement must be an integer in `range(-circuit.num_measurements, "
- "circuit.num_measurements)`, or a `stim.GateTarget`.");
- }
-}
-
-StabilizerFlow args_to_flow(
- uint64_t num_circuit_measurements,
- const pybind11::object &shorthand,
- const pybind11::object &start,
- const pybind11::object &end,
- const pybind11::object &measurements) {
- StabilizerFlow flow{
- .input = PauliString{0},
- .output = PauliString{0},
- .measurement_outputs = {},
- };
- if (!shorthand.is_none() && !start.is_none()) {
- throw std::invalid_argument("Can't specify both `shorthand` and `start`.");
- }
- if (!shorthand.is_none() && !end.is_none()) {
- throw std::invalid_argument("Can't specify both `shorthand` and `end`.");
- }
-
- if (!shorthand.is_none()) {
- flow = StabilizerFlow::from_str(
- pybind11::cast(shorthand).c_str(), num_circuit_measurements);
- } else {
- FlexPauliString in = arg_to_pauli_string(start);
- FlexPauliString out = arg_to_pauli_string(end);
- if (in.imag != out.imag) {
- throw std::invalid_argument(
- "The requested flow '" + in.str() + " -> " + out.str() +
- "' is anti-Hermitian (unbalanced imaginary signs). Stabilizer flows are always Hermitian.");
- }
- flow.input = std::move(in.value);
- flow.output = std::move(out.value);
- }
-
- append_measurements_from_args(num_circuit_measurements, measurements, flow.measurement_outputs);
-
- return flow;
-}
-
std::set obj_to_abs_detector_id_set(
const pybind11::object &obj, const std::function &get_num_detectors) {
std::set filter;
@@ -2304,30 +2220,21 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ bool {
- StabilizerFlow flow =
- args_to_flow(self.count_measurements(), shorthand, start, end, measurements);
+ [](const Circuit &self, const Flow &flow, bool unsigned_only) -> bool {
+ std::span> flows = {&flow, &flow + 1};
if (unsigned_only) {
- return check_if_circuit_has_unsigned_stabilizer_flows(self, &flow)[0];
+ return check_if_circuit_has_unsigned_stabilizer_flows(self, flows)[0];
} else {
auto rng = externally_seeded_rng();
- return sample_if_circuit_has_stabilizer_flows(256, rng, self, &flow)[0];
+ return sample_if_circuit_has_stabilizer_flows(256, rng, self, flows)[0];
}
},
- pybind11::arg("shorthand") = pybind11::none(),
+ pybind11::arg("flow"),
pybind11::kw_only(),
- pybind11::arg("start") = pybind11::none(),
- pybind11::arg("end") = pybind11::none(),
- pybind11::arg("measurements") = pybind11::none(),
pybind11::arg("unsigned") = false,
clean_doc_string(R"DOC(
- @signature def has_flow(self, shorthand: Optional[str] = None, *, start: Union[None, str, stim.PauliString] = None, end: Union[None, str, stim.PauliString] = None, measurements: Optional[Iterable[Union[int, stim.GateTarget]]] = None, unsigned: bool = False) -> bool:
- Determines if the circuit has a stabilizer flow or not.
+ @signature def has_flow(self, flow: stim.Flow, *, unsigned: bool = False) -> bool:
+ Determines if the circuit has the given stabilizer flow or not.
A circuit has a stabilizer flow P -> Q if it maps the instantaneous stabilizer
P at the start of the circuit to the instantaneous stabilizer Q at the end of
@@ -2341,26 +2248,7 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ 1 means the circuit contains a check (could be a DETECTOR).
Args:
- shorthand: Specifies the flow as a short string like "X1 -> -YZ xor rec[1]".
- The text must contain "->" to separate the input pauli string from the
- output pauli string. Measurements are included by appending
- " xor rec[k]" for each measurement index k. Indexing uses the python
- convention where non-negative indices index from the start and negative
- indices index from the end. The pauli strings are parsed as if by
- `stim.PauliString.__init__`.
- start: The input into the flow at the start of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- end: The output from the flow at the end of the circuit. Defaults to None
- (the identity Pauli string). When specified, this should be a
- `stim.PauliString`, or a `str` (which will be parsed using
- `stim.PauliString.__init__`).
- measurements: Defaults to None (empty). The indices of measurements to
- include in the flow. This should be a collection of integers and/or
- stim.GateTarget instances. Indexing uses the python convention where
- non-negative indices index from the start and negative indices index
- from the end.
+ flow: The flow to check for.
unsigned: Defaults to False. When False, the flows must be correct including
the sign of the Pauli strings. When True, only the Pauli terms need to
be correct; the signs are permitted to be inverted. In effect, this
@@ -2369,56 +2257,49 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> import stim
>>> m = stim.Circuit('M 0')
- >>> m.has_flow('Z -> Z')
+ >>> m.has_flow(stim.Flow('Z -> Z'))
True
- >>> m.has_flow('X -> X')
+ >>> m.has_flow(stim.Flow('X -> X'))
False
- >>> m.has_flow('Z -> I')
+ >>> m.has_flow(stim.Flow('Z -> I'))
False
- >>> m.has_flow('Z -> I xor rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> I xor rec[-1]'))
True
- >>> m.has_flow('Z -> rec[-1]')
+ >>> m.has_flow(stim.Flow('Z -> rec[-1]'))
True
>>> cx58 = stim.Circuit('CX 5 8')
- >>> cx58.has_flow('X5 -> X5*X8')
+ >>> cx58.has_flow(stim.Flow('X5 -> X5*X8'))
True
- >>> cx58.has_flow('X_ -> XX')
+ >>> cx58.has_flow(stim.Flow('X_ -> XX'))
False
- >>> cx58.has_flow('_____X___ -> _____X__X')
+ >>> cx58.has_flow(stim.Flow('_____X___ -> _____X__X'))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("Y"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("Y"),
+ ... ))
True
>>> stim.Circuit('''
... RY 0
- ... ''').has_flow(
- ... end=stim.PauliString("X"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... output=stim.PauliString("X"),
+ ... ))
False
>>> stim.Circuit('''
... CX 0 1
- ... ''').has_flow(
- ... start=stim.PauliString("+X_"),
- ... end=stim.PauliString("+XX"),
- ... )
+ ... ''').has_flow(stim.Flow(
+ ... input=stim.PauliString("+X_"),
+ ... output=stim.PauliString("+XX"),
+ ... ))
True
>>> stim.Circuit('''
@@ -2427,22 +2308,100 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> stim.Circuit('''
... H 0
... ''').has_flow(
- ... start=stim.PauliString("Y"),
- ... end=stim.PauliString("Y"),
+ ... stim.Flow("Y -> Y"),
... unsigned=True,
... )
True
+ >>> stim.Circuit('''
+ ... H 0
+ ... ''').has_flow(
+ ... stim.Flow("Y -> Y"),
+ ... unsigned=False,
+ ... )
+ False
+
+ Caveats:
+ Currently, the unsigned=False version of this method is implemented by
+ performing 256 randomized tests. Each test has a 50% chance of a false
+ positive, and a 0% chance of a false negative. So, when the method returns
+ True, there is technically still a 2^-256 chance the circuit doesn't have
+ the flow. This is lower than the chance of a cosmic ray flipping the result.
+ )DOC")
+ .data());
+
+ c.def(
+ "has_all_flows",
+ [](const Circuit &self, const std::vector> &flows, bool unsigned_only) -> bool {
+ std::vector results;
+ if (unsigned_only) {
+ results = check_if_circuit_has_unsigned_stabilizer_flows(self, flows);
+ } else {
+ auto rng = externally_seeded_rng();
+ results = sample_if_circuit_has_stabilizer_flows(256, rng, self, flows);
+ }
+ for (auto b : results) {
+ if (!b) {
+ return false;
+ }
+ }
+ return true;
+ },
+ pybind11::arg("flows"),
+ pybind11::kw_only(),
+ pybind11::arg("unsigned") = false,
+ clean_doc_string(R"DOC(
+ @signature def has_all_flows(self, flows: Iterable[stim.Flow], *, unsigned: bool = False) -> bool:
+ Determines if the circuit has all the given stabilizer flow or not.
+
+ This is a faster version of `all(c.has_flow(f) for f in flows)`. It's faster
+ because, behind the scenes, the circuit can be iterated once instead of once
+ per flow.
+
+ 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
+ the sign of the Pauli strings. When True, only the Pauli terms need to
+ be correct; the signs are permitted to be inverted. In effect, this
+ requires the circuit to be correct up to Pauli gates.
+
+ Returns:
+ True if the circuit has the given flow; False otherwise.
+
+ Examples:
+ >>> import stim
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ False
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> -Y'),
+ ... stim.Flow('Z -> X'),
+ ... ])
+ True
+
+ >>> stim.Circuit('H 0').has_all_flows([
+ ... stim.Flow('X -> Z'),
+ ... stim.Flow('Y -> Y'),
+ ... stim.Flow('Z -> X'),
+ ... ], unsigned=True)
+ True
+
Caveats:
Currently, the unsigned=False version of this method is implemented by
performing 256 randomized tests. Each test has a 50% chance of a false
diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py
index 31fe6510f..4dca08503 100644
--- a/src/stim/circuit/circuit_pybind_test.py
+++ b/src/stim/circuit/circuit_pybind_test.py
@@ -1579,12 +1579,12 @@ def test_has_flow_ry():
c = stim.Circuit("""
RY 0
""")
- assert c.has_flow(end=stim.PauliString("Y"))
- assert not c.has_flow(end=stim.PauliString("-Y"))
- assert not c.has_flow(end=stim.PauliString("X"))
- assert c.has_flow(end=stim.PauliString("Y"), unsigned=True)
- assert not c.has_flow(end=stim.PauliString("X"), unsigned=True)
- assert c.has_flow(end=stim.PauliString("-Y"), unsigned=True)
+ assert c.has_flow(stim.Flow("1 -> Y"))
+ assert not c.has_flow(stim.Flow("1 -> -Y"))
+ assert not c.has_flow(stim.Flow("1 -> X"))
+ assert c.has_flow(stim.Flow("1 -> Y"), unsigned=True)
+ assert not c.has_flow(stim.Flow("1 -> X"), unsigned=True)
+ assert c.has_flow(stim.Flow("1 -> -Y"), unsigned=True)
def test_has_flow_cxs():
@@ -1593,15 +1593,15 @@ def test_has_flow_cxs():
S 0
""")
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"))
- assert c.has_flow(start=stim.PauliString("Y_"), end=stim.PauliString("-XX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-XX"))
+ assert c.has_flow(stim.Flow("X_ -> YX"))
+ assert c.has_flow(stim.Flow("Y_ -> -XX"))
+ assert not c.has_flow(stim.Flow("X_ -> XX"))
+ assert not c.has_flow(stim.Flow("X_ -> -XX"))
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"), unsigned=True)
- assert c.has_flow(start=stim.PauliString("Y_"), end=stim.PauliString("-XX"), unsigned=True)
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-XX"), unsigned=True)
+ assert c.has_flow(stim.Flow("X_ -> YX"), unsigned=True)
+ assert c.has_flow(stim.Flow("Y_ -> -XX"), unsigned=True)
+ assert not c.has_flow(stim.Flow("X_ -> XX"), unsigned=True)
+ assert not c.has_flow(stim.Flow("X_ -> -XX"), unsigned=True)
def test_has_flow_cxm():
@@ -1609,14 +1609,14 @@ def test_has_flow_cxm():
CX 0 1
M 1
""")
- assert c.has_flow(end=stim.PauliString("_Z"), measurements=[0])
- assert c.has_flow(start=stim.PauliString("ZZ"), measurements=[0])
- assert c.has_flow(start=stim.PauliString("ZZ"), end=stim.PauliString("_Z"))
- assert c.has_flow(start=stim.PauliString("XX"), end=stim.PauliString("X_"))
- assert c.has_flow(end=stim.PauliString("_Z"), measurements=[0], unsigned=True)
- assert c.has_flow(start=stim.PauliString("ZZ"), measurements=[0], unsigned=True)
- assert c.has_flow(start=stim.PauliString("ZZ"), end=stim.PauliString("_Z"), unsigned=True)
- assert c.has_flow(start=stim.PauliString("XX"), end=stim.PauliString("X_"), unsigned=True)
+ assert c.has_flow(stim.Flow("1 -> _Z xor rec[0]"))
+ assert c.has_flow(stim.Flow("ZZ -> rec[0]"))
+ assert c.has_flow(stim.Flow("ZZ -> _Z"))
+ assert c.has_flow(stim.Flow("XX -> X_"))
+ assert c.has_flow(stim.Flow("1 -> _Z xor rec[0]"), unsigned=True)
+ assert c.has_flow(stim.Flow("ZZ -> rec[0]"), unsigned=True)
+ assert c.has_flow(stim.Flow("ZZ -> _Z"), unsigned=True)
+ assert c.has_flow(stim.Flow("XX -> X_"), unsigned=True)
def test_has_flow_lattice_surgery():
@@ -1631,16 +1631,16 @@ def test_has_flow_lattice_surgery():
S 0
""")
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"))
- assert c.has_flow(start=stim.PauliString("Z_"), end=stim.PauliString("Z_"))
- assert c.has_flow(start=stim.PauliString("_X"), end=stim.PauliString("_X"))
- assert c.has_flow(start=stim.PauliString("_Z"), end=stim.PauliString("ZZ"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
+ assert c.has_flow(stim.Flow("X_ -> YX"))
+ assert c.has_flow(stim.Flow("Z_ -> Z_"))
+ assert c.has_flow(stim.Flow("_X -> _X"))
+ assert c.has_flow(stim.Flow("_Z -> ZZ"))
+ assert not c.has_flow(stim.Flow("X_ -> XX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"), unsigned=True)
+ assert not c.has_flow(stim.Flow("X_ -> XX"))
+ assert not c.has_flow(stim.Flow("X_ -> -YX"))
+ assert not c.has_flow(stim.Flow("X_ -> XX"), unsigned=True)
+ assert c.has_flow(stim.Flow("X_ -> -YX"), unsigned=True)
def test_has_flow_lattice_surgery_without_feedback():
@@ -1653,16 +1653,26 @@ def test_has_flow_lattice_surgery_without_feedback():
S 0
""")
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("YX"), measurements=[1])
- assert c.has_flow(start=stim.PauliString("Z_"), end=stim.PauliString("Z_"))
- assert c.has_flow(start=stim.PauliString("_X"), end=stim.PauliString("_X"))
- assert c.has_flow(start=stim.PauliString("_Z"), end=stim.PauliString("ZZ"), measurements=[0, 2])
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
-
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"))
- assert not c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("XX"), unsigned=True)
- assert c.has_flow(start=stim.PauliString("X_"), end=stim.PauliString("-YX"), unsigned=True, measurements=[1])
+ assert c.has_flow(stim.Flow("X_ -> YX xor rec[1]"))
+ assert c.has_flow(stim.Flow("Z_ -> Z_"))
+ assert c.has_flow(stim.Flow("_X -> _X"))
+ assert c.has_flow(stim.Flow("_Z -> ZZ xor rec[0] xor rec[2]"))
+ assert not c.has_flow(stim.Flow("X_ -> XX"))
+ assert c.has_all_flows([])
+ assert c.has_all_flows([
+ stim.Flow("X_ -> YX xor rec[1]"),
+ stim.Flow("Z_ -> Z_"),
+ ])
+ assert not c.has_all_flows([
+ stim.Flow("X_ -> YX xor rec[1]"),
+ stim.Flow("Z_ -> Z_"),
+ stim.Flow("X_ -> XX"),
+ ])
+
+ assert not c.has_flow(stim.Flow("X_ -> XX"))
+ assert not c.has_flow(stim.Flow("X_ -> -YX"))
+ assert not c.has_flow(stim.Flow("X_ -> XX"), unsigned=True)
+ assert c.has_flow(stim.Flow("X_ -> -YX xor rec[1]"), unsigned=True)
def test_has_flow_shorthands():
@@ -1673,18 +1683,17 @@ def test_has_flow_shorthands():
MX 99
""")
- assert c.has_flow("X_ -> XX xor rec[1] xor rec[3]")
- assert c.has_flow("Z_ -> Z_")
- assert c.has_flow("_X -> _X")
- assert c.has_flow("_Z -> ZZ", measurements=[0, 2])
- assert c.has_flow("_Z -> ZZ xor rec[0]", measurements=[2])
-
- assert c.has_flow(start="X_", end="XX", measurements=[1, 3])
- assert not c.has_flow("Z_ -> -Z_")
- assert not c.has_flow("-Z_ -> Z_")
- assert not c.has_flow("Z_ -> X_")
- assert c.has_flow("iX_ -> iXX xor rec[1] xor rec[3]")
- assert not c.has_flow("-iX_ -> iXX xor rec[1] xor rec[3]")
- assert c.has_flow("-iX_ -> -iXX xor rec[1] xor rec[3]")
+ assert c.has_flow(stim.Flow("X_ -> XX xor rec[1] xor rec[3]"))
+ assert c.has_flow(stim.Flow("Z_ -> Z_"))
+ assert c.has_flow(stim.Flow("_X -> _X"))
+ assert c.has_flow(stim.Flow("_Z -> ZZ xor rec[0] xor rec[2]"))
+
+ assert c.has_flow(stim.Flow("X_ -> XX xor rec[1] xor rec[3]"))
+ assert not c.has_flow(stim.Flow("Z_ -> -Z_"))
+ assert not c.has_flow(stim.Flow("-Z_ -> Z_"))
+ assert not c.has_flow(stim.Flow("Z_ -> X_"))
+ assert c.has_flow(stim.Flow("iX_ -> iXX xor rec[1] xor rec[3]"))
+ assert not c.has_flow(stim.Flow("-iX_ -> iXX xor rec[1] xor rec[3]"))
+ assert c.has_flow(stim.Flow("-iX_ -> -iXX xor rec[1] xor rec[3]"))
with pytest.raises(ValueError):
- c.has_flow("iX_ -> XX")
+ stim.Flow("iX_ -> XX")
diff --git a/src/stim/circuit/stabilizer_flow.test.cc b/src/stim/circuit/stabilizer_flow.test.cc
deleted file mode 100644
index e11511e84..000000000
--- a/src/stim/circuit/stabilizer_flow.test.cc
+++ /dev/null
@@ -1,205 +0,0 @@
-// Copyright 2021 Google LLC
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include "stim/circuit/stabilizer_flow.h"
-
-#include "gtest/gtest.h"
-
-#include "stim/circuit/circuit.h"
-#include "stim/mem/simd_word.test.h"
-#include "stim/test_util.test.h"
-
-using namespace stim;
-
-TEST_EACH_WORD_SIZE_W(stabilizer_flow, from_str, {
- ASSERT_THROW({ StabilizerFlow::from_str(""); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X>X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X-X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X > X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X - X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("->X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X->"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("rec[0] -> X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X -> rec[ -1]"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X -> X rec[-1]"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X -> X xor"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X -> rec[-1] xor X"); }, std::invalid_argument);
- ASSERT_THROW({ StabilizerFlow::from_str("X -> rec[55]"); }, std::invalid_argument);
-
- ASSERT_EQ(
- StabilizerFlow::from_str("1 -> 1"),
- (StabilizerFlow{
- .input = PauliString::from_str(""),
- .output = PauliString::from_str(""),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("i -> -i"),
- (StabilizerFlow{
- .input = PauliString::from_str(""),
- .output = PauliString::from_str("-"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("iX -> -iY"),
- (StabilizerFlow{
- .input = PauliString::from_str("X"),
- .output = PauliString::from_str("-Y"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("X->-Y"),
- (StabilizerFlow{
- .input = PauliString::from_str("X"),
- .output = PauliString::from_str("-Y"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("X -> -Y"),
- (StabilizerFlow{
- .input = PauliString::from_str("X"),
- .output = PauliString::from_str("-Y"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("-X -> Y"),
- (StabilizerFlow{
- .input = PauliString::from_str("-X"),
- .output = PauliString::from_str("Y"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("XYZ -> -Z_Z"),
- (StabilizerFlow{
- .input = PauliString::from_str("XYZ"),
- .output = PauliString::from_str("-Z_Z"),
- .measurement_outputs = {},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("XYZ -> Z_Y xor rec[-1]"),
- (StabilizerFlow{
- .input = PauliString::from_str("XYZ"),
- .output = PauliString::from_str("Z_Y"),
- .measurement_outputs = {GateTarget::rec(-1)},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("XYZ -> rec[-1]"),
- (StabilizerFlow{
- .input = PauliString::from_str("XYZ"),
- .output = PauliString::from_str(""),
- .measurement_outputs = {GateTarget::rec(-1)},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("XYZ -> Z_Y xor rec[-1] xor rec[-3]"),
- (StabilizerFlow{
- .input = PauliString::from_str("XYZ"),
- .output = PauliString::from_str("Z_Y"),
- .measurement_outputs = {GateTarget::rec(-1), GateTarget::rec(-3)},
- }));
- ASSERT_EQ(
- StabilizerFlow::from_str("XYZ -> ZIY xor rec[55] xor rec[-3]", 100),
- (StabilizerFlow{
- .input = PauliString::from_str("XYZ"),
- .output = PauliString::from_str("Z_Y"),
- .measurement_outputs = {GateTarget::rec(-45), GateTarget::rec(-3)},
- }));
-});
-
-TEST_EACH_WORD_SIZE_W(stabilizer_flow, sample_if_circuit_has_stabilizer_flows, {
- auto rng = INDEPENDENT_TEST_RNG();
- auto results = sample_if_circuit_has_stabilizer_flows(
- 256,
- rng,
- Circuit(R"CIRCUIT(
- R 4
- CX 0 4 1 4 2 4 3 4
- M 4
- )CIRCUIT"),
- std::vector>{
- StabilizerFlow::from_str("Z___ -> Z____"),
- StabilizerFlow::from_str("_Z__ -> _Z__"),
- StabilizerFlow::from_str("__Z_ -> __Z_"),
- StabilizerFlow::from_str("___Z -> ___Z"),
- StabilizerFlow::from_str("XX__ -> XX__"),
- StabilizerFlow::from_str("XXXX -> XXXX"),
- StabilizerFlow::from_str("XYZ_ -> XYZ_"),
- StabilizerFlow::from_str("XXX_ -> XXX_"),
- StabilizerFlow::from_str("ZZZZ -> ____ xor rec[-1]"),
- StabilizerFlow::from_str("+___Z -> -___Z"),
- StabilizerFlow::from_str("-___Z -> -___Z"),
- StabilizerFlow::from_str("-___Z -> +___Z"),
- });
- ASSERT_EQ(results, (std::vector{1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0}));
-})
-
-TEST_EACH_WORD_SIZE_W(stabilizer_flow, str_and_from_str, {
- auto flow = StabilizerFlow{
- PauliString::from_str("XY"),
- PauliString::from_str("_Z"),
- {GateTarget::rec(-3)},
- };
- auto s = "+XY -> +_Z xor rec[-3]";
- ASSERT_EQ(flow.str(), s);
- ASSERT_EQ(StabilizerFlow::from_str(s), flow);
-
- ASSERT_EQ(
- StabilizerFlow::from_str("1 -> rec[-1]"),
- (StabilizerFlow{PauliString(0), PauliString(0), {GateTarget::rec(-1)}}));
-
- ASSERT_EQ(
- StabilizerFlow::from_str("-1 -> -X xor rec[-1] xor rec[-3]"),
- (StabilizerFlow{
- PauliString::from_str("-"),
- PauliString::from_str("-X"),
- {GateTarget::rec(-1), GateTarget::rec(-3)}}));
-})
-
-TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_unsigned_stabilizer_flows, {
- auto results = check_if_circuit_has_unsigned_stabilizer_flows(
- Circuit(R"CIRCUIT(
- R 4
- CX 0 4 1 4 2 4 3 4
- M 4
- )CIRCUIT"),
- std::vector>{
- StabilizerFlow::from_str("Z___ -> Z____"),
- StabilizerFlow::from_str("_Z__ -> _Z__"),
- StabilizerFlow::from_str("__Z_ -> __Z_"),
- StabilizerFlow::from_str("___Z -> ___Z"),
- StabilizerFlow::from_str("XX__ -> XX__"),
- StabilizerFlow::from_str("XXXX -> XXXX"),
- StabilizerFlow::from_str("XYZ_ -> XYZ_"),
- StabilizerFlow::from_str("XXX_ -> XXX_"),
- StabilizerFlow::from_str("ZZZZ -> ____ xor rec[-1]"),
- StabilizerFlow::from_str("+___Z -> -___Z"),
- StabilizerFlow::from_str("-___Z -> -___Z"),
- StabilizerFlow::from_str("-___Z -> +___Z"),
- });
- ASSERT_EQ(results, (std::vector{1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1}));
-});
-
-TEST_EACH_WORD_SIZE_W(stabilizer_flow, check_if_circuit_has_unsigned_stabilizer_flows_historical_failure, {
- auto results = check_if_circuit_has_unsigned_stabilizer_flows(
- Circuit(R"CIRCUIT(
- CX 0 1
- S 0
- )CIRCUIT"),
- std::vector>{
- StabilizerFlow::from_str("X_ -> YX"),
- StabilizerFlow::from_str("Y_ -> XX"),
- StabilizerFlow::from_str("X_ -> XX"),
- });
- ASSERT_EQ(results, (std::vector{1, 1, 0}));
-})
diff --git a/src/stim/cmd/command_diagram.cc b/src/stim/cmd/command_diagram.cc
index bdfe80fcc..04eadf028 100644
--- a/src/stim/cmd/command_diagram.cc
+++ b/src/stim/cmd/command_diagram.cc
@@ -94,7 +94,7 @@ std::vector _read_coord_filter(int argc, const char **argv) {
}
std::vector result;
- for (const auto &term : split(':', arg)) {
+ for (std::string_view term : split_view(':', arg)) {
result.push_back(CoordFilter::parse_from(term));
}
return result;
diff --git a/src/stim/cmd/command_help.cc b/src/stim/cmd/command_help.cc
index 909bb6965..0bbee7027 100644
--- a/src/stim/cmd/command_help.cc
+++ b/src/stim/cmd/command_help.cc
@@ -31,10 +31,10 @@
#include "command_sample.h"
#include "command_sample_dem.h"
#include "stim/arg_parse.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/cmd/command_analyze_errors.h"
#include "stim/gates/gates.h"
#include "stim/io/stim_data_formats.h"
+#include "stim/stabilizers/flow.h"
#include "stim/stabilizers/tableau.h"
using namespace stim;
diff --git a/src/stim/diagram/detector_slice/detector_slice_set.cc b/src/stim/diagram/detector_slice/detector_slice_set.cc
index e600531b6..100b16cd8 100644
--- a/src/stim/diagram/detector_slice/detector_slice_set.cc
+++ b/src/stim/diagram/detector_slice/detector_slice_set.cc
@@ -192,7 +192,7 @@ bool CoordFilter::matches(stim::SpanRef coords, stim::DemTarget ta
}
return true;
}
-CoordFilter CoordFilter::parse_from(const std::string &data) {
+CoordFilter CoordFilter::parse_from(std::string_view data) {
CoordFilter filter;
if (data.empty()) {
// no filter
@@ -203,7 +203,7 @@ CoordFilter CoordFilter::parse_from(const std::string &data) {
filter.use_target = true;
filter.exact_target = DemTarget::observable_id(parse_exact_double_from_string(data.substr(1)));
} else {
- for (const auto &v : split(',', data)) {
+ for (const auto &v : split_view(',', data)) {
if (v == "*") {
filter.coordinates.push_back(std::numeric_limits::quiet_NaN());
} else {
diff --git a/src/stim/diagram/detector_slice/detector_slice_set.h b/src/stim/diagram/detector_slice/detector_slice_set.h
index 41f3a85f0..82056a23f 100644
--- a/src/stim/diagram/detector_slice/detector_slice_set.h
+++ b/src/stim/diagram/detector_slice/detector_slice_set.h
@@ -32,7 +32,7 @@ struct CoordFilter {
stim::DemTarget exact_target{};
bool matches(stim::SpanRef coords, stim::DemTarget target) const;
- static CoordFilter parse_from(const std::string &data);
+ static CoordFilter parse_from(std::string_view data);
};
struct DetectorSliceSet {
diff --git a/src/stim/gates/gates.h b/src/stim/gates/gates.h
index f6f9c577f..0edd527a9 100644
--- a/src/stim/gates/gates.h
+++ b/src/stim/gates/gates.h
@@ -32,7 +32,7 @@ template
struct Tableau;
template
-struct StabilizerFlow;
+struct Flow;
template
struct PauliString;
@@ -255,25 +255,25 @@ struct Gate {
}
template
- std::vector> flows() const {
+ std::vector> flows() const {
if (flags & GateFlags::GATE_IS_UNITARY) {
auto t = tableau();
if (flags & GateFlags::GATE_TARGETS_PAIRS) {
return {
- StabilizerFlow{stim::PauliString::from_str("X_"), t.xs[0], {}},
- StabilizerFlow{stim::PauliString::from_str("Z_"), t.zs[0], {}},
- StabilizerFlow{stim::PauliString::from_str("_X"), t.xs[1], {}},
- StabilizerFlow{stim::PauliString::from_str("_Z"), t.zs[1], {}},
+ Flow{stim::PauliString::from_str("X_"), t.xs[0], {}},
+ Flow{stim::PauliString::from_str("Z_"), t.zs[0], {}},
+ Flow{stim::PauliString::from_str("_X"), t.xs[1], {}},
+ Flow{stim::PauliString::from_str("_Z"), t.zs[1], {}},
};
}
return {
- StabilizerFlow{stim::PauliString::from_str("X"), t.xs[0], {}},
- StabilizerFlow{stim::PauliString::from_str("Z"), t.zs[0], {}},
+ Flow{stim::PauliString::from_str("X"), t.xs[0], {}},
+ Flow{stim::PauliString::from_str("Z"), t.zs[0], {}},
};
}
- std::vector> out;
+ std::vector> out;
for (const auto &c : flow_data) {
- out.push_back(StabilizerFlow::from_str(c));
+ out.push_back(Flow::from_str(c));
}
return out;
}
diff --git a/src/stim/gates/gates.pybind.cc b/src/stim/gates/gates.pybind.cc
index a70b60de7..583938461 100644
--- a/src/stim/gates/gates.pybind.cc
+++ b/src/stim/gates/gates.pybind.cc
@@ -14,9 +14,9 @@
#include "stim/gates/gates.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/gates/gates.pybind.h"
#include "stim/py/base.pybind.h"
+#include "stim/stabilizers/flow.h"
#include "stim/str_util.h"
using namespace stim;
@@ -478,22 +478,22 @@ void stim_pybind::pybind_gate_data_methods(pybind11::module &m, pybind11::class_
>>> for e in stim.gate_data('H').__unstable_flows:
... print(e)
- +X -> +Z
- +Z -> +X
+ X -> Z
+ Z -> X
>>> for e in stim.gate_data('ISWAP').__unstable_flows:
... print(e)
- +X_ -> +ZY
- +Z_ -> +_Z
- +_X -> +YZ
- +_Z -> +Z_
+ X_ -> ZY
+ Z_ -> _Z
+ _X -> YZ
+ _Z -> Z_
>>> for e in stim.gate_data('MXX').__unstable_flows:
... print(e)
- +X_ -> +X_
- +_X -> +_X
- +ZZ -> +ZZ
- +XX -> rec[-1]
+ X_ -> X_
+ _X -> _X
+ ZZ -> ZZ
+ XX -> rec[-1]
)DOC")
.data());
diff --git a/src/stim/gates/gates.test.cc b/src/stim/gates/gates.test.cc
index c2879d2f6..19adc114d 100644
--- a/src/stim/gates/gates.test.cc
+++ b/src/stim/gates/gates.test.cc
@@ -17,9 +17,9 @@
#include "gtest/gtest.h"
#include "stim/circuit/circuit.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/mem/simd_word.test.h"
#include "stim/simulators/tableau_simulator.h"
+#include "stim/stabilizers/flow.h"
#include "stim/test_util.test.h"
using namespace stim;
diff --git a/src/stim/py/stim.pybind.cc b/src/stim/py/stim.pybind.cc
index df3c8f067..9f1e05a2f 100644
--- a/src/stim/py/stim.pybind.cc
+++ b/src/stim/py/stim.pybind.cc
@@ -36,6 +36,7 @@
#include "stim/simulators/matched_error.pybind.h"
#include "stim/simulators/measurements_to_detection_events.pybind.h"
#include "stim/simulators/tableau_simulator.pybind.h"
+#include "stim/stabilizers/flow.pybind.h"
#include "stim/stabilizers/pauli_string.pybind.h"
#include "stim/stabilizers/pauli_string_iter.pybind.h"
#include "stim/stabilizers/tableau.h"
@@ -124,7 +125,6 @@ std::vector target_combined_paulis(const pybind11::object &paulis, b
result.push_back(GateTarget::combiner());
continue;
}
-
}
std::stringstream ss;
@@ -610,6 +610,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) {
auto c_circuit_targets_inside_instruction = pybind_circuit_targets_inside_instruction(m);
auto c_circuit_error_location = pybind_circuit_error_location(m);
auto c_circuit_error_location_methods = pybind_explained_error(m);
+ auto c_flow = pybind_flow(m);
auto c_diagram_helper = pybind_diagram(m);
@@ -650,6 +651,7 @@ PYBIND11_MODULE(STIM_PYBIND11_MODULE_NAME, m) {
pybind_circuit_targets_inside_instruction_methods(m, c_circuit_targets_inside_instruction);
pybind_circuit_error_location_methods(m, c_circuit_error_location);
pybind_explained_error_methods(m, c_circuit_error_location_methods);
+ pybind_flow_methods(m, c_flow);
pybind_diagram_methods(m, c_diagram_helper);
}
diff --git a/src/stim/stabilizers/flex_pauli_string.cc b/src/stim/stabilizers/flex_pauli_string.cc
index 82a758457..a98733c5a 100644
--- a/src/stim/stabilizers/flex_pauli_string.cc
+++ b/src/stim/stabilizers/flex_pauli_string.cc
@@ -227,9 +227,12 @@ static void parse_sparse_pauli_string(std::string_view text, FlexPauliString *ou
if (cur_pauli == '\0' || !has_cur_index || cur_index > out->value.num_qubits) {
throw std::invalid_argument("");
}
- out->value.right_mul_pauli(
- GateTarget::pauli_xz(cur_index, cur_pauli == 'X' || cur_pauli == 'Y', cur_pauli == 'Z' || cur_pauli == 'Y'),
- &out->imag);
+ if (cur_pauli != 'I') {
+ out->value.right_mul_pauli(
+ GateTarget::pauli_xz(
+ cur_index, cur_pauli == 'X' || cur_pauli == 'Y', cur_pauli == 'Z' || cur_pauli == 'Y'),
+ &out->imag);
+ }
has_cur_index = false;
cur_pauli = '\0';
cur_index = 0;
diff --git a/src/stim/stabilizers/flex_pauli_string.test.cc b/src/stim/stabilizers/flex_pauli_string.test.cc
index 6e7b5f600..7a14ccced 100644
--- a/src/stim/stabilizers/flex_pauli_string.test.cc
+++ b/src/stim/stabilizers/flex_pauli_string.test.cc
@@ -75,4 +75,6 @@ TEST(flex_pauli_string, from_text) {
ASSERT_EQ(f.value.zs.as_u64(), 0b100000100);
ASSERT_EQ(FlexPauliString::from_text("X1"), FlexPauliString::from_text("_X"));
+
+ ASSERT_EQ(FlexPauliString::from_text("X20*I21"), FlexPauliString::from_text("____________________X_"));
}
diff --git a/src/stim/circuit/stabilizer_flow.h b/src/stim/stabilizers/flow.h
similarity index 73%
rename from src/stim/circuit/stabilizer_flow.h
rename to src/stim/stabilizers/flow.h
index d3d53027b..b5046d203 100644
--- a/src/stim/circuit/stabilizer_flow.h
+++ b/src/stim/stabilizers/flow.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef _STIM_CIRCUIT_STABILIZER_FLOW_H
-#define _STIM_CIRCUIT_STABILIZER_FLOW_H
+#ifndef _STIM_STABILIZERS_FLOW_H
+#define _STIM_STABILIZERS_FLOW_H
#include
#include
@@ -26,14 +26,15 @@
namespace stim {
template
-struct StabilizerFlow {
+struct Flow {
stim::PauliString input;
stim::PauliString output;
- std::vector measurement_outputs;
+ /// Indexing follows python convention: -1 is the last element, 0 is the first element.
+ std::vector measurements;
- static StabilizerFlow from_str(const char *text, uint64_t num_measurements_for_non_neg_recs = 0);
- bool operator==(const StabilizerFlow &other) const;
- bool operator!=(const StabilizerFlow &other) const;
+ static Flow from_str(std::string_view text);
+ bool operator==(const Flow &other) const;
+ bool operator!=(const Flow &other) const;
std::string str() const;
};
@@ -51,17 +52,17 @@ struct StabilizerFlow {
/// k'th flow passed all checks.
template
std::vector sample_if_circuit_has_stabilizer_flows(
- size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, SpanRef> flows);
+ size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, std::span> flows);
template
std::vector check_if_circuit_has_unsigned_stabilizer_flows(
- const Circuit &circuit, SpanRef> flows);
+ const Circuit &circuit, std::span> flows);
template
-std::ostream &operator<<(std::ostream &out, const StabilizerFlow &flow);
+std::ostream &operator<<(std::ostream &out, const Flow &flow);
} // namespace stim
-#include "stim/circuit/stabilizer_flow.inl"
+#include "stim/stabilizers/flow.inl"
#endif
diff --git a/src/stim/circuit/stabilizer_flow.inl b/src/stim/stabilizers/flow.inl
similarity index 61%
rename from src/stim/circuit/stabilizer_flow.inl
rename to src/stim/stabilizers/flow.inl
index 74c81d93b..2ebfccfe1 100644
--- a/src/stim/circuit/stabilizer_flow.inl
+++ b/src/stim/stabilizers/flow.inl
@@ -1,10 +1,10 @@
#include "stim/arg_parse.h"
#include "stim/circuit/circuit.h"
-#include "stim/circuit/stabilizer_flow.h"
#include "stim/simulators/frame_simulator_util.h"
#include "stim/simulators/sparse_rev_frame_tracker.h"
#include "stim/simulators/tableau_simulator.h"
#include "stim/stabilizers/flex_pauli_string.h"
+#include "stim/stabilizers/flow.h"
namespace stim {
@@ -25,32 +25,51 @@ void _pauli_string_controlled_not(PauliStringRef control, uint32_t target, Ci
}
}
+template
+static GateTarget measurement_index_to_target(int32_t m, uint64_t num_measurements, const Flow &flow) {
+ if ((m >= 0 && (uint64_t)m >= num_measurements) || (m < 0 && (uint64_t) - (int64_t)m > num_measurements)) {
+ std::stringstream ss;
+ ss << "The flow '" << flow;
+ ss << "' is malformed for the given circuit. ";
+ ss << "The flow mentions a measurement index '" << m;
+ ss << "', but this index out of range because the circuit only has ";
+ ss << num_measurements << " measurements.";
+ throw std::invalid_argument(ss.str());
+ }
+ if (m >= 0) {
+ m -= num_measurements;
+ }
+ return GateTarget::rec(m);
+}
+
template
bool _sample_if_circuit_has_stabilizer_flow(
- size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, const StabilizerFlow &flow) {
- uint32_t n = (uint32_t)circuit.count_qubits();
- n = std::max(n, (uint32_t)flow.input.num_qubits);
- n = std::max(n, (uint32_t)flow.output.num_qubits);
+ size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, const Flow &flow) {
+ uint32_t num_qubits = (uint32_t)circuit.count_qubits();
+ uint64_t num_measurements = circuit.count_measurements();
+
+ num_qubits = std::max(num_qubits, (uint32_t)flow.input.num_qubits);
+ num_qubits = std::max(num_qubits, (uint32_t)flow.output.num_qubits);
Circuit augmented_circuit;
- for (uint32_t k = 0; k < n; k++) {
- augmented_circuit.safe_append_u("XCX", {k, k + n + 1}, {});
+ for (uint32_t k = 0; k < num_qubits; k++) {
+ augmented_circuit.safe_append_u("XCX", {k, k + num_qubits + 1}, {});
}
- for (uint32_t k = 0; k < n; k++) {
+ for (uint32_t k = 0; k < num_qubits; k++) {
augmented_circuit.safe_append_u("DEPOLARIZE1", {k}, {0.75});
}
augmented_circuit.append_from_text("TICK");
- _pauli_string_controlled_not(flow.input, n, augmented_circuit);
+ _pauli_string_controlled_not(flow.input, num_qubits, augmented_circuit);
augmented_circuit.append_from_text("TICK");
augmented_circuit += circuit;
augmented_circuit.append_from_text("TICK");
- _pauli_string_controlled_not(flow.output, n, augmented_circuit);
- for (const auto &m : flow.measurement_outputs) {
- assert(m.is_measurement_record_target());
- std::vector targets{m, GateTarget::qubit(n)};
+ _pauli_string_controlled_not(flow.output, num_qubits, augmented_circuit);
+ for (int32_t m : flow.measurements) {
+ std::array targets{
+ measurement_index_to_target(m, num_measurements, flow), GateTarget::qubit(num_qubits)};
augmented_circuit.safe_append(GateType::CX, targets, {});
}
- augmented_circuit.safe_append_u("M", {n}, {});
+ augmented_circuit.safe_append_u("M", {num_qubits}, {});
auto out = sample_batch_measurements(
augmented_circuit, TableauSimulator::reference_sample_circuit(augmented_circuit), num_samples, rng, false);
@@ -61,7 +80,7 @@ bool _sample_if_circuit_has_stabilizer_flow(
template
std::vector sample_if_circuit_has_stabilizer_flows(
- size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, SpanRef> flows) {
+ size_t num_samples, std::mt19937_64 &rng, const Circuit &circuit, std::span> flows) {
std::vector result;
for (const auto &flow : flows) {
result.push_back(_sample_if_circuit_has_stabilizer_flow(num_samples, rng, circuit, flow));
@@ -69,8 +88,7 @@ std::vector sample_if_circuit_has_stabilizer_flows(
return result;
}
-inline bool parse_rec_allowing_non_negative(
- std::string_view rec, size_t num_measurements_for_non_neg, GateTarget *out) {
+inline bool parse_rec_allowing_non_negative(std::string_view rec, int32_t *out) {
if (rec.size() < 6 || rec[0] != 'r' || rec[1] != 'e' || rec[2] != 'c' || rec[3] != '[' || rec.back() != ']') {
throw std::invalid_argument(""); // Caught and given a message below.
}
@@ -79,12 +97,8 @@ inline bool parse_rec_allowing_non_negative(
return false;
}
- if (i >= INT32_MIN && i < 0) {
- *out = stim::GateTarget::rec((int32_t)i);
- return true;
- }
- if (i >= 0 && (size_t)i < num_measurements_for_non_neg) {
- *out = stim::GateTarget::rec((int32_t)i - (int32_t)num_measurements_for_non_neg);
+ if (i >= INT32_MIN && i <= INT32_MAX) {
+ *out = (int32_t)i;
return true;
}
return false;
@@ -117,21 +131,21 @@ PauliString parse_non_empty_pauli_string_allowing_i(std::string_view text, bo
}
template
-StabilizerFlow StabilizerFlow::from_str(const char *text, uint64_t num_measurements_for_non_neg_recs) {
+Flow Flow::from_str(std::string_view text) {
try {
- auto parts = split('>', text);
+ auto parts = split_view('>', text);
if (parts.size() != 2 || parts[0].empty() || parts[0].back() != '-') {
throw std::invalid_argument(""); // Caught and given a message below.
}
- parts[0].pop_back();
+ parts[0] = parts[0].substr(0, parts[0].size() - 1);
while (!parts[0].empty() && parts[0].back() == ' ') {
- parts[0].pop_back();
+ parts[0] = parts[0].substr(0, parts[0].size() - 1);
}
bool imag_inp = false;
bool imag_out = false;
PauliString inp = parse_non_empty_pauli_string_allowing_i(parts[0], &imag_inp);
- parts = split(' ', parts[1]);
+ parts = split_view(' ', parts[1]);
size_t k = 0;
while (k < parts.size() && parts[k].empty()) {
k += 1;
@@ -140,89 +154,124 @@ StabilizerFlow