diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d62a3408a..86dbda126 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -322,6 +322,7 @@ jobs:
- run: diff <(python -c "import stim; stim.main(command_line_args=['help', 'gates_markdown'])") doc/gates.md
- run: diff <(python -c "import stim; stim.main(command_line_args=['help', 'formats_markdown'])") doc/result_formats.md
- run: diff <(python -c "import stim; stim.main(command_line_args=['help', 'commands_markdown'])") doc/usage_command_line.md
+ - run: diff <(dev/gen_known_gates_for_js.sh) glue/crumble/test/generated_gate_name_list.test.js
- run: python doc/stim.pyi
- run: npm install -g rollup@3.21.2 uglify-js@3.17.4
- run: diff <(dev/regen_crumble_to_cpp_string_write_to_stdout.sh) src/stim/diagram/crumble_data.cc
diff --git a/dev/gen_known_gates_for_js.sh b/dev/gen_known_gates_for_js.sh
new file mode 100755
index 000000000..90b0b5503
--- /dev/null
+++ b/dev/gen_known_gates_for_js.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+set -e
+
+#########################################################################
+# Generates javascript exporting a string KNOWN_GATE_NAMES_FROM_STIM.
+#########################################################################
+
+echo "const KNOWN_GATE_NAMES_FROM_STIM = \`"
+python -c "import stim; stim.main(command_line_args=['help', 'gates'])" | grep " " | sed 's/^ *//g'
+echo "\`"
+echo
+echo "export {KNOWN_GATE_NAMES_FROM_STIM};"
diff --git a/dev/regen_docs.sh b/dev/regen_docs.sh
index 5cb916235..3f2266ead 100755
--- a/dev/regen_docs.sh
+++ b/dev/regen_docs.sh
@@ -16,3 +16,4 @@ python dev/gen_sinter_api_reference.py -dev > doc/sinter_api.md
python -c "import stim; stim.main(command_line_args=['help', 'gates_markdown'])" > doc/gates.md
python -c "import stim; stim.main(command_line_args=['help', 'formats_markdown'])" > doc/result_formats.md
python -c "import stim; stim.main(command_line_args=['help', 'commands_markdown'])" > doc/usage_command_line.md
+dev/gen_known_gates_for_js.sh > glue/crumble/test/generated_gate_name_list.test.js
diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md
index a0db0e71b..373736162 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__)
@@ -1888,6 +1898,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
@@ -1895,14 +1965,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
@@ -1916,26 +1983,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
@@ -1944,56 +1992,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('''
@@ -2002,22 +2043,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
@@ -6751,6 +6799,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 8368e028a..aa2c065b5 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 16b5dca8e..3b0f6ea4d 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
@@ -73,6 +72,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/crumble/circuit/circuit.js b/glue/crumble/circuit/circuit.js
index 61d1f0f87..1e389dd96 100644
--- a/glue/crumble/circuit/circuit.js
+++ b/glue/crumble/circuit/circuit.js
@@ -1,7 +1,8 @@
import {Operation} from "./operation.js"
-import {GATE_MAP} from "../gates/gateset.js"
+import {GATE_ALIAS_MAP, GATE_MAP} from "../gates/gateset.js"
import {Layer} from "./layer.js"
import {make_mpp_gate} from '../gates/gateset_mpp.js';
+import {describe} from "../base/describe.js";
/**
* @param {!Iterator}items
@@ -92,6 +93,9 @@ function simplifiedMPP(args, combinedTargets) {
let bases = '';
let qubits = [];
for (let t of combinedTargets) {
+ if (t[0] === '!') {
+ t = t.substring(1);
+ }
if (t[0] === 'X' || t[0] === 'Y' || t[0] === 'Z') {
bases += t[0];
let v = parseInt(t.substring(1));
@@ -216,32 +220,17 @@ class Circuit {
let reverse_pairs = false;
if (name === '') {
return;
- } else if (name === 'XCZ') {
- reverse_pairs = true;
- name = 'CX';
- } else if (name === 'SWAPCX') {
- reverse_pairs = true;
- name = 'CXSWAP';
- } else if (name === 'CNOT') {
- name = 'CX';
- } else if (name === 'RZ') {
- name = 'R';
- } else if (name === 'MZ') {
- name = 'M';
- } else if (name === 'MRZ') {
- name = 'MR';
- } else if (name === 'ZCX') {
- name = 'CX';
- } else if (name === 'ZCY') {
- name = 'CY';
- } else if (name === 'ZCZ') {
- name = 'CZ';
- } else if (name === 'YCX') {
- reverse_pairs = true;
- name = 'XCY';
- } else if (name === 'YCZ') {
- reverse_pairs = true;
- name = 'CY';
+ }
+ let alias = GATE_ALIAS_MAP.get(name);
+ if (alias !== undefined) {
+ if (alias.ignore) {
+ return;
+ } else if (alias.name !== undefined) {
+ reverse_pairs = alias.reverse_pairs !== undefined && alias.reverse_pairs;
+ name = alias.name;
+ } else {
+ throw new Error(`Unimplemented alias ${name}: ${describe(alias)}.`);
+ }
} else if (name === 'TICK') {
layers.push(new Layer());
return;
@@ -258,17 +247,6 @@ class Circuit {
}
}
return;
- } else if (name === "X_ERROR" ||
- name === "Y_ERROR" ||
- name === "Z_ERROR" ||
- name === "DETECTOR" ||
- name === "OBSERVABLE_INCLUDE" ||
- name === "DEPOLARIZE1" ||
- name === "DEPOLARIZE2" ||
- name === "SHIFT_COORDS" ||
- name === "REPEAT" ||
- name === "}") {
- return;
} else if (name.startsWith('QUBIT_COORDS')) {
let x = args.length < 1 ? 0 : args[0];
let y = args.length < 2 ? 0 : args[1];
@@ -294,7 +272,6 @@ class Circuit {
break;
}
}
- let t = parseInt(targ);
if (typeof parseInt(targ) !== 'number') {
throw new Error(line);
}
diff --git a/glue/crumble/circuit/circuit.test.js b/glue/crumble/circuit/circuit.test.js
index 744758313..0ffb71eb0 100644
--- a/glue/crumble/circuit/circuit.test.js
+++ b/glue/crumble/circuit/circuit.test.js
@@ -531,3 +531,120 @@ QUBIT_COORDS(6, 0) 6
MPP Z0*Z1*Z2 Z3*Z4*Z5*X6
`.trim())
});
+
+test("circuit.fromStimCircuit_manygates", () => {
+ let c = Circuit.fromStimCircuit(`
+ QUBIT_COORDS(1, 2, 3) 0
+
+ # Pauli gates
+ I 0
+ X 1
+ Y 2
+ Z 3
+ TICK
+
+ # Single Qubit Clifford Gates
+ C_XYZ 0
+ C_ZYX 1
+ H_XY 2
+ H_XZ 3
+ H_YZ 4
+ SQRT_X 0
+ SQRT_X_DAG 1
+ SQRT_Y 2
+ SQRT_Y_DAG 3
+ SQRT_Z 4
+ SQRT_Z_DAG 5
+ TICK
+
+ # Two Qubit Clifford Gates
+ CXSWAP 0 1
+ ISWAP 2 3
+ ISWAP_DAG 4 5
+ SWAP 6 7
+ SWAPCX 8 9
+ CZSWAP 10 11
+ SQRT_XX 0 1
+ SQRT_XX_DAG 2 3
+ SQRT_YY 4 5
+ SQRT_YY_DAG 6 7
+ SQRT_ZZ 8 9
+ SQRT_ZZ_DAG 10 11
+ XCX 0 1
+ XCY 2 3
+ XCZ 4 5
+ YCX 6 7
+ YCY 8 9
+ YCZ 10 11
+ ZCX 12 13
+ ZCY 14 15
+ ZCZ 16 17
+ TICK
+
+ # Noise Channels
+ CORRELATED_ERROR(0.01) X1 Y2 Z3
+ ELSE_CORRELATED_ERROR(0.02) X4 Y7 Z6
+ DEPOLARIZE1(0.02) 0
+ DEPOLARIZE2(0.03) 1 2
+ PAULI_CHANNEL_1(0.01, 0.02, 0.03) 3
+ PAULI_CHANNEL_2(0.001, 0.002, 0.003, 0.004, 0.005, 0.006, 0.007, 0.008, 0.009, 0.010, 0.011, 0.012, 0.013, 0.014, 0.015) 4 5
+ X_ERROR(0.01) 0
+ Y_ERROR(0.02) 1
+ Z_ERROR(0.03) 2
+ HERALDED_ERASE(0.04) 3
+ HERALDED_PAULI_CHANNEL_1(0.01, 0.02, 0.03, 0.04) 6
+ TICK
+
+ # Collapsing Gates
+ MPP X0*Y1*Z2 Z0*Z1
+ MRX 0
+ MRY 1
+ MRZ 2
+ MX 3
+ MY 4
+ MZ 5 6
+ RX 7
+ RY 8
+ RZ 9
+ TICK
+
+ # Pair Measurement Gates
+ MXX 0 1 2 3
+ MYY 4 5
+ MZZ 6 7
+ TICK
+
+ # Control Flow
+ REPEAT 3 {
+ H 0
+ CX 0 1
+ S 1
+ TICK
+ }
+ TICK
+
+ # Annotations
+ MR 0
+ X_ERROR(0.1) 0
+ MR(0.01) 0
+ SHIFT_COORDS(1, 2, 3)
+ DETECTOR(1, 2, 3) rec[-1]
+ OBSERVABLE_INCLUDE(0) rec[-1]
+ MPAD 0 1 0
+ TICK
+
+ # Inverted measurements.
+ MRX !0
+ MY !1
+ MZZ !2 3
+ MYY !4 !5
+ MPP X6*!Y7*Z8
+ TICK
+
+ # Feedback
+ CX rec[-1] 0
+ CY sweep[0] 1
+ CZ 2 rec[-1]
+ `);
+ assertThat(c).isNotEqualTo(undefined);
+})
diff --git a/glue/crumble/circuit/pauli_frame.js b/glue/crumble/circuit/pauli_frame.js
index 4c3bda6ff..ce5b4a5a6 100644
--- a/glue/crumble/circuit/pauli_frame.js
+++ b/glue/crumble/circuit/pauli_frame.js
@@ -465,6 +465,24 @@ class PauliFrame {
}
}
+ /**
+ * @param {!Array} targets
+ */
+ do_cz_swap(targets) {
+ for (let k = 0; k < targets.length; k += 2) {
+ let c = k;
+ let t = k + 1;
+ let xc = this.xs[c];
+ let zc = this.zs[c];
+ let xt = this.xs[t];
+ let zt = this.zs[t];
+ this.xs[c] = xt;
+ this.zs[c] = zt ^ xc;
+ this.xs[t] = xc;
+ this.zs[t] = zc ^ xt;
+ }
+ }
+
/**
* @param {!Array} targets
*/
diff --git a/glue/crumble/crumble.html b/glue/crumble/crumble.html
index 43ed9f8f0..5dfda322b 100644
--- a/glue/crumble/crumble.html
+++ b/glue/crumble/crumble.html
@@ -7,20 +7,31 @@