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 @@
- Crumble is a prototype stabilizer circuit editor.
- Read the manual
- Load example: surface code (d=3,r=2)
- Load example: bacon shor code (d=7,r=2)
- Load example: three coupler surface code (d=7,r=4)
+
+
+ Crumble is a prototype stabilizer circuit editor.
+ Read the manual
+
+ Load example: surface code (d=3,r=2)
+ Load example: bacon shor code (d=7,r=2)
+ Load example: three coupler surface code (d=7,r=4)
+ Load example: surface code Y basis transition round (d=7)
+
+
+ + +
+
- +
+
-
- +
+
@@ -41,12 +52,14 @@
+
+
+
+ +
+
-
- - -