From e353e8f276863e3157dc8a6b4381c70eac6814f2 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Wed, 20 Mar 2024 22:11:27 -0700 Subject: [PATCH] Add `stim.Circuit.to_{crumble,quirk}_url` and `-svg-html` diagrams (#726) - These are variants of `-svg` diagrams, that force an HTML iframe in notebook viewers - This is useful in collab notebooks because it gives a resizable tab - Also it makes right-click + "open image in new tab" work - Also it makes right-click + "save image" work - Adds `stim.Circuit.to_crumble_url` - Adds `stim.Circuit.to_quirk_url` - Move qasm-export from `stim/circuit/` to `stim/util_top/` Fixes https://github.com/quantumlib/Stim/issues/649 --- doc/python_api_reference_vDev.md | 182 ++++---- doc/stim.pyi | 166 +++----- file_lists/source_files_no_main | 4 +- file_lists/test_files | 4 +- glue/crumble/circuit/circuit.js | 3 +- glue/crumble/circuit/circuit.test.js | 11 +- glue/python/src/stim/__init__.pyi | 166 +++----- src/stim.h | 4 +- src/stim/circuit/circuit.pybind.cc | 83 +++- src/stim/circuit/circuit_pybind_test.py | 4 + src/stim/circuit/gate_decomposition.cc | 22 + src/stim/circuit/gate_decomposition.h | 4 + src/stim/circuit/gate_decomposition.test.cc | 36 ++ src/stim/cmd/command_diagram.pybind.cc | 171 +++++--- src/stim/cmd/command_diagram.pybind.h | 5 +- src/stim/dem/detector_error_model.pybind.cc | 9 +- .../dem/detector_error_model_pybind_test.py | 1 + src/stim/diagram/crumble_data.cc | 42 +- src/stim/util_top/export_crumble_url.cc | 34 ++ src/stim/util_top/export_crumble_url.h | 12 + src/stim/util_top/export_crumble_url.test.cc | 108 +++++ .../export_crumble_url_pybind_test.py | 22 + src/stim/{circuit => util_top}/export_qasm.cc | 2 +- src/stim/{circuit => util_top}/export_qasm.h | 0 .../{circuit => util_top}/export_qasm.test.cc | 2 +- .../export_qasm_pybind_test.py | 0 src/stim/util_top/export_quirk_url.cc | 396 ++++++++++++++++++ src/stim/util_top/export_quirk_url.h | 12 + src/stim/util_top/export_quirk_url.test.cc | 165 ++++++++ .../util_top/export_quirk_url_pybind_test.py | 22 + 30 files changed, 1283 insertions(+), 409 deletions(-) create mode 100644 src/stim/util_top/export_crumble_url.cc create mode 100644 src/stim/util_top/export_crumble_url.h create mode 100644 src/stim/util_top/export_crumble_url.test.cc create mode 100644 src/stim/util_top/export_crumble_url_pybind_test.py rename src/stim/{circuit => util_top}/export_qasm.cc (99%) rename src/stim/{circuit => util_top}/export_qasm.h (100%) rename src/stim/{circuit => util_top}/export_qasm.test.cc (99%) rename src/stim/{circuit => util_top}/export_qasm_pybind_test.py (100%) create mode 100644 src/stim/util_top/export_quirk_url.cc create mode 100644 src/stim/util_top/export_quirk_url.h create mode 100644 src/stim/util_top/export_quirk_url.test.cc create mode 100644 src/stim/util_top/export_quirk_url_pybind_test.py diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index cf385f2b7..904f4b8d1 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -52,8 +52,10 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.Circuit.shortest_error_sat_problem`](#stim.Circuit.shortest_error_sat_problem) - [`stim.Circuit.shortest_graphlike_error`](#stim.Circuit.shortest_graphlike_error) - [`stim.Circuit.time_reversed_for_flows`](#stim.Circuit.time_reversed_for_flows) + - [`stim.Circuit.to_crumble_url`](#stim.Circuit.to_crumble_url) - [`stim.Circuit.to_file`](#stim.Circuit.to_file) - [`stim.Circuit.to_qasm`](#stim.Circuit.to_qasm) + - [`stim.Circuit.to_quirk_url`](#stim.Circuit.to_quirk_url) - [`stim.Circuit.to_tableau`](#stim.Circuit.to_tableau) - [`stim.Circuit.with_inlined_feedback`](#stim.Circuit.with_inlined_feedback) - [`stim.Circuit.without_noise`](#stim.Circuit.without_noise) @@ -1592,86 +1594,6 @@ def detector_error_model( # stim.Circuit.diagram # (in class stim.Circuit) -@overload -def diagram( - self, - type: 'Literal["timeline-text"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["timeline-svg"]', - *, - tick: Union[None, int, range] = None, -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["timeline-3d", "timeline-3d-html"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["matchgraph-svg"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["matchgraph-3d"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["detslice-text"]', - *, - tick: int, - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["detslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["detslice-with-ops-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["timeslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["interactive", "interactive-html"]', -) -> 'stim._DiagramHelper': - pass def diagram( self, type: str = 'timeline-text', @@ -1692,6 +1614,11 @@ def diagram( the circuit over time. Includes annotations showing the measurement record index that each measurement writes to, and the measurements used by detectors. + "timeline-svg-html": A resizable SVG image viewer of the + operations applied by the circuit over time. Includes + annotations showing the measurement record index that + each measurement writes to, and the measurements used + by detectors. "timeline-3d": A 3d model, in GLTF format, of the operations applied by the circuit over time. "timeline-3d-html": Same 3d model as 'timeline-3d' but @@ -1710,8 +1637,12 @@ def diagram( usual diagram of a surface code. Uses the Pauli color convention XYZ=RGB. + "detslice-svg-html": Same as detslice-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-svg": An SVG image of the match graph extracted from the circuit by stim.Circuit.detector_error_model. + "matchgraph-svg-html": Same as matchgraph-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-3d": An 3D model of the match graph extracted from the circuit by stim.Circuit.detector_error_model. "matchgraph-3d-html": Same 3d model as 'match-graph-3d' but @@ -1720,10 +1651,14 @@ def diagram( "timeslice-svg": An SVG image of the operations applied between two TICK instructions in the circuit, with the operations laid out in 2d. + "timeslice-svg-html": Same as timeslice-svg but the SVG image + is inside a resizable HTML iframe. "detslice-with-ops-svg": A combination of timeslice-svg and detslice-svg, with the operations overlaid over the detector slices taken from the TICK after the operations were applied. + "detslice-with-ops-svg-html": Same as detslice-with-ops-svg + but the SVG image is inside a resizable HTML iframe. "interactive" or "interactive-html": An HTML web page containing Crumble (an interactive editor for 2D stabilizer circuits) initialized with the given circuit @@ -3098,6 +3033,36 @@ def time_reversed_for_flows( """ ``` + +```python +# stim.Circuit.to_crumble_url + +# (in class stim.Circuit) +def to_crumble_url( + self, +) -> str: + """Returns a URL that opens up crumble and loads this circuit into it. + + Crumble is a tool for editing stabilizer circuits, and visualizing their + stabilizer flows. Its source code is in the `glue/crumble` directory of + the stim code repository on github. A prebuilt version is made available + at https://algassert.com/crumble, which is what the URL returned by this + method will point to. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_crumble_url() + 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + """ +``` + ```python # stim.Circuit.to_file @@ -3206,6 +3171,39 @@ def to_qasm( """ ``` + +```python +# stim.Circuit.to_quirk_url + +# (in class stim.Circuit) +def to_quirk_url( + self, +) -> str: + """Returns a URL that opens up quirk and loads this circuit into it. + + Quirk is an open source drag and drop circuit editor with support for up to 16 + qubits. Its source code is available at https://github.com/strilanc/quirk + and a prebuilt version is available at https://algassert.com/quirk, which is + what the URL returned by this method will point to. + + Quirk doesn't support features like noise, feedback, or detectors. This method + will simply drop any unsupported operations from the circuit when producing + the URL. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_quirk_url() + 'https://algassert.com/quirk#circuit={"cols":[["H"],["•","X"],[1,"Z^½"]]}' + """ +``` + ```python # stim.Circuit.to_tableau @@ -6125,24 +6123,6 @@ def copy( # stim.DetectorErrorModel.diagram # (in class stim.DetectorErrorModel) -@overload -def diagram( - self, - type: 'Literal["matchgraph-svg"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["matchgraph-3d"]', -) -> 'stim._DiagramHelper': - pass -@overload -def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', -) -> 'stim._DiagramHelper': - pass def diagram( self, type: str, @@ -6155,6 +6135,8 @@ def diagram( detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper errors. + "matchgraph-svg-html": Same as matchgraph-svg but with the + SVG wrapped in a resizable HTML iframe. "matchgraph-3d": A 3d model of the decoding graph of the detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper @@ -6186,12 +6168,12 @@ def diagram( >>> dem = circuit.detector_error_model(decompose_errors=True) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-svg") + ... diagram = circuit.diagram("match-graph-svg") ... with open(f"{d}/dem_image.svg", "w") as f: ... print(diagram, file=f) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-3d") + ... diagram = circuit.diagram("match-graph-3d") ... with open(f"{d}/dem_3d_model.gltf", "w") as f: ... print(diagram, file=f) """ diff --git a/doc/stim.pyi b/doc/stim.pyi index e8d757335..aeb6ec670 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -1004,86 +1004,6 @@ class Circuit: error(0.25) D1 ''') """ - @overload - def diagram( - self, - type: 'Literal["timeline-text"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeline-svg"]', - *, - tick: Union[None, int, range] = None, - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeline-3d", "timeline-3d-html"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-svg"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-text"]', - *, - tick: int, - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-with-ops-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["interactive", "interactive-html"]', - ) -> 'stim._DiagramHelper': - pass def diagram( self, type: str = 'timeline-text', @@ -1104,6 +1024,11 @@ class Circuit: the circuit over time. Includes annotations showing the measurement record index that each measurement writes to, and the measurements used by detectors. + "timeline-svg-html": A resizable SVG image viewer of the + operations applied by the circuit over time. Includes + annotations showing the measurement record index that + each measurement writes to, and the measurements used + by detectors. "timeline-3d": A 3d model, in GLTF format, of the operations applied by the circuit over time. "timeline-3d-html": Same 3d model as 'timeline-3d' but @@ -1122,8 +1047,12 @@ class Circuit: usual diagram of a surface code. Uses the Pauli color convention XYZ=RGB. + "detslice-svg-html": Same as detslice-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-svg": An SVG image of the match graph extracted from the circuit by stim.Circuit.detector_error_model. + "matchgraph-svg-html": Same as matchgraph-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-3d": An 3D model of the match graph extracted from the circuit by stim.Circuit.detector_error_model. "matchgraph-3d-html": Same 3d model as 'match-graph-3d' but @@ -1132,10 +1061,14 @@ class Circuit: "timeslice-svg": An SVG image of the operations applied between two TICK instructions in the circuit, with the operations laid out in 2d. + "timeslice-svg-html": Same as timeslice-svg but the SVG image + is inside a resizable HTML iframe. "detslice-with-ops-svg": A combination of timeslice-svg and detslice-svg, with the operations overlaid over the detector slices taken from the TICK after the operations were applied. + "detslice-with-ops-svg-html": Same as detslice-with-ops-svg + but the SVG image is inside a resizable HTML iframe. "interactive" or "interactive-html": An HTML web page containing Crumble (an interactive editor for 2D stabilizer circuits) initialized with the given circuit @@ -2386,6 +2319,29 @@ class Circuit: OBSERVABLE_INCLUDE(0) rec[-3] rec[-1] ''') """ + def to_crumble_url( + self, + ) -> str: + """Returns a URL that opens up crumble and loads this circuit into it. + + Crumble is a tool for editing stabilizer circuits, and visualizing their + stabilizer flows. Its source code is in the `glue/crumble` directory of + the stim code repository on github. A prebuilt version is made available + at https://algassert.com/crumble, which is what the URL returned by this + method will point to. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_crumble_url() + 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + """ def to_file( self, file: Union[io.TextIOBase, str, pathlib.Path], @@ -2480,6 +2436,32 @@ class Circuit: measure q[1] -> rec[1]; dets[0] = rec[1] ^ rec[0] ^ 1; """ + def to_quirk_url( + self, + ) -> str: + """Returns a URL that opens up quirk and loads this circuit into it. + + Quirk is an open source drag and drop circuit editor with support for up to 16 + qubits. Its source code is available at https://github.com/strilanc/quirk + and a prebuilt version is available at https://algassert.com/quirk, which is + what the URL returned by this method will point to. + + Quirk doesn't support features like noise, feedback, or detectors. This method + will simply drop any unsupported operations from the circuit when producing + the URL. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_quirk_url() + 'https://algassert.com/quirk#circuit={"cols":[["H"],["•","X"],[1,"Z^½"]]}' + """ def to_tableau( self, *, @@ -4694,24 +4676,6 @@ class DetectorErrorModel: >>> c2 == c1 True """ - @overload - def diagram( - self, - type: 'Literal["matchgraph-svg"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', - ) -> 'stim._DiagramHelper': - pass def diagram( self, type: str, @@ -4724,6 +4688,8 @@ class DetectorErrorModel: detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper errors. + "matchgraph-svg-html": Same as matchgraph-svg but with the + SVG wrapped in a resizable HTML iframe. "matchgraph-3d": A 3d model of the decoding graph of the detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper @@ -4755,12 +4721,12 @@ class DetectorErrorModel: >>> dem = circuit.detector_error_model(decompose_errors=True) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-svg") + ... diagram = circuit.diagram("match-graph-svg") ... with open(f"{d}/dem_image.svg", "w") as f: ... print(diagram, file=f) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-3d") + ... diagram = circuit.diagram("match-graph-3d") ... with open(f"{d}/dem_3d_model.gltf", "w") as f: ... print(diagram, file=f) """ diff --git a/file_lists/source_files_no_main b/file_lists/source_files_no_main index f62d0c4f3..2c508d733 100644 --- a/file_lists/source_files_no_main +++ b/file_lists/source_files_no_main @@ -2,7 +2,6 @@ src/stim.cc src/stim/arg_parse.cc src/stim/circuit/circuit.cc src/stim/circuit/circuit_instruction.cc -src/stim/circuit/export_qasm.cc src/stim/circuit/gate_decomposition.cc src/stim/circuit/gate_target.cc src/stim/cmd/command_analyze_errors.cc @@ -92,4 +91,7 @@ src/stim/util_bot/error_decomp.cc src/stim/util_top/circuit_inverse_unitary.cc src/stim/util_top/circuit_to_detecting_regions.cc src/stim/util_top/circuit_vs_amplitudes.cc +src/stim/util_top/export_crumble_url.cc +src/stim/util_top/export_qasm.cc +src/stim/util_top/export_quirk_url.cc src/stim/util_top/simplified_circuit.cc diff --git a/file_lists/test_files b/file_lists/test_files index 4bce470a3..5c986278b 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -1,7 +1,6 @@ src/stim.test.cc src/stim/arg_parse.test.cc 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/cmd/command_analyze_errors.test.cc @@ -87,6 +86,9 @@ src/stim/util_top/circuit_inverse_unitary.test.cc src/stim/util_top/circuit_to_detecting_regions.test.cc src/stim/util_top/circuit_vs_amplitudes.test.cc src/stim/util_top/circuit_vs_tableau.test.cc +src/stim/util_top/export_crumble_url.test.cc +src/stim/util_top/export_qasm.test.cc +src/stim/util_top/export_quirk_url.test.cc src/stim/util_top/simplified_circuit.test.cc src/stim/util_top/stabilizers_to_tableau.test.cc src/stim/util_top/stabilizers_vs_amplitudes.test.cc diff --git a/glue/crumble/circuit/circuit.js b/glue/crumble/circuit/circuit.js index bf27156c5..8fde43d03 100644 --- a/glue/crumble/circuit/circuit.js +++ b/glue/crumble/circuit/circuit.js @@ -183,6 +183,7 @@ class Circuit { replaceAll(' ERROR', '_ERROR'). replaceAll('C XYZ', 'C_XYZ'). replaceAll('H XY', 'H_XY'). + replaceAll('H XZ', 'H_XZ'). replaceAll('H YZ', 'H_YZ'). replaceAll(' INCLUDE', '_INCLUDE'). replaceAll('SQRT ', 'SQRT_'). @@ -578,7 +579,7 @@ class Circuit { if (gate === undefined && (gateName === 'MPP' || gateName === 'SPP' || gateName === 'SPP_DAG')) { let line = [gateName + ' ']; for (let op of group) { - let bases = op.gate.name.substring(4); + let bases = op.gate.name.substring(gateName.length + 1); for (let k = 0; k < op.id_targets.length; k++) { line.push(bases[k] + old2new.get(op.id_targets[k])); line.push('*'); diff --git a/glue/crumble/circuit/circuit.test.js b/glue/crumble/circuit/circuit.test.js index ad2d82bc4..9757f26d5 100644 --- a/glue/crumble/circuit/circuit.test.js +++ b/glue/crumble/circuit/circuit.test.js @@ -595,8 +595,13 @@ test("circuit.fromStimCircuit_manygates", () => { HERALDED_PAULI_CHANNEL_1(0.01, 0.02, 0.03, 0.04) 6 TICK - # Collapsing Gates + # Pauli Product Gates MPP X0*Y1*Z2 Z0*Z1 + SPP X0*Y1*Z2 X3 + SPP_DAG X0*Y1*Z2 X2 + TICK + + # Collapsing Gates MRX 0 MRY 1 MRZ 2 @@ -647,6 +652,10 @@ test("circuit.fromStimCircuit_manygates", () => { CZ 2 rec[-1] `); assertThat(c).isNotEqualTo(undefined); + + let c2 = Circuit.fromStimCircuit(`Q(1,2,3)0;I_0;X_1;Y_2;Z_3;TICK;C_XYZ_0;C_ZYX_1;H_XY_2;H_3;H_YZ_4;SQRT_X_0;SQRT_X_DAG_1;SQRT_Y_2;SQRT_Y_DAG_3;S_4;S_DAG_5;TICK;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;CX_12_13;CY_14_15;CZ_16_17;TICK;E(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.01,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;MPP_X0*Y1*Z2_Z0*Z1;SPP_X0*Y1*Z2_X3;SPP_DAG_X0*Y1*Z2_X2;TICK;MRX_0;MRY_1;MR_2;MX_3;MY_4;M_5_6;RX_7;RY_8;R_9;TICK;MXX_0_1_2_3;MYY_4_5;MZZ_6_7;TICK;REPEAT_3_{;H_0;CX_0_1;S_1;TICK;};TICK;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;MRX_!0;MY_!1;MZZ_!2_3;MYY_!4_!5;MPP_X6*!Y7*Z8;TICK;CX_rec[-1]_0;CY_sweep[0]_1;CZ_2_rec[-1]`); + assertThat(c2).isNotEqualTo(undefined); + assertThat(c).isEqualTo(c2); }); test("circuit.fromStimCircuit_cx_ordering", () => { diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index e8d757335..aeb6ec670 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -1004,86 +1004,6 @@ class Circuit: error(0.25) D1 ''') """ - @overload - def diagram( - self, - type: 'Literal["timeline-text"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeline-svg"]', - *, - tick: Union[None, int, range] = None, - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeline-3d", "timeline-3d-html"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-svg"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-text"]', - *, - tick: int, - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["detslice-with-ops-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["timeslice-svg"]', - *, - tick: Union[int, range], - filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),), - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["interactive", "interactive-html"]', - ) -> 'stim._DiagramHelper': - pass def diagram( self, type: str = 'timeline-text', @@ -1104,6 +1024,11 @@ class Circuit: the circuit over time. Includes annotations showing the measurement record index that each measurement writes to, and the measurements used by detectors. + "timeline-svg-html": A resizable SVG image viewer of the + operations applied by the circuit over time. Includes + annotations showing the measurement record index that + each measurement writes to, and the measurements used + by detectors. "timeline-3d": A 3d model, in GLTF format, of the operations applied by the circuit over time. "timeline-3d-html": Same 3d model as 'timeline-3d' but @@ -1122,8 +1047,12 @@ class Circuit: usual diagram of a surface code. Uses the Pauli color convention XYZ=RGB. + "detslice-svg-html": Same as detslice-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-svg": An SVG image of the match graph extracted from the circuit by stim.Circuit.detector_error_model. + "matchgraph-svg-html": Same as matchgraph-svg but the SVG image + is inside a resizable HTML iframe. "matchgraph-3d": An 3D model of the match graph extracted from the circuit by stim.Circuit.detector_error_model. "matchgraph-3d-html": Same 3d model as 'match-graph-3d' but @@ -1132,10 +1061,14 @@ class Circuit: "timeslice-svg": An SVG image of the operations applied between two TICK instructions in the circuit, with the operations laid out in 2d. + "timeslice-svg-html": Same as timeslice-svg but the SVG image + is inside a resizable HTML iframe. "detslice-with-ops-svg": A combination of timeslice-svg and detslice-svg, with the operations overlaid over the detector slices taken from the TICK after the operations were applied. + "detslice-with-ops-svg-html": Same as detslice-with-ops-svg + but the SVG image is inside a resizable HTML iframe. "interactive" or "interactive-html": An HTML web page containing Crumble (an interactive editor for 2D stabilizer circuits) initialized with the given circuit @@ -2386,6 +2319,29 @@ class Circuit: OBSERVABLE_INCLUDE(0) rec[-3] rec[-1] ''') """ + def to_crumble_url( + self, + ) -> str: + """Returns a URL that opens up crumble and loads this circuit into it. + + Crumble is a tool for editing stabilizer circuits, and visualizing their + stabilizer flows. Its source code is in the `glue/crumble` directory of + the stim code repository on github. A prebuilt version is made available + at https://algassert.com/crumble, which is what the URL returned by this + method will point to. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_crumble_url() + 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + """ def to_file( self, file: Union[io.TextIOBase, str, pathlib.Path], @@ -2480,6 +2436,32 @@ class Circuit: measure q[1] -> rec[1]; dets[0] = rec[1] ^ rec[0] ^ 1; """ + def to_quirk_url( + self, + ) -> str: + """Returns a URL that opens up quirk and loads this circuit into it. + + Quirk is an open source drag and drop circuit editor with support for up to 16 + qubits. Its source code is available at https://github.com/strilanc/quirk + and a prebuilt version is available at https://algassert.com/quirk, which is + what the URL returned by this method will point to. + + Quirk doesn't support features like noise, feedback, or detectors. This method + will simply drop any unsupported operations from the circuit when producing + the URL. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_quirk_url() + 'https://algassert.com/quirk#circuit={"cols":[["H"],["•","X"],[1,"Z^½"]]}' + """ def to_tableau( self, *, @@ -4694,24 +4676,6 @@ class DetectorErrorModel: >>> c2 == c1 True """ - @overload - def diagram( - self, - type: 'Literal["matchgraph-svg"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d"]', - ) -> 'stim._DiagramHelper': - pass - @overload - def diagram( - self, - type: 'Literal["matchgraph-3d-html"]', - ) -> 'stim._DiagramHelper': - pass def diagram( self, type: str, @@ -4724,6 +4688,8 @@ class DetectorErrorModel: detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper errors. + "matchgraph-svg-html": Same as matchgraph-svg but with the + SVG wrapped in a resizable HTML iframe. "matchgraph-3d": A 3d model of the decoding graph of the detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper @@ -4755,12 +4721,12 @@ class DetectorErrorModel: >>> dem = circuit.detector_error_model(decompose_errors=True) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-svg") + ... diagram = circuit.diagram("match-graph-svg") ... with open(f"{d}/dem_image.svg", "w") as f: ... print(diagram, file=f) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-3d") + ... diagram = circuit.diagram("match-graph-3d") ... with open(f"{d}/dem_3d_model.gltf", "w") as f: ... print(diagram, file=f) """ diff --git a/src/stim.h b/src/stim.h index 9494d0b1b..090c6d585 100644 --- a/src/stim.h +++ b/src/stim.h @@ -6,7 +6,6 @@ #include "stim/arg_parse.h" #include "stim/circuit/circuit.h" #include "stim/circuit/circuit_instruction.h" -#include "stim/circuit/export_qasm.h" #include "stim/circuit/gate_decomposition.h" #include "stim/circuit/gate_target.h" #include "stim/cmd/command_analyze_errors.h" @@ -113,6 +112,9 @@ #include "stim/util_top/circuit_to_detecting_regions.h" #include "stim/util_top/circuit_vs_amplitudes.h" #include "stim/util_top/circuit_vs_tableau.h" +#include "stim/util_top/export_crumble_url.h" +#include "stim/util_top/export_qasm.h" +#include "stim/util_top/export_quirk_url.h" #include "stim/util_top/simplified_circuit.h" #include "stim/util_top/stabilizers_to_tableau.h" #include "stim/util_top/stabilizers_vs_amplitudes.h" diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 86da0b88a..9649b7658 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -18,7 +18,6 @@ #include "stim/circuit/circuit_instruction.pybind.h" #include "stim/circuit/circuit_repeat_block.pybind.h" -#include "stim/circuit/export_qasm.h" #include "stim/circuit/gate_target.pybind.h" #include "stim/cmd/command_diagram.pybind.h" #include "stim/dem/detector_error_model_target.pybind.h" @@ -43,6 +42,9 @@ #include "stim/util_top/circuit_inverse_qec.h" #include "stim/util_top/circuit_to_detecting_regions.h" #include "stim/util_top/circuit_vs_tableau.h" +#include "stim/util_top/export_crumble_url.h" +#include "stim/util_top/export_qasm.h" +#include "stim/util_top/export_quirk_url.h" #include "stim/util_top/simplified_circuit.h" using namespace stim; @@ -3105,17 +3107,6 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_ 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["timeline-svg"]', *, tick: Union[None, int, range] = None) -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["timeline-3d", "timeline-3d-html"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["matchgraph-svg"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["matchgraph-3d"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["matchgraph-3d-html"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["detslice-text"]', *, tick: int, filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),)) -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["detslice-svg"]', *, tick: Union[int, range], filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),)) -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["detslice-with-ops-svg"]', *, tick: Union[int, range], filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),)) -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["timeslice-svg"]', *, tick: Union[int, range], filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),)) -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["interactive", "interactive-html"]') -> 'stim._DiagramHelper': @signature def diagram(self, type: str = 'timeline-text', *, tick: Union[None, int, range] = None, filter_coords: Iterable[Union[Iterable[float], stim.DemTarget]] = ((),)) -> 'stim._DiagramHelper': Returns a diagram of the circuit, from a variety of options. @@ -3130,6 +3121,11 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_crumble_url() + 'https://algassert.com/crumble#circuit=H_0;CX_0_1;S_1' + )DOC") + .data()); + + c.def( + "to_quirk_url", + &export_quirk_url, + clean_doc_string(R"DOC( + Returns a URL that opens up quirk and loads this circuit into it. + + Quirk is an open source drag and drop circuit editor with support for up to 16 + qubits. Its source code is available at https://github.com/strilanc/quirk + and a prebuilt version is available at https://algassert.com/quirk, which is + what the URL returned by this method will point to. + + Quirk doesn't support features like noise, feedback, or detectors. This method + will simply drop any unsupported operations from the circuit when producing + the URL. + + Returns: + A URL that can be opened in a web browser. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... S 1 + ... ''').to_quirk_url() + 'https://algassert.com/quirk#circuit={"cols":[["H"],["•","X"],[1,"Z^½"]]}' + )DOC") + .data()); } diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index f63747caa..1cd4da5a2 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -1137,6 +1137,10 @@ def test_diagram(): assert c.diagram("time+detector-slice-svg", tick=range(1, 3, 2)) is not None with pytest.raises(ValueError, match="stop"): assert c.diagram("time+detector-slice-svg", tick=range(3, 3)) is not None + assert "iframe" in str(c.diagram(type="match-graph-svg-html")) + assert "iframe" in str(c.diagram(type="detslice-svg-html")) + assert "iframe" in str(c.diagram(type="timeslice-svg-html")) + assert "iframe" in str(c.diagram(type="timeline-svg-html")) def test_circuit_inverse(): diff --git a/src/stim/circuit/gate_decomposition.cc b/src/stim/circuit/gate_decomposition.cc index c6a45c270..349286f5a 100644 --- a/src/stim/circuit/gate_decomposition.cc +++ b/src/stim/circuit/gate_decomposition.cc @@ -299,3 +299,25 @@ void stim::for_each_disjoint_target_segment_in_instruction_reversed( flush(); } } + +void stim::for_each_combined_targets_group( + const CircuitInstruction &inst, + const std::function &callback) { + if (inst.targets.empty()) { + return; + } + size_t start = 0; + size_t next_start = 1; + while (true) { + if (next_start >= inst.targets.size() || !inst.targets[next_start].is_combiner()) { + callback(CircuitInstruction{inst.gate_type, inst.args, inst.targets.sub(start, next_start)}); + start = next_start; + next_start = start + 1; + if (next_start > inst.targets.size()) { + return; + } + } else { + next_start += 2; + } + } +} diff --git a/src/stim/circuit/gate_decomposition.h b/src/stim/circuit/gate_decomposition.h index b106e0f92..176d32c1e 100644 --- a/src/stim/circuit/gate_decomposition.h +++ b/src/stim/circuit/gate_decomposition.h @@ -111,6 +111,10 @@ void for_each_disjoint_target_segment_in_instruction_reversed( simd_bits_range_ref<64> workspace, const std::function &callback); +void for_each_combined_targets_group( + const CircuitInstruction &inst, + const std::function &callback); + } // namespace stim #endif diff --git a/src/stim/circuit/gate_decomposition.test.cc b/src/stim/circuit/gate_decomposition.test.cc index 377fd0b35..f10d8a9b7 100644 --- a/src/stim/circuit/gate_decomposition.test.cc +++ b/src/stim/circuit/gate_decomposition.test.cc @@ -310,3 +310,39 @@ TEST(gate_decomposition, for_each_disjoint_target_segment_in_instruction_reverse )CIRCUIT")); } + +TEST(gate_decomposition, for_each_combined_targets_group) { + Circuit out; + auto append_into_circuit = [&](const CircuitInstruction &segment) { + out.safe_append(segment); + out.append_from_text("TICK"); + }; + for_each_combined_targets_group( + Circuit("MPP(0.25) X0 Z1 Y2*Z3 Y4*Z5*Z6 Z8").operations[0], append_into_circuit); + for_each_combined_targets_group( + Circuit("MPP(0.25) X0 Y1 Z2").operations[0], append_into_circuit); + for_each_combined_targets_group( + Circuit("MPP(0.25) X0*Y1 Z2*Z3").operations[0], append_into_circuit); + ASSERT_EQ(out, Circuit(R"CIRCUIT( + MPP(0.25) X0 + TICK + MPP(0.25) Z1 + TICK + MPP(0.25) Y2*Z3 + TICK + MPP(0.25) Y4*Z5*Z6 + TICK + MPP(0.25) Z8 + TICK + MPP(0.25) X0 + TICK + MPP(0.25) Y1 + TICK + MPP(0.25) Z2 + TICK + MPP(0.25) X0*Y1 + TICK + MPP(0.25) Z2*Z3 + TICK + )CIRCUIT")); +} diff --git a/src/stim/cmd/command_diagram.pybind.cc b/src/stim/cmd/command_diagram.pybind.cc index 4dcdb1798..85c8c6ea5 100644 --- a/src/stim/cmd/command_diagram.pybind.cc +++ b/src/stim/cmd/command_diagram.pybind.cc @@ -74,44 +74,48 @@ std::string escape_html_for_srcdoc(const std::string &src) { return dst.str(); } -void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_ &c) { - c.def("_repr_html_", [](const DiagramHelper &self) -> pybind11::object { - std::string output = "None"; - if (self.type == DiagramType::DIAGRAM_TYPE_TEXT) { - return pybind11::cast("
" + self.content + "
"); - } - if (self.type == DiagramType::DIAGRAM_TYPE_SVG) { - return pybind11::none(); - // This commented out code would wrap the SVG in a little thing with a resizable tab. - // That's convenient, but it breaks things like github showing the image. - // std::stringstream out; - // out << R"HTML(
)HTML"; out << R"HTML(
)HTML"; output = out.str(); - } - if (self.type == DiagramType::DIAGRAM_TYPE_GLTF) { - std::stringstream out; - write_html_viewer_for_gltf_data(self.content, out); - output = out.str(); - } - if (self.type == DiagramType::DIAGRAM_TYPE_HTML) { - output = self.content; - } - if (output == "None") { - return pybind11::none(); - } +pybind11::object diagram_as_html(const DiagramHelper &self) { + std::string output = "None"; + if (self.type == DiagramType::DIAGRAM_TYPE_TEXT) { + return pybind11::cast("
" + self.content + "
"); + } + if (self.type == DiagramType::DIAGRAM_TYPE_SVG_HTML) { + // Wrap the SVG image into an img tag. + std::stringstream out; + out << R"HTML()HTML"; + output = out.str(); + } else if (self.type == DiagramType::DIAGRAM_TYPE_SVG) { + // Github's Jupyter notebook preview will fail to show SVG images if they are wrapped in HTML. + // So, for SVG diagrams, we refuse to return an html repr in this case. + return pybind11::none(); + } + if (self.type == DiagramType::DIAGRAM_TYPE_GLTF) { + std::stringstream out; + write_html_viewer_for_gltf_data(self.content, out); + output = out.str(); + } + if (self.type == DiagramType::DIAGRAM_TYPE_HTML) { + output = self.content; + } + if (output == "None") { + return pybind11::none(); + } - // Wrap the output into an iframe. - // In a Jupyter notebook this is very important, because it prevents output - // cells from seeing each others' elements when finding elements by id. - // Because, for some insane reason, Jupyter notebooks don't isolate the cells - // from each other by default! Colab does the right thing at least... - std::string framed = - R"HTML()HTML"; - return pybind11::cast(framed); - }); + // Wrap the output into an iframe. + // In a Jupyter notebook this is very important, because it prevents output + // cells from seeing each others' elements when finding elements by id. + // Because, for some insane reason, Jupyter notebooks don't isolate the cells + // from each other by default! Colab does the right thing at least... + std::string framed = + R"HTML()HTML"; + return pybind11::cast(framed); +} + +void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_ &c) { + c.def("_repr_html_", &diagram_as_html); c.def("_repr_svg_", [](const DiagramHelper &self) -> pybind11::object { if (self.type != DiagramType::DIAGRAM_TYPE_SVG) { return pybind11::none(); @@ -121,16 +125,22 @@ void stim_pybind::pybind_diagram_methods(pybind11::module &m, pybind11::class_ void { pybind11::getattr(p, "text")(self.content); }); - c.def("__str__", [](const DiagramHelper &self) { - return self.content; + c.def("__str__", [](const DiagramHelper &self) -> pybind11::object { + if (self.type == DiagramType::DIAGRAM_TYPE_SVG_HTML) { + return diagram_as_html(self); + } + return pybind11::cast(self.content); }); } -DiagramHelper stim_pybind::dem_diagram(const DetectorErrorModel &dem, const std::string &type) { - if (type == "matchgraph-svg" || type == "match-graph-svg") { +DiagramHelper stim_pybind::dem_diagram(const DetectorErrorModel &dem, std::string_view type) { + if (type == "matchgraph-svg" || type == "match-graph-svg" || type == "match-graph-svg-html" || type == "matchgraph-svg-html") { std::stringstream out; dem_match_graph_to_svg_diagram_write_to(dem, out); - return DiagramHelper{DiagramType::DIAGRAM_TYPE_SVG, out.str()}; + DiagramType d_type = type.find("html") != std::string::npos + ? DiagramType::DIAGRAM_TYPE_SVG_HTML + : DiagramType::DIAGRAM_TYPE_SVG; + return DiagramHelper{d_type, out.str()}; } else if (type == "matchgraph-3d" || type == "match-graph-3d") { std::stringstream out; dem_match_graph_to_basic_3d_diagram(dem).to_gltf_scene().to_json().write(out); @@ -142,7 +152,9 @@ DiagramHelper stim_pybind::dem_diagram(const DetectorErrorModel &dem, const std: write_html_viewer_for_gltf_data(out.str(), out_html); return DiagramHelper{DiagramType::DIAGRAM_TYPE_GLTF, out_html.str()}; } else { - throw std::invalid_argument("Unrecognized diagram type: " + type); + std::stringstream ss; + ss << "Unrecognized diagram type: " << type; + throw std::invalid_argument(ss.str()); } } @@ -199,7 +211,7 @@ std::vector item_to_filter_multi(const pybind11::object &obj) { DiagramHelper stim_pybind::circuit_diagram( const Circuit &circuit, - const std::string &type, + std::string_view type, const pybind11::object &tick, const pybind11::object &filter_coords_obj) { std::vector filter_coords; @@ -237,22 +249,48 @@ DiagramHelper stim_pybind::circuit_diagram( std::stringstream out; out << DiagramTimelineAsciiDrawer::make_diagram(circuit); return DiagramHelper{DiagramType::DIAGRAM_TYPE_TEXT, out.str()}; - } else if (type == "timeline-svg" || type == "timeline") { + } else if (type == "timeline-svg" || type == "timeline" || type == "timeline-svg-html" || type == "timeline-html") { std::stringstream out; DiagramTimelineSvgDrawer::make_diagram_write_to( - circuit, out, tick_min, num_ticks, DiagramTimelineSvgDrawerMode::SVG_MODE_TIMELINE, filter_coords); - return DiagramHelper{DiagramType::DIAGRAM_TYPE_SVG, out.str()}; - } else if (type == "time-slice-svg" || type == "timeslice-svg" || type == "timeslice" || type == "time-slice") { + circuit, + out, + tick_min, + num_ticks, + DiagramTimelineSvgDrawerMode::SVG_MODE_TIMELINE, + filter_coords); + DiagramType d_type = type.find("html") != std::string::npos + ? DiagramType::DIAGRAM_TYPE_SVG_HTML + : DiagramType::DIAGRAM_TYPE_SVG; + return DiagramHelper{d_type, out.str()}; + } else if (type == "time-slice-svg" + || type == "timeslice-svg" + || type == "timeslice-html" + || type == "timeslice-svg-html" + || type == "time-slice-html" + || type == "time-slice-svg-html" + || type == "timeslice" + || type == "time-slice") { std::stringstream out; DiagramTimelineSvgDrawer::make_diagram_write_to( circuit, out, tick_min, num_ticks, DiagramTimelineSvgDrawerMode::SVG_MODE_TIME_SLICE, filter_coords); - return DiagramHelper{DiagramType::DIAGRAM_TYPE_SVG, out.str()}; + DiagramType d_type = type.find("html") != std::string::npos + ? DiagramType::DIAGRAM_TYPE_SVG_HTML + : DiagramType::DIAGRAM_TYPE_SVG; + return DiagramHelper{d_type, out.str()}; } else if ( - type == "detslice-svg" || type == "detslice" || type == "detector-slice-svg" || type == "detector-slice") { + type == "detslice-svg" + || type == "detslice" + || type == "detslice-html" + || type == "detslice-svg-html" + || type == "detector-slice-svg" + || type == "detector-slice") { std::stringstream out; DetectorSliceSet::from_circuit_ticks(circuit, tick_min, num_ticks, filter_coords).write_svg_diagram_to(out); - return DiagramHelper{DiagramType::DIAGRAM_TYPE_SVG, out.str()}; - } else if (type == "detslice-with-ops" || type == "detslice-with-ops-svg" || type == "time+detector-slice-svg") { + DiagramType d_type = type.find("html") != std::string::npos + ? DiagramType::DIAGRAM_TYPE_SVG_HTML + : DiagramType::DIAGRAM_TYPE_SVG; + return DiagramHelper{d_type, out.str()}; + } else if (type == "detslice-with-ops" || type == "detslice-with-ops-svg" || type == "detslice-with-ops-html" || type == "detslice-with-ops-svg-html" || type == "time+detector-slice-svg") { std::stringstream out; DiagramTimelineSvgDrawer::make_diagram_write_to( circuit, @@ -261,7 +299,10 @@ DiagramHelper stim_pybind::circuit_diagram( num_ticks, DiagramTimelineSvgDrawerMode::SVG_MODE_TIME_DETECTOR_SLICE, filter_coords); - return DiagramHelper{DiagramType::DIAGRAM_TYPE_SVG, out.str()}; + DiagramType d_type = type.find("html") != std::string::npos + ? DiagramType::DIAGRAM_TYPE_SVG_HTML + : DiagramType::DIAGRAM_TYPE_SVG; + return DiagramHelper{d_type, out.str()}; } else if (type == "timeline-3d") { std::stringstream out; DiagramTimeline3DDrawer::circuit_to_basic_3d_diagram(circuit).to_gltf_scene().to_json().write(out); @@ -280,16 +321,22 @@ DiagramHelper stim_pybind::circuit_diagram( std::stringstream out; write_crumble_html_with_preloaded_circuit(circuit, out); return DiagramHelper{DiagramType::DIAGRAM_TYPE_HTML, out.str()}; - } else if (type == "match-graph-svg" || type == "matchgraph-svg") { - auto dem = ErrorAnalyzer::circuit_to_detector_error_model(circuit, true, true, false, 1, true, false); - return dem_diagram(dem, "matchgraph-svg"); - } else if (type == "match-graph-3d" || type == "matchgraph-3d") { - auto dem = ErrorAnalyzer::circuit_to_detector_error_model(circuit, true, true, false, 1, true, false); - return dem_diagram(dem, "matchgraph-3d"); - } else if (type == "match-graph-3d-html" || type == "matchgraph-3d-html") { + } else if (type == "match-graph-svg" + || type == "matchgraph-svg" + || type == "matchgraph-svg-html" + || type == "matchgraph-html" + || type == "match-graph-svg-html" + || type == "match-graph-html" + || type == "match-graph-3d" + || type == "matchgraph-3d" + || type == "match-graph-3d-html" + || type == "matchgraph-3d-html") { auto dem = ErrorAnalyzer::circuit_to_detector_error_model(circuit, true, true, false, 1, true, false); - return dem_diagram(dem, "matchgraph-3d-html"); + return dem_diagram(dem, type); } else { - throw std::invalid_argument("Unrecognized diagram type: " + type); + std::stringstream ss; + ss << "Unrecognized diagram type: '"; + ss << type << "'"; + throw std::invalid_argument(ss.str()); } } diff --git a/src/stim/cmd/command_diagram.pybind.h b/src/stim/cmd/command_diagram.pybind.h index a9c446e7b..9e38276f2 100644 --- a/src/stim/cmd/command_diagram.pybind.h +++ b/src/stim/cmd/command_diagram.pybind.h @@ -27,6 +27,7 @@ enum class DiagramType { DIAGRAM_TYPE_SVG, DIAGRAM_TYPE_TEXT, DIAGRAM_TYPE_HTML, + DIAGRAM_TYPE_SVG_HTML, }; struct DiagramHelper { @@ -36,10 +37,10 @@ struct DiagramHelper { pybind11::class_ pybind_diagram(pybind11::module &m); void pybind_diagram_methods(pybind11::module &m, pybind11::class_ &c); -DiagramHelper dem_diagram(const stim::DetectorErrorModel &dem, const std::string &type); +DiagramHelper dem_diagram(const stim::DetectorErrorModel &dem, std::string_view type); DiagramHelper circuit_diagram( const stim::Circuit &circuit, - const std::string &type, + std::string_view type, const pybind11::object &tick, const pybind11::object &filter_coords_obj); diff --git a/src/stim/dem/detector_error_model.pybind.cc b/src/stim/dem/detector_error_model.pybind.cc index 5169a7e77..72b1b8382 100644 --- a/src/stim/dem/detector_error_model.pybind.cc +++ b/src/stim/dem/detector_error_model.pybind.cc @@ -1136,9 +1136,6 @@ void stim_pybind::pybind_detector_error_model_methods( &dem_diagram, pybind11::arg("type"), clean_doc_string(R"DOC( - @overload def diagram(self, type: 'Literal["matchgraph-svg"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["matchgraph-3d"]') -> 'stim._DiagramHelper': - @overload def diagram(self, type: 'Literal["matchgraph-3d-html"]') -> 'stim._DiagramHelper': @signature def diagram(self, type: str) -> Any: Returns a diagram of the circuit, from a variety of options. @@ -1148,6 +1145,8 @@ void stim_pybind::pybind_detector_error_model_methods( detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper errors. + "matchgraph-svg-html": Same as matchgraph-svg but with the + SVG wrapped in a resizable HTML iframe. "matchgraph-3d": A 3d model of the decoding graph of the detector error model. Red lines are errors crossing a logical observable. Blue lines are undecomposed hyper @@ -1179,12 +1178,12 @@ void stim_pybind::pybind_detector_error_model_methods( >>> dem = circuit.detector_error_model(decompose_errors=True) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-svg") + ... diagram = circuit.diagram("match-graph-svg") ... with open(f"{d}/dem_image.svg", "w") as f: ... print(diagram, file=f) >>> with tempfile.TemporaryDirectory() as d: - ... diagram = circuit.diagram(type="match-graph-3d") + ... diagram = circuit.diagram("match-graph-3d") ... with open(f"{d}/dem_3d_model.gltf", "w") as f: ... print(diagram, file=f) )DOC") diff --git a/src/stim/dem/detector_error_model_pybind_test.py b/src/stim/dem/detector_error_model_pybind_test.py index fab07f2c0..1617b00ab 100644 --- a/src/stim/dem/detector_error_model_pybind_test.py +++ b/src/stim/dem/detector_error_model_pybind_test.py @@ -499,6 +499,7 @@ def test_diagram(): assert dem.diagram(type="match-graph-svg") is not None assert dem.diagram(type="match-graph-3d") is not None assert dem.diagram(type="match-graph-3d-html") is not None + assert "iframe" in str(dem.diagram(type="match-graph-svg-html")) def test_shortest_graphlike_error_remnant(): diff --git a/src/stim/diagram/crumble_data.cc b/src/stim/diagram/crumble_data.cc index f5695b95d..3cfe4817c 100644 --- a/src/stim/diagram/crumble_data.cc +++ b/src/stim/diagram/crumble_data.cc @@ -172,13 +172,13 @@ std::string stim_draw_internal::make_crumble_html() { result.append(R"CRUMBLE_PART(class l{constructor(t,r,e,i,o,a,n,s=void 0){this.name=t,this.t=r,this.i=i,this.o=e,this.l=o,this.h=a,this.v=n,this.u=s}X(t){return new l(this.name,this.t,this.o,this.i,this.l,this.h,this.v,t)}}function _(r){var e=[];for(let t=0;tr.get(t)))for(let t=0;tt.do_iswap(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),f(e,i,o),f(e,r,t)}),yield new l("ISWAP_DAG",2,!0,!1,new Map([["IX","YZ"],["IZ","ZI"],["XI","ZY"],["ZI","IZ"]]),(t,r)=>t.do_iswap(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),f(e,i,o),f(e,r,t)}),yield new l("SWAP",2,!0,!1,new Map([["IX","XI"],["IZ","ZI"],["XI","IX"],["ZI","IZ"]]),(t,r)=>t.do_swap(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),S(e,i,o),S(e,r,t)}),yield new l("CXSWAP",2,!0,!1,new Map([["IX","XI"],["IZ","ZZ"],["XI","XX"],["ZI","IZ"]]),(t,r)=>t.do_cx_swap(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),h(e,i,o),i=e,o=t,void 0!==(e=r)&&void 0!==o&&(i.fillStyle="white",i.strokeStyle=)CRUMBLE_PART"); result.append(R"CRUMBLE_PART("black",i.beginPath(),i.arc(e,o,nt,0,2*Math.PI),i.fill(),i.stroke(),t=.4*nt,i.strokeStyle="black",i.lineWidth=3,i.beginPath(),i.moveTo(e-t,o-t),i.lineTo(e+t,o+t),i.stroke(),i.moveTo(e-t,o+t),i.lineTo(e+t,o-t),i.stroke(),i.lineWidth=1)}),yield new l("CZSWAP",2,!0,!1,new Map([["IX","XZ"],["IZ","ZI"],["XI","ZX"],["ZI","IZ"]]),(t,r)=>t.do_cz_swap(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),h(e,i,o),h(e,r,t)})}function*P(){yield new l("CX",2,!0,!1,new Map([["IX","IX"],["IZ","ZZ"],["XI","XX"],["ZI","ZI"]]),(t,r)=>t.do_cx(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),s(e,i,o),a(e,r,t)}),yield new l("CY",2,!0,!1,new Map([["IX","ZX"],["IZ","ZZ"],["XI","XY"],["ZI","ZI"]]),(t,r)=>t.do_cy(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),s(e,i,o),n(e,r,t)}),yield new l("XCX",2,!0,!1,new Map([["IX","IX"],["IZ","XZ"],["XI","XI"],["ZI","ZX"]]),(t,r)=>t.do_xcx(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),a(e,i,o),a(e,r,t)}),yield new l("XCY",2,!0,!1,new Map([["IX","X)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(X"],["IZ","XZ"],["XI","XI"],["ZI","ZY"]]),(t,r)=>t.do_xcy(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),a(e,i,o),n(e,r,t)}),yield new l("YCY",2,!0,!1,new Map([["IX","YX"],["IZ","YZ"],["XI","XY"],["ZI","ZY"]]),(t,r)=>t.do_ycy(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),n(e,i,o),n(e,r,t)}),yield new l("CZ",2,!0,!1,new Map([["IX","ZX"],["IZ","IZ"],["XI","XZ"],["ZI","ZI"]]),(t,r)=>t.do_cz(r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),s(e,i,o),s(e,r,t)}),yield new l("MR",1,!0,!1,new Map([["X","ERR:I"],["Y","ERR:I"],["Z","I"]]),(t,r)=>t.do_demolition_measure("Z",r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="gray",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MR",r,t)}),yield new l("MRY",1,!0,!1,new Map([["X","ERR:I"],["Y","I"],["Z","ERR:I"]]),(t,r)=>t.do_demolition_measure("Y",r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="gray",e.fillRect(r-nt,t-nt,2*nt,)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MRY",r,t)}),yield new l("MRX",1,!0,!1,new Map([["X","I"],["Y","ERR:I"],["Z","ERR:I"]]),(t,r)=>t.do_demolition_measure("X",r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="gray",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MRX",r,t)}),yield new l("H",1,!0,!1,new Map([["X","Z"],["Z","X"]]),(t,r)=>t.do_exchange_xz(r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="yellow",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("H",r,t)}),yield new l("H_XY",1,!0,!1,new Map([["X","Y"],["Z","Z"]]),(t,r)=>t.do_exchange_xy(r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="yellow",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fil)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(lStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("H",r,t-nt/3),e.fillText("XY",r,t+nt/3)}),yield new l("H_YZ",1,!0,!1,new Map([["X","X"],["Z","Y"]]),(t,r)=>t.do_exchange_yz(r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="yellow",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("H",r,t-nt/3),e.fillText("YZ",r,t+nt/3)}),yield new l("POLYGON",void 0,!1,!0,void 0,()=>{},(t,r,e)=>{var i,o=[];for(i of t.R)o.push(r(i));W(e,o),e.globalAlpha*=t.Y[3],e.fillStyle=`rgb(${255*t.Y[0]},${255*t.Y[1]},${255*t.Y[2]})`,e.strokeStyle=`rgb(${32*t.Y[0]},${32*t.Y[1]},${32*t.Y[2]})`,e.fill(),e.stroke()}),yield new l("MARKX",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=U(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="red",n.beginPath(),n.moveTo(a,s),n)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARKY",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=U(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="green",n.beginPath(),n.moveTo(a,s),n.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARKZ",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=U(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="blue",n.beginPath(),n.moveTo(a,s),n.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARK",1,!1,!0,void 0,()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);void 0!==r&&void 0!==t&&(e.fillStyle="magenta",e.fillRect(r-nt,t-nt,nt,nt))}),yield new l("MXX",2,!0,!1,new Map([["II","II"],["IX","IX"],["IY","ERR:IY"],["IZ","ERR:IZ"],["XI","XI"],["XX",")CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(lStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("H",r,t-nt/3),e.fillText("XY",r,t+nt/3)}),yield new l("H_YZ",1,!0,!1,new Map([["X","X"],["Z","Y"]]),(t,r)=>t.do_exchange_yz(r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="yellow",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("H",r,t-nt/3),e.fillText("YZ",r,t+nt/3)}),yield new l("POLYGON",void 0,!1,!0,void 0,()=>{},(t,r,e)=>{var i,o=[];for(i of t.R)o.push(r(i));W(e,o),e.globalAlpha*=t.Y[3],e.fillStyle=`rgb(${255*t.Y[0]},${255*t.Y[1]},${255*t.Y[2]})`,e.strokeStyle=`rgb(${32*t.Y[0]},${32*t.Y[1]},${32*t.Y[2]})`,e.fill(),e.stroke()}),yield new l("MARKX",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=H(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="red",n.beginPath(),n.moveTo(a,s),n)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARKY",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=H(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="green",n.beginPath(),n.moveTo(a,s),n.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARKZ",1,!0,!0,void 0,()=>{},(o,a,n)=>{var[a,s]=a(o.R[0]);if(void 0!==a&&void 0!==s){var{dx:o,dy:l,m:h,p:f}=H(o.Y[0]);let t,r,e,i;i=h===f?(t=a+o,r=s+l,e=t+o+h,r+l+f):(t=a+(o<0?1:-1)*nt,r=s+(l<0?1:-1)*nt,e=t+(h>nt?1:0)*nt*2,r+(f>nt?1:0)*nt*2),n.fillStyle="blue",n.beginPath(),n.moveTo(a,s),n.lineTo(t,r),n.lineTo(e,i),n.lineTo(a,s),n.fill()}}),yield new l("MARK",1,!1,!0,void 0,()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);void 0!==r&&void 0!==t&&(e.fillStyle="magenta",e.fillRect(r-nt,t-nt,nt,nt))}),yield new l("MXX",2,!0,!1,new Map([["II","II"],["IX","IX"],["IY","ERR:IY"],["IZ","ERR:IZ"],["XI","XI"],["XX",")CRUMBLE_PART"); result.append(R"CRUMBLE_PART(XX"],["XY","ERR:XY"],["XZ","ERR:XZ"],["YI","ERR:YI"],["YX","ERR:YX"],["YY","YY"],["YZ","YZ"],["ZI","ERR:ZI"],["ZX","ERR:ZX"],["ZY","ZY"],["ZZ","ZZ"]]),(t,r)=>t.do_measure("XX",r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),e.fillStyle="gray",e.fillRect(i-nt,o-nt,2*nt,2*nt),e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(i-nt,o-nt,2*nt,2*nt),e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MXX",i,o),e.fillText("MXX",r,t)}),yield new l("MYY",2,!0,!1,new Map([["II","II"],["IX","ERR:IX"],["IY","IY"],["IZ","ERR:IZ"],["XI","ERR:XI"],["XX","XX"],["XY","ERR:XY"],["XZ","XZ"],["YI","YI"],["YX","ERR:YX"],["YY","YY"],["YZ","ERR:YZ"],["ZI","ERR:ZI"],["ZX","ZX"],["ZY","ERR:ZY"],["ZZ","ZZ"]]),(t,r)=>t.do_measure("YY",r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),e.fillStyle="gray",e.fillRect(i-nt,o-nt,2*nt,2*nt),e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(i-nt,o-nt,2*nt,2*nt),e.strokeRect(r-nt)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MYY",i,o),e.fillText("MYY",r,t)}),yield new l("MZZ",2,!0,!1,new Map([["II","II"],["IX","ERR:IX"],["IY","ERR:IY"],["IZ","IZ"],["XI","ERR:XI"],["XX","XX"],["XY","XY"],["XZ","ERR:XZ"],["YI","ERR:YI"],["YX","YX"],["YY","YY"],["YZ","ERR:YZ"],["ZI","ZI"],["ZX","ERR:ZX"],["ZY","ERR:ZY"],["ZZ","ZZ"]]),(t,r)=>t.do_measure("ZZ",r),(t,r,e)=>{var[i,o]=r(t.R[0]),[r,t]=r(t.R[1]);v(e,i,o,r,t),e.fillStyle="gray",e.fillRect(i-nt,o-nt,2*nt,2*nt),e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(i-nt,o-nt,2*nt,2*nt),e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("MZZ",i,o),e.fillText("MZZ",r,t)}),yield new l("I",1,!0,!1,new Map([["X","X"],["Z","Z"]]),()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="white",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(.fillText("I",r,t)}),yield new l("X",1,!0,!1,new Map([["X","X"],["Z","Z"]]),()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="white",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("X",r,t)}),yield new l("Y",1,!0,!1,new Map([["X","X"],["Z","Z"]]),()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="white",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("Y",r,t)}),yield new l("Z",1,!0,!1,new Map([["X","X"],["Z","Z"]]),()=>{},(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="white",e.fillRect(r-nt,t-nt,2*nt,2*nt),e.strokeStyle="black",e.strokeRect(r-nt,t-nt,2*nt,2*nt),e.fillStyle="black",e.textAlign="center",e.textBaseline="middle",e.fillText("Z",r,t)}),yield new l("S",1,!0,!1,new Map([["X","Y"],["Z","Z"]]),(t,r)=>t.do_exchange_xy(r),(t,r,e)=>{var[r,t]=r(t.R[0]);e.fillStyle="yellow",e.fillRect(r-)CRUMBLE_PART"); @@ -195,14 +195,14 @@ std::string stim_draw_internal::make_crumble_html() { result.append(R"CRUMBLE_PART({try{var e=String(t);if(e!==T)return e}catch{}var i=t,o=r,a=[];for(s in i)if(i.hasOwnProperty(s)){if(a.length>w){a.push("[...]");break}var n=i[s],s=A(s,o-1),n=A(n,o-1);a.push(s+": "+n)}return void 0===i.constructor?"[an unknown non-primitive value with no constructor]":(e=(e=i.constructor.name)==={}.constructor.name?"":`(Type: ${e})`)+`{${a.join(", ")}}`}function A(t,r=x){return(null===(e=t)?"null":void 0===e?"undefined":"string"==typeof e?`"${e}"`:"number"==typeof e?""+e:void 0)||G(t,r)||L(t,r);var e}function Q(t,r){var e,i=new Map;for(e of t){var o=r(e),a=i.get(o);void 0===a?i.set(o,[e]):a.push(e)}return i}function F(t){let r=[];var e,i=()=>{""!==o&&(r.push(o),o="")};let o="";for(e of t)" "===e?i():"*"===e?(i(),r.push("*")):o+=e;return i(),r}function N(e){var t=[];let i=0;for(;ie.length)throw Error(`Dangling combiner in ${e}.`);var o=[];for(let t=i;tt.do_mpp(f,r),(r,e,i)=>{let o=void 0,a=void 0;for(let t=0;tt.do_spp(f,r),(r,e,i)=>{let o=void 0,a=void 0;for(let t=0;tt instanceof b))throw new)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART( Error("!layers.every(e => e instanceof Layer)");this.F=t,this.N=r}static K(t){t=t.replaceAll(";","\n").replaceAll("_"," ").replaceAll("Q(","QUBIT_COORDS(").replaceAll("DT","DETECTOR").replaceAll(" COORDS","_COORDS").replaceAll(" ERROR","_ERROR").replaceAll("C XYZ","C_XYZ").replaceAll("H XY","H_XY").replaceAll("H YZ","H_YZ").replaceAll(" INCLUDE","_INCLUDE").replaceAll("SQRT ","SQRT_").replaceAll(" DAG ","_DAG ").replaceAll("C ZYX","C_ZYX").split("\n");let M=[new b],I=new Map,m=new Set,s=(e,i,o,r)=>{M[M.length-1].empty()||M.push(new b);for(let t=0;t{let o=0;for(let r=t;r{let a=[],n=[],s="";if(o.indexOf(")")!==-1){let[t,r]=o.split(")");let[e,i]=t.split("(");s=e.trim();a=i)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(.split(",").map(t=>t.trim()).map(parseFloat);n=F(r)}else{let t=o.split(" ").map(t=>t.trim()).filter(t=>t!=="");if(t.length===0)return;let[r,...e]=t;s=r.trim();a=[];n=e.flatMap(F)}let e=false;if(""!==s){var r=O.get(s),i;if(void 0!==r){if(r.k)return;if(void 0===r.name)throw new Error(`Unimplemented alias ${s}: ${A(r)}.`);e=void 0!==r.A&&r.A,s=r.name}else{if("TICK"===s)return M.push(new b);if("MPP"===s){var l=N(n),h;let r=M[M.length-1];for(h of l)try{r.put(K(new Float32Array(a),h),!1)}catch(t){M.push(new b),(r=M[M.length-1]).put(K(new Float32Array(a),h),!1)}return}if("SPP"===s||"SPP_DAG"===s){var f="SPP_DAG"===s,c=N(n),v;let r=M[M.length-1];for(v of c)try{r.put(q(new Float32Array(a),f,v),!1)}catch(t){M.push(new b),(r=M[M.length-1]).put(q(new Float32Array(a),f,v),!1)}return}if(s.startsWith("QUBIT_COORDS")){var d=a.length<1?0:a[0],w=a.length<2?0:a[1],u;for(u of n){var X=parseInt(u);I.has(X)?console.warn(`Ignoring "${o}" because there's already coordinate data for qubit ${X}.`):m.has(d+","+w)?console.warn(`Ignoring)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART( "${o}" because there's already a qubit placed at ${d},${w}.`):(I.set(X,[d,w]),m.add(d+","+w))}return}}let t=!1;for(i of n){if(i.startsWith("rec[")&&("CX"===s||"CY"===s||"CZ"===s||"ZCX"===s||"ZCY"===s)){t=!0;break}if("number"!=typeof parseInt(i))throw new Error(o)}if(t)console.warn("IGNORED",s);else{var Z=E.get(s);if(void 0===Z)console.warn("Unrecognized gate name in "+o);else{var Y=new Float32Array(a);let r=M[M.length-1];if(void 0===Z.t)r.put(new p(Z,Y,new Uint32Array(n)));else{if(n.length%Z.t!=0)throw new Error("Incorrect number of targets in line "+o);for(let t=0;t{let r=!0;for(;!I.has(t);){var e=r?t:o,i=e+",0";m.has(i)||(m.add(i),I.set(t,[e,0])),o+=!r,r=!1}};for(r of M)for(var i of r.L())for(var a of i.R)e(a);var n=Mat)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(h.max(...I.keys(),0)+1,l=new Float64Array(2*n);for(let t=0;t[t-r,t+r])}W(){var t,r,e=new Map;for(let t=0;t1/256;)s/=2;let l;if(s<=1/256)l=1;else{l=1/s;let t=0;for(var[h,f]of e.values()){var c=(h-a+f-n)%(2*s),h=(h-a-f+n)%(2*s);t=t|(0==c?1:2)|(0==h?4:8)}5===t?l/=2:10===t&&(a-=s,l/=2)}let v=-a,d=-n;return(t,r)=>[(t+v)*l,(r+d)*l]}q(){return this.$(this.W())}B(e,i){return this.$((t,r)=>[t+e,r+i])}g(){return this.B(0,0)}$(r){var e=new Float64Array(this.F.length);for(let t=0;tt.g());return new u(e,t)}j(){var t,r=new Set;for(t of this.N)for(var e of t.L())for(var i of e.R)r.add)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART((i);var o,a=[];for(o of r){var n=this.F[2*o],s=this.F[2*o+1];a.push({V:o,x:n,y:s})}a.sort((t,r)=>t.x!==r.x?t.x-r.x:t.y!==r.y?t.y-r.y:t.V-r.V);var l,h=new Map,f=[];for(let t=0;t{let r=t.Z.name;return(r=(r=r.startsWith("MPP:")&&!E.has(r)?"MPP":r).startsWith("SPP:")&&!E.has(r)?"SPP":r).startsWith("SPP_DAG:")&&!E.has(r)&&(r="SPP_DAG"),0{var e=t.startsWith("MARK")||t.startsWith("POLY"),i=r.startsWith("MARK")||r.startsWith("POLY");return e!==i?et.g()))}tt(){var r=new Map;for(let t=0;t!(e instanceof t))){var s=t,l=r;if(s.length!==l.length)return!1;for(let t=0;t e instanceof Layer)");this.F=t,this.N=r}static K(t){t=t.replaceAll(";","\n").replaceAll("_"," ").replaceAll("Q(","QUBIT_COORDS(").replaceAll("DT","DETECTOR").replaceAll(" COORDS","_COORDS").replaceAll(" ERROR","_ERROR").replaceAll("C XYZ","C_XYZ").replaceAll("H XY","H_XY").replaceAll("H XZ","H_XZ").replaceAll("H YZ","H_YZ").replaceAll(" INCLUDE","_INCLUDE").replaceAll("SQRT ","SQRT_").replaceAll(" DAG ","_DAG ").replaceAll("C ZYX","C_ZYX").split("\n");let M=[new b],I=new Map,m=new Set,s=(e,i,o,r)=>{M[M.length-1].empty()||M.push(new b);for(let t=0;t{let o=0;for(let r=t;r{let a=[],n=[],s="";if(o.indexOf(")")!==-1){let[t,r]=o.split(")");let[e,i]=t)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(.split("(");s=e.trim();a=i.split(",").map(t=>t.trim()).map(parseFloat);n=F(r)}else{let t=o.split(" ").map(t=>t.trim()).filter(t=>t!=="");if(t.length===0)return;let[r,...e]=t;s=r.trim();a=[];n=e.flatMap(F)}let e=false;if(""!==s){var r=O.get(s),i;if(void 0!==r){if(r.k)return;if(void 0===r.name)throw new Error(`Unimplemented alias ${s}: ${A(r)}.`);e=void 0!==r.A&&r.A,s=r.name}else{if("TICK"===s)return M.push(new b);if("MPP"===s){var l=N(n),h;let r=M[M.length-1];for(h of l)try{r.put(K(new Float32Array(a),h),!1)}catch(t){M.push(new b),(r=M[M.length-1]).put(K(new Float32Array(a),h),!1)}return}if("SPP"===s||"SPP_DAG"===s){var f="SPP_DAG"===s,c=N(n),v;let r=M[M.length-1];for(v of c)try{r.put(q(new Float32Array(a),f,v),!1)}catch(t){M.push(new b),(r=M[M.length-1]).put(q(new Float32Array(a),f,v),!1)}return}if(s.startsWith("QUBIT_COORDS")){var d=a.length<1?0:a[0],w=a.length<2?0:a[1],u;for(u of n){var X=parseInt(u);I.has(X)?console.warn(`Ignoring "${o}" because there's already coordinate data for qubit ${X}.`):m.has(d+",")CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(+w)?console.warn(`Ignoring "${o}" because there's already a qubit placed at ${d},${w}.`):(I.set(X,[d,w]),m.add(d+","+w))}return}}let t=!1;for(i of n){if(i.startsWith("rec[")&&("CX"===s||"CY"===s||"CZ"===s||"ZCX"===s||"ZCY"===s)){t=!0;break}if("number"!=typeof parseInt(i))throw new Error(o)}if(t)console.warn("IGNORED",s);else{var Z=E.get(s);if(void 0===Z)console.warn("Unrecognized gate name in "+o);else{var Y=new Float32Array(a);let r=M[M.length-1];if(void 0===Z.t)r.put(new p(Z,Y,new Uint32Array(n)));else{if(n.length%Z.t!=0)throw new Error("Incorrect number of targets in line "+o);for(let t=0;t{let r=!0;for(;!I.has(t);){var e=r?t:o,i=e+",0";m.has(i)||(m.add(i),I.set(t,[e,0])),o+=!r,r=!1}};for(r of M)for(var i of r.L())for(v)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(ar a of i.R)e(a);var n=Math.max(...I.keys(),0)+1,l=new Float64Array(2*n);for(let t=0;t[t-r,t+r])}W(){var t,r,e=new Map;for(let t=0;t1/256;)s/=2;let l;if(s<=1/256)l=1;else{l=1/s;let t=0;for(var[h,f]of e.values()){var c=(h-a+f-n)%(2*s),h=(h-a-f+n)%(2*s);t=t|(0==c?1:2)|(0==h?4:8)}5===t?l/=2:10===t&&(a-=s,l/=2)}let v=-a,d=-n;return(t,r)=>[(t+v)*l,(r+d)*l]}q(){return this.$(this.W())}B(e,i){return this.$((t,r)=>[t+e,r+i])}g(){return this.B(0,0)}$(r){var e=new Float64Array(this.F.length);for(let t=0;tt.g());return new u(e,t)}j(){var t,r=new Set;for(t of this.N)for(var e of t.)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(L())for(var i of e.R)r.add(i);var o,a=[];for(o of r){var n=this.F[2*o],s=this.F[2*o+1];a.push({V:o,x:n,y:s})}a.sort((t,r)=>t.x!==r.x?t.x-r.x:t.y!==r.y?t.y-r.y:t.V-r.V);var l,h=new Map,f=[];for(let t=0;t{let r=t.Z.name;return(r=(r=r.startsWith("MPP:")&&!E.has(r)?"MPP":r).startsWith("SPP:")&&!E.has(r)?"SPP":r).startsWith("SPP_DAG:")&&!E.has(r)&&(r="SPP_DAG"),0{var e=t.startsWith("MARK")||t.startsWith("POLY"),i=r.startsWith("MARK")||r.startsWith("POLY");return e!==i?et.g()))}tt(){var r=new Map;for(let t=0;t!(e instanceof t))){var s=t,l=r;if(s.length!==l.length)return!1;for(let t=0;t{performance.now(){this.Xt="idle",this.Zt=-1/0,this.yt()},e)}}class i{constructor(t){this.Mt=t}subscribe(t){return this.Mt(t)}static of(...e){return new i(t=>{for(var r of e)t(r);return()=>{}})}It(){let r=[];return this.subscribe(t=>r.push(t))(),r}map(e){return new i(r=>this.subscribe(t=>r(e(t))))}filter(e){return new i(r=>this.subscribe(t=>{e(t)&&r(t)}))}bt(s,l){return new i(r=>{let e=!1,i=!1,o,a,t=this.subscribe(t=>{o=t,e=!0,i&&r(l(o,a))}),n=s.subscribe(t=>{a=t,i=!0,e&&r(l(o,a))});return()CRUMBLE_PART"); result.append(R"CRUMBLE_PART()=>{t(),n()}})}static At(){return new i(t=>{let r,e=!1;return(r=()=>{e||(t(void 0),window.requestAnimationFrame(r))})(),()=>{e=!0}})}kt(){return new i(e=>{let i=()=>{},o=!1,t=this.subscribe(t=>{var r;o||(r=i,i=t.subscribe(e),r())});return()=>{o=!0,i(),t()}})}_t(r){return this.map(t=>(r(t),t))}St(){return new i(r=>{let e=[];return e.push(this.subscribe(t=>e.push(t.subscribe(r)))),()=>{for(var t of e)t()}})}Et(a){return new i(t=>{let r=void 0,e=!1,i=new it(()=>{e||t(r)},a),o=this.subscribe(t=>{r=t,i.yt()});return()=>{e=!0,o()}})}static gt(r,e){return new i(t=>(r.addEventListener(e,t),()=>r.removeEventListener(e,t)))}Ct(t){return new i(r=>{let e=t;return this.subscribe(t=>{0t===r);return new i(r=>{let e=!1,i=void 0;return this.subscribe(t=>{e&&o(i,t)||(i=t,e=!0,r(t))})})}}class ot{constructor(){this.Ot=[],this.Tt=new i(t=>{this.Ot.push(t);let r=!1;return()=>{r||(r=!0,this.Ot.splice(this.Ot.indexOf(t),1))}})}Dt(){return this.Tt}send(t){for(var r of this.Ot)r(t)}}class)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART( at{constructor(t=void 0){this.xt=t,this.Gt=new ot,this.Tt=new i(t=>(t(this.xt),this.Gt.Dt().subscribe(t)))}Dt(){return this.Tt}set(t){this.xt=t,this.Gt.send(t)}get(){return this.xt}}class r{constructor(t,r,e){if(r<0||r>=t.length)throw new Error("Bad index: "+{history:t,index:r,Lt:e});if(!Array.isArray(t))throw new Error("Bad history: "+{history:t,index:r,Lt:e});this.history=t,this.index=r,this.Lt=e,this.Qt=new ot,this.Ft=new at(this.history[this.index])}Nt(){return this.Qt.Dt()}Kt(){return this.Ft.Dt()}Ut(){return this.Ft.get()}static Ht(t){return new r([t],0,!1)}zt(){return 0===this.index&&!this.Lt}$t(){return this.index===this.history.length-1}clear(t){this.history=[t],this.index=0,this.Lt=!1,this.Ft.set(t),this.Qt.send(t)}Wt(){this.Lt=!0,this.Qt.send(void 0)}qt(){this.Lt=!1;var t=this.history[this.index];return this.Ft.set(t),this.Qt.send(t),t}commit(t){t===this.history[this.index]?this.qt():(this.Lt=!1,this.index+=1,this.history.splice(this.index,this.history.length-this.index),this.history.push(t),this.)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART( at{constructor(t=void 0){this.xt=t,this.Gt=new ot,this.Tt=new i(t=>(t(this.xt),this.Gt.Dt().subscribe(t)))}Dt(){return this.Tt}set(t){this.xt=t,this.Gt.send(t)}get(){return this.xt}}class r{constructor(t,r,e){if(r<0||r>=t.length)throw new Error("Bad index: "+{history:t,index:r,Lt:e});if(!Array.isArray(t))throw new Error("Bad history: "+{history:t,index:r,Lt:e});this.history=t,this.index=r,this.Lt=e,this.Qt=new ot,this.Ft=new at(this.history[this.index])}Nt(){return this.Qt.Dt()}Kt(){return this.Ft.Dt()}Ht(){return this.Ft.get()}static Ut(t){return new r([t],0,!1)}zt(){return 0===this.index&&!this.Lt}$t(){return this.index===this.history.length-1}clear(t){this.history=[t],this.index=0,this.Lt=!1,this.Ft.set(t),this.Qt.send(t)}Wt(){this.Lt=!0,this.Qt.send(void 0)}qt(){this.Lt=!1;var t=this.history[this.index];return this.Ft.set(t),this.Qt.send(t),t}commit(t){t===this.history[this.index]?this.qt():(this.Lt=!1,this.index+=1,this.history.splice(this.index,this.history.length-this.index),this.history.push(t),this.)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(Ft.set(t),this.Qt.send(t))}Bt(){if(!this.Lt){if(0===this.index)return;--this.index}this.Lt=!1;var t=this.history[this.index];return this.Ft.set(t),this.Qt.send(t),t}jt(){var t;if(this.index+1!==this.history.length)return this.index+=1,this.Lt=!1,t=this.history[this.index],this.Ft.set(t),this.Qt.send(t),t}toString(){return"Revision("+A({index:this.index,count:this.history.length,Vt:this.Lt,head:this.history[this.index]})+")"}rt(t){return t instanceof r&&this.index===t.index&&this.Lt===t.Lt&&X(this.history,t.history)}}let lt=32;function ht(i,o,a,n){var t,r=i.canvas.width/2,e=o.Jt();e.sort((t,r)=>{var[t,e]=n(t),[r,i]=n(r);return e!==i?e-i:t-r});let s=new Map,l=void 0,h=0,f=0,c=0,v=0;for(t of e){var[D,d]=n(t);f+=lt,l!==d?(l=d,h=1.5*r,c=Math.max(c,v),v=0,f+=.25*lt):(h+=.25*nt,v++),s.set(D+","+d,[h,f])}let w=lt+nt*c*.25,u=(t,r)=>{var[t,e]=n(t);return[t,e,r]=[[t,e,r]][0],t=t+","+e,s.has(t)?([e,t]=s.get(t),[e+(r-o.tr)*w,t]):[void 0,void 0]};var X=Math.floor(i.canvas.width/4/w),Z=Math.max(0,o.tr-X+1),Y=o.tr+X+2;i.save)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(();try{i.clearRect(r,0,r,i.canvas.height);for(let t=0;tu(t,r),J=o.lr.N[r];if(void 0!==J)for(var tt of J.L())tt.I(V,i)}i.globalAlpha=.25,i.setLineDash([1,1]);let t=0;for(var rt of e)t=Math.max(t,n(rt)[0]);for(var et of e){var[it,ot]=u(et,Z-1),[,at]=n(et);i.beginPath(),i.moveTo(it,ot),i.lineTo(t+.25,at),i.stroke()}}finally{i.restore()}}class ft{constructor(t,r,e){this.er=t,this.ir=r,this.sr=e}}class ct{constructor(t){this.hr=t}rr(t){let r=this.hr.get(t);return r=void 0===r?new ft(new Map,new Set,[]):r}static cr(r,e){var i=new ct(new Map);let o=new Map;for(let t=0;t[t*d-H,r*d-z],F=t=>{var r=L.F[2*t],t=L.F[2*t+1];return Q(r,t)},N=[];for(let t=0;t{x.fillStyle="white",x.clearRect(0,0,x.canvas.width,x.canvas.height);var[t,r]=vt(G.vr,G.dr);let e=G.tr;for(let t=0;t<=G.tr;t++)for(var D of L.N[t].S)if("POLYGON"===D.Z.name){e=t;break}var i,o,a=[...L.N[e].S];a.sort((t,r)=>r.R.length-t.R.length);for(i of a)"POLYGON"===i.Z.name&&i.I(F,x);$(x,()=>{L.U();x.strokeStyle="black";for(let r=0;r<100;r+=.5)for(let t=r%1;t<100;t+=1){var[e,i]=Q(r,t),o=(r%1==.5?x.fillStyle="pink":x.fillStyle="white",!K.has(r+","+t));o&&(x.globalAlpha*=.25),x.fillRect(e-nt,i-nt,2*nt,2*nt),x.strokeRect(e-nt,i-nt,2*nt,2*nt),o&&(x.globalAlpha*=4)}});for(let t=0;t{)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(x.globalAlpha*=.25;for(var[t,r]of G.wr.values()){var[t,r]=Q(t,r);x.fillStyle="yellow",x.fillRect(t-1.25*nt,r-1.25*nt,2.5*nt,2.5*nt)}}),$(x,()=>{x.globalAlpha*=.5;for(var[t,r]of G.ur.values()){var[t,r]=Q(t,r);x.fillStyle="blue",x.fillRect(t-1.25*nt,r-1.25*nt,2.5*nt,2.5*nt)}});for(let t=0;t"X"===t[0])?Y.fillStyle="red":k.every(t=>"Y"===t[0])?Y.fillStyle="green":k.every(t=>"Z"===t[0])?Y.fillStyle="blue":Y.fillStyle="black",Y.strokeStyle=Y.fillStyle;var _,S,E=k.map(t=>t[1]);let n=0,s=0;for([_,S]of E)n+=_,s+=S;n/=E.length,s/=E.length,E.sort((t,r)=>{var[t,e]=t,[r,i]=r;let o=Math.atan2(e-s,t-n),a=Math.atan2(i-s,r-n);return t===n&&e===s&&(o=-100),r===n&&i===s&&(a=-100),o-a}),W(Y,E),Y.globalAlpha*=.25,Y.fill(),Y.globalAlpha*=4,Y.lineWidth=3,Y.stroke(),Y.lineWidth=1}if(I<4)for(var[g,[C,P]]of k){if("X"===g)Y.fillStyle)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(="red";else if("Y"===g)Y.fillStyle="green";else{if("Z"!==g)throw new Error("Not a pauli: "+g);Y.fillStyle="blue"}Y.fillRect(C-m,P-p,b,A)}for(Z of M.rr(R.tr).ir){var[O,T]=y(Z);Y.fillStyle="magenta",Y.fillRect(O-m-8,T-p-8,b+16,A+16),Y.fillStyle="black",Y.fillRect(O-m,T-p,b,A)}}void 0!==t&&(x.save(),x.globalAlpha*=.5,[a,t]=Q(t,r),x.fillStyle="red",x.fillRect(a-nt,t-nt,2*nt,2*nt),x.restore()),$(x,()=>{var t,r,e,i,o,a;x.globalAlpha*=.25,x.fillStyle="blue",void 0!==G.Xr&&void 0!==G.vr&&(t=Math.min(G.vr,G.Xr),r=Math.max(G.vr,G.Xr),e=Math.min(G.dr,G.Zr),i=Math.max(G.dr,G.Zr),--t,r+=1,--e,i+=1,t-=H,r-=H,e-=z,i-=z,x.fillRect(t,e,r-t,i-e));for([o,a]of G.Yr){var[n,s]=Q(o,a);x.fillRect(n-nt,s-nt,2*nt,2*nt)}})}),x.save();try{x.strokeStyle="black";for(let r=0;r0===t.rr(r).ir.size);r===G.tr?x.fillStyle="red":(n&&(x.fillStyle="magenta",x.fillRect(-2,5*r-2,14,8)),x.fillStyle="black"),x.fillRect(0,5*r,10,4)}}finally{x.restore()}ht(x,G,N,F)}class ut{constructor(t,r,e,i,o,a,n,s,l){for(this.lr=t.)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(g(),this.tr=r,this.ur=new Map(e.entries()),this.wr=new Map(i.entries()),this.vr=o,this.dr=a,this.Xr=n,this.Zr=s,this.Yr=[...l];this.lr.N.length<=this.tr;)this.lr.N.push(new b)}Rr(){return this.lr.U()}Jt(){let r=this.Rr();var t=[];if(0r.has(t))}}function Xt(r){let e=[1,0],i=[0,1];var o=(t,r)=>[t-r,t+r];r=(r%8+8)%8;for(let t=0;t[e[0]*t+i[0]*r,e[1]*t+i[1]*r]}function Zt(){try{return window.self===window.top}catch(t){}}class Yt{constructor(){this.yr=!1,this.Mr=void 0}Ir(){this.Mr={mr:!0}}pr(t){this.Mr=t}br(){this.Mr=void 0}Ar(t,r){if(!r.startsWith("#"))throw new Error('"Expected a hash URL: '+{kr:t,_r:r});if(Zt()&&this.Mr!==t)if(this.yr)document.location.hash=r;else try{void 0===this.Mr?history.replaceState(t,"",r):(history.pushState(t,"",r),this.Mr=void 0)}catch(t){console.warn("Calling 'history.replaceState/pushState' f)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(,R.lineWidth=1}}}i.globalAlpha*=.5,i.fillStyle="black",i.fillRect(1.5*r-1.3*nt,0,w,i.canvas.height),i.globalAlpha*=2,i.strokeStyle="black",i.fillStyle="black";for(var K of e){var[H,U]=u(K,Z-1),[z,$]=u(K,Y+1);i.beginPath(),i.moveTo(H,U),i.lineTo(z,$),i.stroke()}i.textAlign="right",i.textBaseline="middle";for(var T of e){var[W,q]=u(T,Z-1),B=o.lr.F[2*T],j=o.lr.F[2*T+1];i.fillText(B+`,${j}:`,W,q)}for(let r=Z;r<=Y;r++){var V=t=>u(t,r),J=o.lr.N[r];if(void 0!==J)for(var tt of J.L())tt.I(V,i)}i.globalAlpha=.25,i.setLineDash([1,1]);let t=0;for(var rt of e)t=Math.max(t,n(rt)[0]);for(var et of e){var[it,ot]=u(et,Z-1),[,at]=n(et);i.beginPath(),i.moveTo(it,ot),i.lineTo(t+.25,at),i.stroke()}}finally{i.restore()}}class ft{constructor(t,r,e){this.er=t,this.ir=r,this.sr=e}}class ct{constructor(t){this.hr=t}rr(t){let r=this.hr.get(t);return r=void 0===r?new ft(new Map,new Set,[]):r}static cr(r,e){var i=new ct(new Map);let o=new Map;for(let t=0;t[t*d-U,r*d-z],F=t=>{var r=L.F[2*t],t=L.F[2*t+1];return Q(r,t)},N=[];for(let t=0;t{x.fillStyle="white",x.clearRect(0,0,x.canvas.width,x.canvas.height);var[t,r]=vt(G.vr,G.dr);let e=G.tr;for(let t=0;t<=G.tr;t++)for(var D of L.N[t].S)if("POLYGON"===D.Z.name){e=t;break}var i,o,a=[...L.N[e].S];a.sort((t,r)=>r.R.length-t.R.length);for(i of a)"POLYGON"===i.Z.name&&i.I(F,x);$(x,()=>{L.H();x.strokeStyle="black";for(let r=0;r<100;r+=.5)for(let t=r%1;t<100;t+=1){var[e,i]=Q(r,t),o=(r%1==.5?x.fillStyle="pink":x.fillStyle="white",!K.has(r+","+t));o&&(x.globalAlpha*=.25),x.fillRect(e-nt,i-nt,2*nt,2*nt),x.strokeRect(e-nt,i-nt,2*nt,2*nt),o&&(x.globalAlpha*=4)}});for(let t=0;t{)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(x.globalAlpha*=.25;for(var[t,r]of G.wr.values()){var[t,r]=Q(t,r);x.fillStyle="yellow",x.fillRect(t-1.25*nt,r-1.25*nt,2.5*nt,2.5*nt)}}),$(x,()=>{x.globalAlpha*=.5;for(var[t,r]of G.ur.values()){var[t,r]=Q(t,r);x.fillStyle="blue",x.fillRect(t-1.25*nt,r-1.25*nt,2.5*nt,2.5*nt)}});for(let t=0;t"X"===t[0])?Y.fillStyle="red":k.every(t=>"Y"===t[0])?Y.fillStyle="green":k.every(t=>"Z"===t[0])?Y.fillStyle="blue":Y.fillStyle="black",Y.strokeStyle=Y.fillStyle;var _,S,E=k.map(t=>t[1]);let n=0,s=0;for([_,S]of E)n+=_,s+=S;n/=E.length,s/=E.length,E.sort((t,r)=>{var[t,e]=t,[r,i]=r;let o=Math.atan2(e-s,t-n),a=Math.atan2(i-s,r-n);return t===n&&e===s&&(o=-100),r===n&&i===s&&(a=-100),o-a}),W(Y,E),Y.globalAlpha*=.25,Y.fill(),Y.globalAlpha*=4,Y.lineWidth=3,Y.stroke(),Y.lineWidth=1}if(I<4)for(var[g,[C,P]]of k){if("X"===g)Y.fillStyle)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(="red";else if("Y"===g)Y.fillStyle="green";else{if("Z"!==g)throw new Error("Not a pauli: "+g);Y.fillStyle="blue"}Y.fillRect(C-m,P-p,b,A)}for(Z of M.rr(R.tr).ir){var[O,T]=y(Z);Y.fillStyle="magenta",Y.fillRect(O-m-8,T-p-8,b+16,A+16),Y.fillStyle="black",Y.fillRect(O-m,T-p,b,A)}}void 0!==t&&(x.save(),x.globalAlpha*=.5,[a,t]=Q(t,r),x.fillStyle="red",x.fillRect(a-nt,t-nt,2*nt,2*nt),x.restore()),$(x,()=>{var t,r,e,i,o,a;x.globalAlpha*=.25,x.fillStyle="blue",void 0!==G.Xr&&void 0!==G.vr&&(t=Math.min(G.vr,G.Xr),r=Math.max(G.vr,G.Xr),e=Math.min(G.dr,G.Zr),i=Math.max(G.dr,G.Zr),--t,r+=1,--e,i+=1,t-=U,r-=U,e-=z,i-=z,x.fillRect(t,e,r-t,i-e));for([o,a]of G.Yr){var[n,s]=Q(o,a);x.fillRect(n-nt,s-nt,2*nt,2*nt)}})}),x.save();try{x.strokeStyle="black";for(let r=0;r0===t.rr(r).ir.size);r===G.tr?x.fillStyle="red":(n&&(x.fillStyle="magenta",x.fillRect(-2,5*r-2,14,8)),x.fillStyle="black"),x.fillRect(0,5*r,10,4)}}finally{x.restore()}ht(x,G,N,F)}class ut{constructor(t,r,e,i,o,a,n,s,l){for(this.lr=t.)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(g(),this.tr=r,this.ur=new Map(e.entries()),this.wr=new Map(i.entries()),this.vr=o,this.dr=a,this.Xr=n,this.Zr=s,this.Yr=[...l];this.lr.N.length<=this.tr;)this.lr.N.push(new b)}Rr(){return this.lr.H()}Jt(){let r=this.Rr();var t=[];if(0r.has(t))}}function Xt(r){let e=[1,0],i=[0,1];var o=(t,r)=>[t-r,t+r];r=(r%8+8)%8;for(let t=0;t[e[0]*t+i[0]*r,e[1]*t+i[1]*r]}function Zt(){try{return window.self===window.top}catch(t){}}class Yt{constructor(){this.yr=!1,this.Mr=void 0}Ir(){this.Mr={mr:!0}}pr(t){this.Mr=t}br(){this.Mr=void 0}Ar(t,r){if(!r.startsWith("#"))throw new Error('"Expected a hash URL: '+{kr:t,_r:r});if(Zt()&&this.Mr!==t)if(this.yr)document.location.hash=r;else try{void 0===this.Mr?history.replaceState(t,"",r):(history.pushState(t,"",r),this.Mr=void 0)}catch(t){console.warn("Calling 'history.replaceState/pushState' f)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(ailed. Falling back to setting location.hash.",t),this.yr=!0,document.location.hash=r}}}function Rt(t){return"#circuit="+(t=-1===(t=t.replaceAll("QUBIT_COORDS","Q").replaceAll(", ",",").replaceAll(") ",")").replaceAll(" ","_").replaceAll("\n",";")).indexOf("%")&&-1===t.indexOf("&")?t:encodeURIComponent(t))}let c=document.getElementById("toolbox"),Y=10.5,R=["H","S","R","M","C","W","SC","MC","P","1","2","3","4"],yt=[1,2,2,2,1,2,2,2,-1,-1,-1,-1,-1];let Mt=function(){var r=new Map([["0,0",E.get("H_YZ")],["0,1",E.get("H")],["0,2",E.get("H_XY")],["1,0",E.get("SQRT_X")],["1,1",E.get("SQRT_Y")],["1,2",E.get("S")],["2,0",E.get("RX")],["2,1",E.get("RY")],["2,2",E.get("R")],["3,0",E.get("MX")],["3,1",E.get("MY")],["3,2",E.get("M")],["4,0",E.get("CX")],["4,1",E.get("CY")],["4,2",E.get("CZ")],["5,0",E.get("CXSWAP")],["5,1",E.get("SWAP")],["5,2",E.get("CZSWAP")],["6,0",E.get("SQRT_XX")],["6,1",E.get("SQRT_YY")],["6,2",E.get("SQRT_ZZ")],["7,0",E.get("MXX")],["7,1",E.get("MYY")],["7,2",E.get("MZZ")]]);let e=9;for(let t=0;t<4)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(;t++)r.set(e+",0",E.get("MARKX").X(t)),r.set(e+",1",E.get("MARKY").X(t)),r.set(e+",2",E.get("MARKZ").X(t)),r.set(e+",-1",E.get("MARK").X(t)),e+=1;return r}();function It(t){var r,e=function(t){var r,e;if(!t.ctrlKey)return r=+t.it.has("x"),e=+t.it.has("y"),t=+t.it.has("z"),r&&!e&&!t||!r&&e&&t?{Sr:0,strength:Math.max(r,Math.min(e,t))}:!r&&e&&!t||r&&!e&&t?{Sr:1,strength:Math.max(e,Math.min(r,t))}:!r&&!e&&t||r&&e&&!t?{Sr:2,strength:Math.max(t,Math.min(r,e))}:void 0}(t),t=function(i){if(!i.ctrlKey){let e=void 0;for(let r=0;r=e.strength)&&(e={Er:r,strength:t/R[r].length})}return e}}(t);let i=e,o=(void 0!==t&&void 0===e&&(r=yt[t.Er],i=void 0===r?void 0:{strength:0,Sr:r}),void 0);return void 0!==i&&void 0!==t&&(r=t.Er+","+i.Sr,Mt.has(r))&&(o=Mt.get(r)),{gr:e,Cr:i,Pr:t,Or:o}}const e=-d+Math.floor(d/4)+.5,o=-d+Math.floor(d/4)+.5;var t=document.getElementById("btnUndo"),mt=document.getElementById("btnRedo")CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(),pt=document.getElementById("btnClearMarkers");const bt=document.getElementById("btnShowHideImportExport");var At=document.getElementById("btnNextLayer"),kt=document.getElementById("btnPrevLayer"),_t=document.getElementById("btnRotate45"),St=document.getElementById("btnRotate45Counter"),M=document.getElementById("btnExport"),Et=document.getElementById("btnImport"),gt=document.getElementById("clear");const I=document.getElementById("txtStimCircuit");var Ct=document.getElementById("btnTimelineFocus"),Pt=document.getElementById("btnClearTimelineFocus"),Ot=document.getElementById("btnClearSelectedMarkers");I.addEventListener("keyup",t=>t.stopPropagation()),I.addEventListener("keydown",t=>t.stopPropagation());let m=new class{constructor(t){this.rev=r.Ht(""),this.canvas=t,this.dr=void 0,this.vr=void 0,this.Tr=new et,this.tr=0,this.ur=new Map,this.wr=new Map,this.Xr=void 0,this.Zr=void 0,this.Dr=new at(this.Gr(void 0))}Lr(){for(var t=u.K(this.rev.Ut());t.N.length<=this.tr;)t.N.push(new b);return t}Qr(){this.ur.clea)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(r(),this.Fr()}Nr(t){var r,e=this.Lr(),i=e.tt();for(r of this.ur.keys()){var o=i.get(r);void 0!==o&&e.N[this.tr].D(o)}this.Kr(e,t)}Ur(t){var r=this.Lr();r.N.splice(this.tr,1),this.Kr(r,t)}Hr(t){var r=this.Lr();r.N.splice(this.tr,0,new b),this.Kr(r,t)}Bt(){this.rev.Bt()}jt(){this.rev.jt()}Kr(t,r){r?this.zr(t):this.commit(t)}commit(t){for(;0"MARKX"!==t.Z.name&&"MARKY"!==t.Z.name&&"MARKZ"!==t.Z.name);this.commit(r)}$r(i){var t=this.vr,r=this.dr,o=this.Xr,a=this.Zr,n=[];if(void 0!==t&&void 0!==o){var[s,l]=vt(o,a),h=Math.min(t,o),f=Math.max(t,o),c=Math.min(r,a),v=Math.max(r,a),t=d/4-nt;h+=t,f-=t,c+=t,v-=t,h=Math.floor()CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(),pt=document.getElementById("btnClearMarkers");const bt=document.getElementById("btnShowHideImportExport");var At=document.getElementById("btnNextLayer"),kt=document.getElementById("btnPrevLayer"),_t=document.getElementById("btnRotate45"),St=document.getElementById("btnRotate45Counter"),M=document.getElementById("btnExport"),Et=document.getElementById("btnImport"),gt=document.getElementById("clear");const I=document.getElementById("txtStimCircuit");var Ct=document.getElementById("btnTimelineFocus"),Pt=document.getElementById("btnClearTimelineFocus"),Ot=document.getElementById("btnClearSelectedMarkers");I.addEventListener("keyup",t=>t.stopPropagation()),I.addEventListener("keydown",t=>t.stopPropagation());let m=new class{constructor(t){this.rev=r.Ut(""),this.canvas=t,this.dr=void 0,this.vr=void 0,this.Tr=new et,this.tr=0,this.ur=new Map,this.wr=new Map,this.Xr=void 0,this.Zr=void 0,this.Dr=new at(this.Gr(void 0))}Lr(){for(var t=u.K(this.rev.Ht());t.N.length<=this.tr;)t.N.push(new b);return t}Qr(){this.ur.clea)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(r(),this.Fr()}Nr(t){var r,e=this.Lr(),i=e.tt();for(r of this.ur.keys()){var o=i.get(r);void 0!==o&&e.N[this.tr].D(o)}this.Kr(e,t)}Hr(t){var r=this.Lr();r.N.splice(this.tr,1),this.Kr(r,t)}Ur(t){var r=this.Lr();r.N.splice(this.tr,0,new b),this.Kr(r,t)}Bt(){this.rev.Bt()}jt(){this.rev.jt()}Kr(t,r){r?this.zr(t):this.commit(t)}commit(t){for(;0"MARKX"!==t.Z.name&&"MARKY"!==t.Z.name&&"MARKZ"!==t.Z.name);this.commit(r)}$r(i){var t=this.vr,r=this.dr,o=this.Xr,a=this.Zr,n=[];if(void 0!==t&&void 0!==o){var[s,l]=vt(o,a),h=Math.min(t,o),f=Math.max(t,o),c=Math.min(r,a),v=Math.max(r,a),t=d/4-nt;h+=t,f-=t,c+=t,v-=t,h=Math.floor()CRUMBLE_PART"); result.append(R"CRUMBLE_PART(2*h/d+.5)/2,f=Math.floor(2*f/d+.5)/2,c=Math.floor(2*c/d+.5)/2,v=Math.floor(2*v/d+.5)/2;let e=1;h!=f&&c!=v||(e=2);for(let r=h;r<=f;r+=.5)for(let t=c;t<=v;t+=.5)r%1!=t%1||i&&(s%e!=r%e||l%e!=t%e)||n.push([r,t])}return n}Br(o,t){let r=this.Lr();var e;r=r.$(o),t||(this.wr=(e=t=>{var r,e,i=new Map;for([r,e]of t.values())[r,e]=o(r,e),i.set(r+","+e,[r,e]);return i})(this.wr),this.ur=e(this.ur)),this.Kr(r,t)}jr(t,r){let e=Xt(t),i=this.Lr().$(e).W();this.Br((t,r)=>([t,r]=e(t,r),i(t,r)),r)}Vr(t){this.tr=Math.max(t,0),this.Fr()}Jr(t,r,e){r||e||this.ur.clear();for(var[i,o]of t){var a=i+","+o;e&&this.ur.has(a)?this.ur.delete(a):this.ur.set(a,[i,o])}this.Fr()}te(t){var r,e=new Map,i=this.Lr().N[this.tr];for(r of[...t]){var o=i._.get(r);if(void 0!==o)if("RX"===o.Z.name||"MX"===o.Z.name||"MRX"===o.Z.name)e.set(r,"X");else if("RY"===o.Z.name||"MY"===o.Z.name||"MRY"===o.Z.name)e.set(r,"Y");else if("R"===o.Z.name||"M"===o.Z.name||"MR"===o.Z.name)e.set(r,"Z");else if("MXX"===o.Z.name||"MYY"===o.Z.name||"MZZ"===o.Z.name){var a,n=o)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(.Z.name[1];for(a of o.R)e.set(a,n)}else if(o.Z.name.startsWith("MPP:")&&void 0===o.Z.l&&o.R.length===o.Z.name.length-1){var s=o.Z.name.substring(4);for(let t=0;t{var[t,e]=t,[r,i]=r;return Math.atan2(e-a,t-o)-Math.atan2(i-a,r-o)});var s=this.Lr().J(this.ur.values()),l=s.tt(),h=new Uint32Array(this.ur.size);for(let t=0;t{Tt()}),Et.addEventListener("click",t=>{var r=I.value,r=u.K(r.replaceAll("\n#!pragma ","\n"));m.commit(r)}),bt.addEventListener("click",t=>{var r=document.getElementById("divImportExport");"none"===r.style.display?(r.style.display="block",bt.textContent="Hide Import/Export",Tt()):(r.style.display="none",bt.textContent="Show Import/Export",I.value=""),setTimeout(()=>{window.scrollTo(0,0)},0)}),gt.addEventListener("click",t=>{m.Wr()}),t.addEventListener("click",t=>{m.Bt()}),Ct.addEventListener("click",t=>{m.wr=new Map(m.ur.entries()),m.Fr()}),Ot.addEventListener("click",t=>{m.ee(!1),m.Fr()}),Pt.addEventListener("click",t=>{m.wr=new Map,m.Fr()}),mt.addEventListener("click",t=>{m.jt()}),pt.addEventListener("click",t=>{m.qr()}),_t.addEventListener("click",t=>{m.jr(1,!1)}),St.addEventListener("click",t=>{m.jr(-1,!1)}),At.addEventListener("click)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(",t=>{m.Vr(m.tr+1)}),kt.addEventListener("click",t=>{m.Vr(m.tr-1)}),window.addEventListener("resize",t=>{m.canvas.width=m.canvas.scrollWidth,m.canvas.height=m.canvas.scrollHeight,m.Fr()}),m.canvas.addEventListener("mousemove",t=>{m.vr=t.offsetX+e,m.dr=t.offsetY+o,m.Xr-e<10&&m.vr-e<10&&1===t.buttons?(m.Vr(Math.floor(t.offsetY/5)),t.preventDefault()):m.Fr()}),m.canvas.addEventListener("mousedown",t=>{m.vr=t.offsetX+e,m.dr=t.offsetY+o,m.Xr=t.offsetX+e,m.Zr=t.offsetY+o,m.Xr-e<10&&1===t.buttons?m.Vr(Math.floor(t.offsetY/5)):m.Fr()}),m.canvas.addEventListener("mouseup",t=>{var r=m.$r(t.altKey);m.Xr=void 0,m.Zr=void 0,m.vr=t.offsetX+e,m.dr=t.offsetY+o,m.Jr(r,t.shiftKey,t.ctrlKey)});let Dt=void 0;async function xt(){let e=m.Lr();e.N=[e.N[m.tr]],0{var r=e.F[2*t],t=e.F[2*t+1];return m.ur.has(r+","+t)}),[r,t]=y(m.ur.values()),e=e.B(-r,-t));var t,r=e.j();Dt=r;try{await navigator.clipboard.writeText(r)}catch(t){console.warn("Failed to write to clipboard. Using fallback emulated clipboard.",)CRUMBLE_PART"); - result.append(R"CRUMBLE_PART(t)}}async function Gt(e){let i;try{i=await navigator.clipboard.readText()}catch(t){console.warn("Failed to read from clipboard. Using fallback emulated clipboard.",t),i=Dt}if(void 0!==i){let r=u.K(i);if(1!==r.N.length)throw new Error(i);let t=m.Lr();0m.jr(-1,t)),o.set("t",t=>m.jr(1,t)),o.set("escape",()=>m.Qr),o.set("delete",t=>m.Nr(t)),o.set("backspace",t=>m.Nr(t)),o.set("ctrl+delete",t=>m.Ur(t)),o.set("ctrl+insert",t=>m.Hr(t)),o.set("ctrl+backspace",()=>m.Ur),o.set("ctrl+z",t=>{t||m.Bt()}),o.set("ctrl+y",t=>{t||m.jt()}),o.set("ctrl+shift+z",t=>{t||m.jt()}),o.set("ctrl+c",async t=>{await xt()}),o.se)CRUMBLE_PART"); + result.append(R"CRUMBLE_PART(t)}}async function Gt(e){let i;try{i=await navigator.clipboard.readText()}catch(t){console.warn("Failed to read from clipboard. Using fallback emulated clipboard.",t),i=Dt}if(void 0!==i){let r=u.K(i);if(1!==r.N.length)throw new Error(i);let t=m.Lr();0m.jr(-1,t)),o.set("t",t=>m.jr(1,t)),o.set("escape",()=>m.Qr),o.set("delete",t=>m.Nr(t)),o.set("backspace",t=>m.Nr(t)),o.set("ctrl+delete",t=>m.Hr(t)),o.set("ctrl+insert",t=>m.Ur(t)),o.set("ctrl+backspace",()=>m.Hr),o.set("ctrl+z",t=>{t||m.Bt()}),o.set("ctrl+y",t=>{t||m.jt()}),o.set("ctrl+shift+z",t=>{t||m.jt()}),o.set("ctrl+c",async t=>{await xt()}),o.se)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(t("ctrl+v",Gt),o.set("ctrl+x",async t=>{await xt(),m.Nr(t)}),o.set("l",t=>{t||(m.wr=new Map(m.ur.entries()),m.Fr())}),o.set(" ",t=>m.ee(t));for(let[t,r]of[["1",0],["2",1],["3",2],["4",3],["5",4],["6",5],["7",6],["8",7],["9",8],["0",9],["-",10],["=",11],["\\",12],["`",13]])o.set(""+t,t=>m.re(t,r)),o.set(t+"+x",t=>m.ne(t,E.get("MARKX").X(r))),o.set(t+"+y",t=>m.ne(t,E.get("MARKY").X(r))),o.set(t+"+z",t=>m.ne(t,E.get("MARKZ").X(r)));function a(t,r,e=void 0){for(var i of t){if(o.has(i))throw new Error("Chord collision: "+i);o.set(i,t=>m.ne(t,E.get(r)))}void 0!==e&&a(t.map(t=>"shift+"+t),e)}return o.set("p",t=>m.ne(t,E.get("POLYGON"),[1,0,0,.5])),o.set("alt+p",t=>m.ne(t,E.get("POLYGON"),[0,1,0,.5])),o.set("shift+p",t=>m.ne(t,E.get("POLYGON"),[0,0,1,.5])),o.set("p+x",t=>m.ne(t,E.get("POLYGON"),[1,0,0,.5])),o.set("p+y",t=>m.ne(t,E.get("POLYGON"),[0,1,0,.5])),o.set("p+z",t=>m.ne(t,E.get("POLYGON"),[0,0,1,.5])),o.set("p+x+y",t=>m.ne(t,E.get("POLYGON"),[1,1,0,.5])),o.set("p+x+z",t=>m.ne(t,E.get("POLYGON"),[1,0,1,.5])),o)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(.set("p+y+z",t=>m.ne(t,E.get("POLYGON"),[0,1,1,.5])),o.set("p+x+y+z",t=>m.ne(t,E.get("POLYGON"),[1,1,1,.5])),a(["h","h+y","h+x+z"],"H","H"),a(["h+z","h+x+y"],"H_XY","H_XY"),a(["h+x","h+y+z"],"H_YZ","H_YZ"),a(["s+x","s+y+z"],"SQRT_X","SQRT_X_DAG"),a(["s+y","s+x+z"],"SQRT_Y","SQRT_Y_DAG"),a(["s","s+z","s+x+y"],"S","S_DAG"),a(["r+x","r+y+z"],"RX"),a(["r+y","r+x+z"],"RY"),a(["r","r+z","r+x+y"],"R"),a(["m+x","m+y+z"],"MX"),a(["m+y","m+x+z"],"MY"),a(["m","m+z","m+x+y"],"M"),a(["m+r+x","m+r+y+z"],"MRX"),a(["m+r+y","m+r+x+z"],"MRY"),a(["m+r","m+r+z","m+r+x+y"],"MR"),a(["c"],"CX","CX"),a(["c+x"],"CX","CX"),a(["c+y"],"CY","CY"),a(["c+z"],"CZ","CZ"),a(["c+x+y"],"XCY","XCY"),a(["alt+c+x"],"XCX","XCX"),a(["alt+c+y"],"YCY","YCY"),a(["w"],"SWAP","SWAP"),a(["w+x"],"CXSWAP",void 0),a(["c+w+x"],"CXSWAP",void 0),a(["i+w"],"ISWAP","ISWAP_DAG"),a(["w+z"],"CZSWAP",void 0),a(["c+w+z"],"CZSWAP",void 0),a(["c+w"],"CZSWAP",void 0),a(["f"],"C_XYZ","C_ZYX"),a(["c+s+x"],"SQRT_XX","SQRT_XX_DAG"),a(["c+s+y"],"SQRT_YY","SQRT_YY_DAG"),a(["c+)CRUMBLE_PART"); result.append(R"CRUMBLE_PART(s+z"],"SQRT_ZZ","SQRT_ZZ_DAG"),a(["c+s"],"SQRT_ZZ","SQRT_ZZ_DAG"),a(["c+m+x"],"MXX","MXX"),a(["c+m+y"],"MYY","MYY"),a(["c+m+z"],"MZZ","MZZ"),a(["c+m"],"MZZ","MZZ"),o}();function Qt(r){if(m.Tr.vt(r),"keydown"===r.type){if("q"===r.key)return void m.Vr(m.tr-1);if("e"===r.key)return void m.Vr(m.tr+1)}var t=m.Tr.lt;if(0!==t.length){for(var e=t[t.length-1];0{m.Dr.set(m.Gr(void 0));var t=m.Tr.ht(!1),e=(c.width=c.scrollWidth,c.height=c.scrollHeight,c.getContext("2d"));e.clearRect(0,0,c.width,c.height),e.textAlign="right",e.textBaseline="middle",e.fillText("X",7.5,)CRUMBLE_PART"); diff --git a/src/stim/util_top/export_crumble_url.cc b/src/stim/util_top/export_crumble_url.cc new file mode 100644 index 000000000..fcb9765cf --- /dev/null +++ b/src/stim/util_top/export_crumble_url.cc @@ -0,0 +1,34 @@ +#include "stim/util_top/export_crumble_url.h" + +using namespace stim; + +std::string stim::export_crumble_url(const Circuit &circuit) { + auto s = circuit.str(); + std::string_view s_view = s; + std::vector> replace_rules{ + {"QUBIT_COORDS", "Q"}, + {", ", ","}, + {") ", ")"}, + {" ", ""}, + {" ", "_"}, + {"\n", ";"}, + }; + std::stringstream out; + out << "https://algassert.com/crumble#circuit="; + for (size_t k = 0; k < s.size(); k++) { + std::string_view v = s_view.substr(k); + bool matched = false; + for (auto [src, dst] : replace_rules) { + if (v.starts_with(src)) { + out << dst; + k += src.size() - 1; + matched = true; + break; + } + } + if (!matched) { + out << s[k]; + } + } + return out.str(); +} diff --git a/src/stim/util_top/export_crumble_url.h b/src/stim/util_top/export_crumble_url.h new file mode 100644 index 000000000..9c063cec7 --- /dev/null +++ b/src/stim/util_top/export_crumble_url.h @@ -0,0 +1,12 @@ +#ifndef _STIM_UTIL_TOP_EXPORT_CRUMBLE_URL_H +#define _STIM_UTIL_TOP_EXPORT_CRUMBLE_URL_H + +#include "stim/circuit/circuit.h" + +namespace stim { + +std::string export_crumble_url(const Circuit &circuit); + +} // namespace stim + +#endif diff --git a/src/stim/util_top/export_crumble_url.test.cc b/src/stim/util_top/export_crumble_url.test.cc new file mode 100644 index 000000000..29061aacb --- /dev/null +++ b/src/stim/util_top/export_crumble_url.test.cc @@ -0,0 +1,108 @@ +#include "stim/util_top/export_crumble_url.h" + +#include "gtest/gtest.h" + +#include "stim/circuit/circuit.test.h" + +using namespace stim; + +TEST(export_crumble, all_operations) { + auto actual = export_crumble_url(generate_test_circuit_with_all_operations()); + auto expected = + "https://algassert.com/crumble#circuit=" + "Q(1,2,3)0;" + "I_0;" + "X_1;" + "Y_2;" + "Z_3;" + "TICK;" + "C_XYZ_0;" + "C_ZYX_1;" + "H_XY_2;" + "H_3;" + "H_YZ_4;" + "SQRT_X_0;" + "SQRT_X_DAG_1;" + "SQRT_Y_2;" + "SQRT_Y_DAG_3;" + "S_4;" + "S_DAG_5;" + "TICK;" + "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;" + "CX_12_13;" + "CY_14_15;" + "CZ_16_17;" + "TICK;" + "E(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.01,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;" + "MPP_X0*Y1*Z2_Z0*Z1;" + "SPP_X0*Y1*Z2_X3;" + "SPP_DAG_X0*Y1*Z2_X2;" + "TICK;" + "MRX_0;" + "MRY_1;" + "MR_2;" + "MX_3;" + "MY_4;" + "M_5_6;" + "RX_7;" + "RY_8;" + "R_9;" + "TICK;" + "MXX_0_1_2_3;" + "MYY_4_5;" + "MZZ_6_7;" + "TICK;" + "REPEAT_3_{;" + "H_0;" + "CX_0_1;" + "S_1;" + "TICK;" + "};" + "TICK;" + "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;" + "MRX_!0;" + "MY_!1;" + "MZZ_!2_3;" + "MYY_!4_!5;" + "MPP_X6*!Y7*Z8;" + "TICK;" + "CX_rec[-1]_0;" + "CY_sweep[0]_1;" + "CZ_2_rec[-1]"; + ASSERT_EQ(actual, expected); +} diff --git a/src/stim/util_top/export_crumble_url_pybind_test.py b/src/stim/util_top/export_crumble_url_pybind_test.py new file mode 100644 index 000000000..75d7f08d9 --- /dev/null +++ b/src/stim/util_top/export_crumble_url_pybind_test.py @@ -0,0 +1,22 @@ +import stim + + +def test_to_crumble_url_simple(): + c = stim.Circuit(""" + QUBIT_COORDS(2, 1) 0 + QUBIT_COORDS(2, 2) 1 + H 0 + TICK + CX 0 1 + TICK + C_XYZ 0 + S 1 + TICK + MZZ 1 0 + """) + assert c.to_crumble_url() == "https://algassert.com/crumble#circuit=Q(2,1)0;Q(2,2)1;H_0;TICK;CX_0_1;TICK;C_XYZ_0;S_1;TICK;MZZ_1_0" + + +def test_to_crumble_url_complex(): + c = stim.Circuit.generated('surface_code:rotated_memory_x', distance=3, rounds=2, after_clifford_depolarization=0.001) + assert 'DEPOLARIZE1' in c.to_crumble_url() diff --git a/src/stim/circuit/export_qasm.cc b/src/stim/util_top/export_qasm.cc similarity index 99% rename from src/stim/circuit/export_qasm.cc rename to src/stim/util_top/export_qasm.cc index 20ee303c2..c4fbed58d 100644 --- a/src/stim/circuit/export_qasm.cc +++ b/src/stim/util_top/export_qasm.cc @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "stim/circuit/export_qasm.h" +#include "stim/util_top/export_qasm.h" #include diff --git a/src/stim/circuit/export_qasm.h b/src/stim/util_top/export_qasm.h similarity index 100% rename from src/stim/circuit/export_qasm.h rename to src/stim/util_top/export_qasm.h diff --git a/src/stim/circuit/export_qasm.test.cc b/src/stim/util_top/export_qasm.test.cc similarity index 99% rename from src/stim/circuit/export_qasm.test.cc rename to src/stim/util_top/export_qasm.test.cc index 7585fa5b5..713397054 100644 --- a/src/stim/circuit/export_qasm.test.cc +++ b/src/stim/util_top/export_qasm.test.cc @@ -1,4 +1,4 @@ -#include "stim/circuit/export_qasm.h" +#include "stim/util_top/export_qasm.h" #include "gtest/gtest.h" diff --git a/src/stim/circuit/export_qasm_pybind_test.py b/src/stim/util_top/export_qasm_pybind_test.py similarity index 100% rename from src/stim/circuit/export_qasm_pybind_test.py rename to src/stim/util_top/export_qasm_pybind_test.py diff --git a/src/stim/util_top/export_quirk_url.cc b/src/stim/util_top/export_quirk_url.cc new file mode 100644 index 000000000..b8db58b68 --- /dev/null +++ b/src/stim/util_top/export_quirk_url.cc @@ -0,0 +1,396 @@ +#include "stim/util_top/export_quirk_url.h" + +#include "stim/circuit/gate_decomposition.h" + +using namespace stim; + +static void for_each_target_group(CircuitInstruction instruction, const std::function &callback) { + Gate g = GATE_DATA[instruction.gate_type]; + if (g.flags & GATE_TARGETS_COMBINERS) { + return for_each_combined_targets_group(instruction, callback); + } else if (g.flags & GATE_TARGETS_PAIRS) { + for (size_t k = 0; k < instruction.targets.size(); k += 2) { + callback({instruction.gate_type, instruction.args, instruction.targets.sub(k, k + 2)}); + } + } else if (g.flags & GATE_IS_SINGLE_QUBIT_GATE) { + for (GateTarget t : instruction.targets) { + callback({instruction.gate_type, instruction.args, &t}); + } + } else { + callback(instruction); + } +} + +struct QuirkExporter { + size_t num_qubits; + size_t col_offset = 0; + std::array used{}; + std::array stim_name_to_quirk_name{}; + std::array, NUM_DEFINED_GATES> control_target_type{}; + std::array phase_type{}; + std::array custom_gate_definition{}; + std::map> cols; + + QuirkExporter(size_t num_qubits) : num_qubits(num_qubits) { + custom_gate_definition[(int)GateType::H_XY] = R"URL({"id":"~Hxy","name":"Hxy","matrix":"{{0,-√½-√½i},{√½-√½i,0}}"})URL"; + custom_gate_definition[(int)GateType::H_YZ] = R"URL({"id":"~Hyz","name":"Hyz","matrix":"{{-√½i,-√½},{√½,√½i}}"})URL"; + custom_gate_definition[(int)GateType::C_XYZ] = R"URL({"id":"~Cxyz","name":"Cxyz","matrix":"{{½-½i,-½-½i},{½-½i,½+½i}}"})URL"; + custom_gate_definition[(int)GateType::C_ZYX] = R"URL({"id":"~Czyx","name":"Czyx","matrix":"{{½+½i,½+½i},{-½+½i,½-½i}}"})URL"; + + stim_name_to_quirk_name[(int)GateType::H] = "H"; + stim_name_to_quirk_name[(int)GateType::H_XY] = "~Hxy"; + stim_name_to_quirk_name[(int)GateType::H_YZ] = "~Hyz"; + stim_name_to_quirk_name[(int)GateType::I] = "…"; + stim_name_to_quirk_name[(int)GateType::X] = "X"; + stim_name_to_quirk_name[(int)GateType::Y] = "Y"; + stim_name_to_quirk_name[(int)GateType::Z] = "Z"; + stim_name_to_quirk_name[(int)GateType::C_XYZ] = "~Cxyz"; + stim_name_to_quirk_name[(int)GateType::C_ZYX] = "~Czyx"; + stim_name_to_quirk_name[(int)GateType::SQRT_X] = "X^½"; + stim_name_to_quirk_name[(int)GateType::SQRT_X_DAG] = "X^-½"; + stim_name_to_quirk_name[(int)GateType::SQRT_Y] = "Y^½"; + stim_name_to_quirk_name[(int)GateType::SQRT_Y_DAG] = "Y^-½"; + stim_name_to_quirk_name[(int)GateType::S] = "Z^½"; + stim_name_to_quirk_name[(int)GateType::S_DAG] = "Z^-½"; + stim_name_to_quirk_name[(int)GateType::MX] = "XDetector"; + stim_name_to_quirk_name[(int)GateType::MY] = "YDetector"; + stim_name_to_quirk_name[(int)GateType::M] = "ZDetector"; + stim_name_to_quirk_name[(int)GateType::MRX] = "XDetectControlReset"; + stim_name_to_quirk_name[(int)GateType::MRY] = "YDetectControlReset"; + stim_name_to_quirk_name[(int)GateType::MR] = "ZDetectControlReset"; + stim_name_to_quirk_name[(int)GateType::RX] = "XDetectControlReset"; + stim_name_to_quirk_name[(int)GateType::RY] = "YDetectControlReset"; + stim_name_to_quirk_name[(int)GateType::R] = "ZDetectControlReset"; + + std::string_view x_control = "⊖"; + std::string_view y_control = "(/)"; + std::string_view z_control = "•"; + control_target_type[(int)GateType::XCX] = {x_control, "X"}; + control_target_type[(int)GateType::XCY] = {x_control, "Y"}; + control_target_type[(int)GateType::XCZ] = {x_control, "Z"}; + control_target_type[(int)GateType::YCX] = {y_control, "X"}; + control_target_type[(int)GateType::YCY] = {y_control, "Y"}; + control_target_type[(int)GateType::YCZ] = {y_control, "Z"}; + control_target_type[(int)GateType::CX] = {z_control, "X"}; + control_target_type[(int)GateType::CY] = {z_control, "Y"}; + control_target_type[(int)GateType::CZ] = {z_control, "Z"}; + control_target_type[(int)GateType::SWAPCX] = {z_control, "X"}; + control_target_type[(int)GateType::CXSWAP] = {x_control, "Z"}; + control_target_type[(int)GateType::CZSWAP] = {z_control, "Z"}; + control_target_type[(int)GateType::ISWAP] = {"zpar", "zpar"}; + control_target_type[(int)GateType::ISWAP_DAG] = {"zpar", "zpar"}; + control_target_type[(int)GateType::SQRT_XX] = {"xpar", "xpar"}; + control_target_type[(int)GateType::SQRT_YY] = {"ypar", "ypar"}; + control_target_type[(int)GateType::SQRT_ZZ] = {"zpar", "zpar"}; + control_target_type[(int)GateType::SQRT_XX_DAG] = {"xpar", "xpar"}; + control_target_type[(int)GateType::SQRT_YY_DAG] = {"ypar", "ypar"}; + control_target_type[(int)GateType::SQRT_ZZ_DAG] = {"zpar", "zpar"}; + control_target_type[(int)GateType::MXX] = {"xpar", "xpar"}; + control_target_type[(int)GateType::MYY] = {"xpar", "xpar"}; + control_target_type[(int)GateType::MZZ] = {"xpar", "xpar"}; + + phase_type[(int)GateType::SQRT_XX] = "i"; + phase_type[(int)GateType::SQRT_YY] = "i"; + phase_type[(int)GateType::SQRT_ZZ] = "i"; + phase_type[(int)GateType::SQRT_XX_DAG] = "-i"; + phase_type[(int)GateType::SQRT_YY_DAG] = "-i"; + phase_type[(int)GateType::SQRT_ZZ_DAG] = "-i"; + phase_type[(int)GateType::SPP] = "i"; + phase_type[(int)GateType::SPP_DAG] = "-i"; + phase_type[(int)GateType::ISWAP] = "i"; + phase_type[(int)GateType::ISWAP_DAG] = "-i"; + } + + void write_pauli_par_controls(GateType g, size_t col, std::span targets) { + for (auto t : targets) { + if (t.has_qubit_value()) { + bool x = t.data & TARGET_PAULI_X_BIT; + bool z = t.data & TARGET_PAULI_Z_BIT; + uint8_t p = x + z * 2; + if (!p) { + cols[col][t.value()] = control_target_type[(int)g].first; + } else { + cols[col][t.value()] = std::array{"", "xpar", "zpar", "ypar"}[p]; + } + } + } + } + + size_t pick_free_qubit(std::span targets) { + if (num_qubits <= 16) { + return num_qubits; + } + std::set qs; + for (auto t : targets) { + if (t.has_qubit_value()) { + qs.insert(t.value()); + } + } + size_t q = 0; + while (qs.contains(q)) { + q += 1; + } + return q; + } + + size_t pick_merge_qubit(std::span targets) { + if (num_qubits <= 16) { + return num_qubits; + } + for (auto t : targets) { + uint8_t p = t.pauli_type(); + if (p && t.has_qubit_value() && t.qubit_value() <= 16) { + return t.qubit_value(); + } + } + + return num_qubits; + } + + void do_single_qubit_gate(GateType g, GateTarget t) { + if (t.has_qubit_value()) { + if (cols[col_offset].contains(t.value()) || cols[col_offset + 1].contains(t.value()) || cols[col_offset + 2].contains(t.value())) { + col_offset += 3; + } + auto n = stim_name_to_quirk_name[(int)g]; + if (n == "XDetectControlReset") { + cols[col_offset][t.value()] = n; + cols[col_offset + 1][t.value()] = "H"; + } else if (n == "YDetectControlReset") { + cols[col_offset][t.value()] = n; + cols[col_offset + 1][t.value()] = "~Hyz"; + } else if (n == "ZDetectControlReset") { + cols[col_offset][t.value()] = n; + } else { + cols[col_offset + 1][t.value()] = n; + } + } + } + + void do_multi_phase_gate(GateType g, std::span group) { + col_offset += 3; + size_t q_free = pick_free_qubit(group); + write_pauli_par_controls(g, col_offset, group); + cols[col_offset][q_free] = phase_type[(int)g]; + col_offset += 3; + } + + void do_multi_measure_gate(GateType g, std::span group) { + col_offset += 3; + size_t q_free = pick_merge_qubit(group); + write_pauli_par_controls(g, col_offset, group); + if (q_free == num_qubits) { + cols[col_offset][q_free] = "X"; + cols[col_offset + 1][q_free] = "ZDetectControlReset"; + } else { + write_pauli_par_controls(g, col_offset + 2, group); + + auto r = cols[col_offset][q_free]; + if (r == "xpar") { + cols[col_offset][q_free] = "Z"; + cols[col_offset + 1][q_free] = "XDetector"; + cols[col_offset + 2][q_free] = "Z"; + } else if (r == "ypar") { + cols[col_offset][q_free] = "X"; + cols[col_offset + 1][q_free] = "YDetector"; + cols[col_offset + 2][q_free] = "X"; + } else { + cols[col_offset][q_free] = "X"; + cols[col_offset + 1][q_free] = "ZDetector"; + cols[col_offset + 2][q_free] = "X"; + } + } + col_offset += 3; + } + + void do_controlled_gate(GateType g, GateTarget t1, GateTarget t2) { + if (t1.has_qubit_value() && t2.has_qubit_value()) { + col_offset += 3; + auto [c, t] = control_target_type[(int)g]; + cols[col_offset][t1.qubit_value()] = c; + cols[col_offset][t2.qubit_value()] = t; + col_offset += 3; + } + } + + void do_swap_plus_gate(GateType g, GateTarget t1, GateTarget t2) { + if (t1.has_qubit_value() && t2.has_qubit_value()) { + col_offset += 3; + cols[col_offset][t1.qubit_value()] = "Swap"; + cols[col_offset][t2.qubit_value()] = "Swap"; + if (g == GateType::ISWAP || g == GateType::ISWAP_DAG) { + std::array group{t1, t2}; + do_multi_phase_gate(g, group); + } else { + do_controlled_gate(g, t1, t2); + } + col_offset += 3; + } + } + + void do_circuit(const Circuit &circuit) { + circuit.for_each_operation([&](CircuitInstruction full_instruction) { + used[(int)full_instruction.gate_type] = true; + for_each_target_group(full_instruction, [&](CircuitInstruction inst) { + switch (inst.gate_type) { + case GateType::DETECTOR: + case GateType::OBSERVABLE_INCLUDE: + case GateType::QUBIT_COORDS: + case GateType::SHIFT_COORDS: + case GateType::MPAD: + case GateType::DEPOLARIZE1: + case GateType::DEPOLARIZE2: + case GateType::X_ERROR: + case GateType::Y_ERROR: + case GateType::Z_ERROR: + case GateType::PAULI_CHANNEL_1: + case GateType::PAULI_CHANNEL_2: + case GateType::E: + case GateType::ELSE_CORRELATED_ERROR: + case GateType::HERALDED_ERASE: + case GateType::HERALDED_PAULI_CHANNEL_1: + // Ignored. + break; + + case GateType::TICK: + col_offset += 3; + break; + + case GateType::MX: + case GateType::MY: + case GateType::M: + case GateType::MRX: + case GateType::MRY: + case GateType::MR: + case GateType::RX: + case GateType::RY: + case GateType::R: + case GateType::H: + case GateType::H_XY: + case GateType::H_YZ: + case GateType::I: + case GateType::X: + case GateType::Y: + case GateType::Z: + case GateType::C_XYZ: + case GateType::C_ZYX: + case GateType::SQRT_X: + case GateType::SQRT_X_DAG: + case GateType::SQRT_Y: + case GateType::SQRT_Y_DAG: + case GateType::S: + case GateType::S_DAG: + do_single_qubit_gate(inst.gate_type, inst.targets[0]); + break; + + case GateType::SQRT_XX: + case GateType::SQRT_YY: + case GateType::SQRT_ZZ: + case GateType::SQRT_XX_DAG: + case GateType::SQRT_YY_DAG: + case GateType::SQRT_ZZ_DAG: + case GateType::SPP: + case GateType::SPP_DAG: + do_multi_phase_gate(inst.gate_type, inst.targets); + break; + + case GateType::XCX: + case GateType::XCY: + case GateType::XCZ: + case GateType::YCX: + case GateType::YCY: + case GateType::YCZ: + case GateType::CX: + case GateType::CY: + case GateType::CZ: + do_controlled_gate(inst.gate_type, inst.targets[0], inst.targets[1]); + break; + + case GateType::SWAP: + case GateType::ISWAP: + case GateType::CXSWAP: + case GateType::SWAPCX: + case GateType::CZSWAP: + case GateType::ISWAP_DAG: + do_swap_plus_gate(inst.gate_type, inst.targets[0], inst.targets[1]); + break; + + case GateType::MXX: + case GateType::MYY: + case GateType::MZZ: + case GateType::MPP: + do_multi_measure_gate(inst.gate_type, inst.targets); + break; + + default: + throw std::invalid_argument("Not supported in export_quirk_url: " + full_instruction.str()); + } + }); + }); + + } +}; + +std::string stim::export_quirk_url(const Circuit &circuit) { + QuirkExporter exporter(circuit.count_qubits()); + exporter.do_circuit(circuit); + + std::stringstream out; + exporter.col_offset += 3; + + out << R"URL(https://algassert.com/quirk#circuit={"cols":[)URL"; + bool has_col = false; + for (size_t k = 0; k < exporter.col_offset; k++) { + if (!exporter.cols.contains(k)) { + continue; + } + const auto &col = exporter.cols.at(k); + if (col.empty()) { + continue; + } + std::vector entries; + for (auto kv : col) { + while (entries.size() <= kv.first) { + entries.push_back(""); + } + entries[kv.first] = kv.second; + } + if (has_col) { + out << ","; + } + has_col = true; + out << "["; + for (size_t q = 0; q < entries.size(); q++) { + if (q) { + out << ","; + } + if (entries[q].empty()) { + out << "1"; + } else { + out << '"'; + out << entries[q]; + out << '"'; + } + } + out << "]"; + } + out << R"URL(])URL"; + bool has_custom_gates = false; + for (size_t k = 0; k < NUM_DEFINED_GATES; k++) { + if (!exporter.custom_gate_definition[k].empty() && exporter.used[k]) { + if (!has_custom_gates) { + out << R"URL(,"gates":[)URL"; + has_custom_gates = true; + } else { + out << ','; + } + out << exporter.custom_gate_definition[k]; + } + } + if (has_custom_gates) { + out << "]"; + } + out << "}"; + + return out.str(); +} diff --git a/src/stim/util_top/export_quirk_url.h b/src/stim/util_top/export_quirk_url.h new file mode 100644 index 000000000..fe2b7bd4a --- /dev/null +++ b/src/stim/util_top/export_quirk_url.h @@ -0,0 +1,12 @@ +#ifndef _STIM_UTIL_TOP_EXPORT_QUIRK_URL_H +#define _STIM_UTIL_TOP_EXPORT_QUIRK_URL_H + +#include "stim/circuit/circuit.h" + +namespace stim { + +std::string export_quirk_url(const Circuit &circuit); + +} // namespace stim + +#endif diff --git a/src/stim/util_top/export_quirk_url.test.cc b/src/stim/util_top/export_quirk_url.test.cc new file mode 100644 index 000000000..f552eb4ea --- /dev/null +++ b/src/stim/util_top/export_quirk_url.test.cc @@ -0,0 +1,165 @@ +#include "stim/util_top/export_quirk_url.h" + +#include "gtest/gtest.h" + +#include "stim/circuit/circuit.test.h" + +using namespace stim; + +TEST(export_quirk, simple) { + auto actual = export_quirk_url(Circuit(R"CIRCUIT( + R 0 + H 0 1 + S 2 + H 2 + CX 0 1 + M 1 + SQRT_ZZ 2 3 + MXX 0 1 + )CIRCUIT")); + auto expected = R"URL(https://algassert.com/quirk#circuit={"cols":[)URL" + R"URL(["ZDetectControlReset"],)URL" + R"URL(["H","H","Z^½"],)URL" + R"URL([1,1,"H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"ZDetector"],)URL" + R"URL([1,1,"zpar","zpar","i"],)URL" + R"URL(["xpar","xpar",1,1,"X"],)URL" + R"URL([1,1,1,1,"ZDetectControlReset"])URL" + R"URL(]})URL"; + ASSERT_EQ(actual, expected); + + actual = export_quirk_url(Circuit(R"CIRCUIT( + R 0 + H_XY 0 1 + S 2 + H 2 + CX 0 1 + M 1 + SQRT_ZZ 2 3 + MXX 0 1 + )CIRCUIT")); + expected = R"URL(https://algassert.com/quirk#circuit={"cols":[)URL" + R"URL(["ZDetectControlReset"],)URL" + R"URL(["~Hxy","~Hxy","Z^½"],)URL" + R"URL([1,1,"H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"ZDetector"],)URL" + R"URL([1,1,"zpar","zpar","i"],)URL" + R"URL(["xpar","xpar",1,1,"X"],)URL" + R"URL([1,1,1,1,"ZDetectControlReset"])URL" + R"URL(],"gates":[)URL" + R"URL({"id":"~Hxy","name":"Hxy","matrix":"{{0,-√½-√½i},{√½-√½i,0}}"}]})URL"; + ASSERT_EQ(actual, expected); + + actual = export_quirk_url(Circuit(R"CIRCUIT( + R 0 + H_XY 0 + H_YZ 1 + S 2 + H 2 + CX 0 1 + M 1 + SQRT_ZZ 2 3 + MXX 0 1 + )CIRCUIT")); + expected = R"URL(https://algassert.com/quirk#circuit={"cols":[)URL" + R"URL(["ZDetectControlReset"],)URL" + R"URL(["~Hxy","~Hyz","Z^½"],)URL" + R"URL([1,1,"H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"ZDetector"],)URL" + R"URL([1,1,"zpar","zpar","i"],)URL" + R"URL(["xpar","xpar",1,1,"X"],)URL" + R"URL([1,1,1,1,"ZDetectControlReset"])URL" + R"URL(],"gates":[)URL" + R"URL({"id":"~Hxy","name":"Hxy","matrix":"{{0,-√½-√½i},{√½-√½i,0}}"},)URL" + R"URL({"id":"~Hyz","name":"Hyz","matrix":"{{-√½i,-√½},{√½,√½i}}"}]})URL"; + ASSERT_EQ(actual, expected); +} + +TEST(export_quirk, all_operations) { + auto actual = export_quirk_url(generate_test_circuit_with_all_operations()); + auto expected = R"URL(https://algassert.com/quirk#circuit={"cols":[)URL" + R"URL(["…","X","Y","Z"],)URL" + R"URL(["~Cxyz","~Czyx","~Hxy","H","~Hyz"],)URL" + R"URL(["X^½","X^-½","Y^½","Y^-½","Z^½","Z^-½"],)URL" + R"URL(["Swap","Swap"],)URL" + R"URL(["⊖","Z"],)URL" + R"URL([1,1,"Swap","Swap"],)URL" + R"URL(["i",1,"zpar","zpar"],)URL" + R"URL([1,1,1,1,"Swap","Swap"],)URL" + R"URL(["-i",1,1,1,"zpar","zpar"],)URL" + R"URL([1,1,1,1,1,1,"Swap","Swap"],)URL" + R"URL([1,1,1,1,1,1,1,1],)URL" + R"URL([1,1,1,1,1,1,1,1,"Swap","Swap"],)URL" + R"URL([1,1,1,1,1,1,1,1,"•","X"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,"Swap","Swap"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,"•","Z"],)URL" + R"URL(["xpar","xpar","i"],)URL" + R"URL(["-i",1,"xpar","xpar"],)URL" + R"URL(["i",1,1,1,"ypar","ypar"],)URL" + R"URL(["-i",1,1,1,1,1,"ypar","ypar"],)URL" + R"URL(["i",1,1,1,1,1,1,1,"zpar","zpar"],)URL" + R"URL(["-i",1,1,1,1,1,1,1,1,1,"zpar","zpar"],)URL" + R"URL(["⊖","X"],)URL" + R"URL([1,1,"⊖","Y"],)URL" + R"URL([1,1,1,1,"⊖","Z"],)URL" + R"URL([1,1,1,1,1,1,"(/)","X"],)URL" + R"URL([1,1,1,1,1,1,1,1,"(/)","Y"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,"(/)","Z"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,1,1,"•","X"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,1,1,1,1,"•","Y"],)URL" + R"URL([1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,"•","Z"],)URL" + R"URL(["Z","ypar","zpar"],)URL" + R"URL(["XDetector"],)URL" + R"URL(["Z","ypar","zpar"],)URL" + R"URL(["X","zpar"],)URL" + R"URL(["ZDetector"],)URL" + R"URL(["X","zpar"],)URL" + R"URL(["xpar","ypar","zpar","i"],)URL" + R"URL(["i",1,1,"xpar"],)URL" + R"URL(["xpar","ypar","zpar","-i"],)URL" + R"URL(["-i",1,"xpar"],)URL" + R"URL(["XDetectControlReset","YDetectControlReset","ZDetectControlReset",1,1,1,1,"XDetectControlReset","YDetectControlReset","ZDetectControlReset"],)URL" + R"URL(["H","~Hyz",1,"XDetector","YDetector","ZDetector","ZDetector","H","~Hyz"],)URL" + R"URL(["Z","xpar"],)URL" + R"URL(["XDetector"],)URL" + R"URL(["Z","xpar"],)URL" + R"URL([1,1,"Z","xpar"],)URL" + R"URL([1,1,"XDetector"],)URL" + R"URL([1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,"XDetector"],)URL" + R"URL([1,1,1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,1,1,"XDetector"],)URL" + R"URL([1,1,1,1,1,1,"Z","xpar"],)URL" + R"URL(["H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"Z^½"],)URL" + R"URL(["H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"Z^½"],)URL" + R"URL(["H"],)URL" + R"URL(["•","X"],)URL" + R"URL([1,"Z^½"],)URL" + R"URL(["ZDetectControlReset"],)URL" + R"URL(["ZDetectControlReset"],)URL" + R"URL(["XDetectControlReset"],)URL" + R"URL(["H","YDetector"],)URL" + R"URL([1,1,"Z","xpar"],)URL" + R"URL([1,1,"XDetector"],)URL" + R"URL([1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,"XDetector"],)URL" + R"URL([1,1,1,1,"Z","xpar"],)URL" + R"URL([1,1,1,1,1,1,"Z","ypar","zpar"],)URL" + R"URL([1,1,1,1,1,1,"XDetector"],)URL" + R"URL([1,1,1,1,1,1,"Z","ypar","zpar"]],"gates":)URL" + R"URL([{"id":"~Hxy","name":"Hxy","matrix":"{{0,-√½-√½i},{√½-√½i,0}}"},)URL" + R"URL({"id":"~Hyz","name":"Hyz","matrix":"{{-√½i,-√½},{√½,√½i}}"},)URL" + R"URL({"id":"~Cxyz","name":"Cxyz","matrix":"{{½-½i,-½-½i},{½-½i,½+½i}}"},)URL" + R"URL({"id":"~Czyx","name":"Czyx","matrix":"{{½+½i,½+½i},{-½+½i,½-½i}}"}]})URL"; + ASSERT_EQ(actual, expected); +} diff --git a/src/stim/util_top/export_quirk_url_pybind_test.py b/src/stim/util_top/export_quirk_url_pybind_test.py new file mode 100644 index 000000000..55567f72e --- /dev/null +++ b/src/stim/util_top/export_quirk_url_pybind_test.py @@ -0,0 +1,22 @@ +import stim + + +def test_to_quirk_url_simple(): + c = stim.Circuit(""" + QUBIT_COORDS(2, 1) 0 + QUBIT_COORDS(2, 2) 1 + H 0 + TICK + CX 0 1 + TICK + C_XYZ 0 + S 1 + TICK + MZZ 1 0 + """) + assert c.to_quirk_url() == 'https://algassert.com/quirk#circuit={"cols":[["H"],["•","X"],["~Cxyz","Z^½"],["xpar","xpar","X"],[1,1,"ZDetectControlReset"]],"gates":[{"id":"~Cxyz","name":"Cxyz","matrix":"{{½-½i,-½-½i},{½-½i,½+½i}}"}]}' + + +def test_to_quirk_url_complex(): + c = stim.Circuit.generated('surface_code:rotated_memory_x', distance=2, rounds=2, after_clifford_depolarization=0.001) + assert '"H"' in c.to_quirk_url()