From a6eb117d00e86dd1c11330887b438ce7608875b0 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 18 Nov 2023 17:11:13 -0800 Subject: [PATCH] Add `stim.Circuit.to_qasm` - Add `stim.Circuit.to_tableau` alias for `stim.Tableau.from_circuit` - Remove unnecessary operations from the decomposition of RX and RY Fixes https://github.com/quantumlib/Stim/issues/648 Fixes https://github.com/quantumlib/Stim/issues/657 --- doc/gates.md | 5 - doc/python_api_reference_vDev.md | 126 ++++++ doc/stim.pyi | 110 +++++ file_lists/source_files_no_main | 1 + file_lists/test_files | 1 + glue/python/src/stim/__init__.pyi | 110 +++++ src/stim.h | 1 + src/stim/circuit/circuit.pybind.cc | 126 ++++++ src/stim/circuit/circuit_pybind_test.py | 60 +++ src/stim/circuit/export_qasm.cc | 530 +++++++++++++++++++++++ src/stim/circuit/export_qasm.h | 28 ++ src/stim/circuit/export_qasm.test.cc | 482 +++++++++++++++++++++ src/stim/circuit/gate_data_collapsing.cc | 5 - 13 files changed, 1575 insertions(+), 10 deletions(-) create mode 100644 src/stim/circuit/export_qasm.cc create mode 100644 src/stim/circuit/export_qasm.h create mode 100644 src/stim/circuit/export_qasm.test.cc diff --git a/doc/gates.md b/doc/gates.md index 32e73d6f8..a8106f157 100644 --- a/doc/gates.md +++ b/doc/gates.md @@ -2757,7 +2757,6 @@ Stabilizer Generators: Decomposition (into H, S, CX, M, R): # The following circuit is equivalent (up to global phase) to `RX 0` - H 0 R 0 H 0 @@ -2790,10 +2789,6 @@ Stabilizer Generators: Decomposition (into H, S, CX, M, R): # The following circuit is equivalent (up to global phase) to `RY 0` - S 0 - S 0 - S 0 - H 0 R 0 H 0 S 0 diff --git a/doc/python_api_reference_vDev.md b/doc/python_api_reference_vDev.md index b729716b3..5175612a0 100644 --- a/doc/python_api_reference_vDev.md +++ b/doc/python_api_reference_vDev.md @@ -46,6 +46,8 @@ API references for stable versions are kept on the [stim github wiki](https://gi - [`stim.Circuit.search_for_undetectable_logical_errors`](#stim.Circuit.search_for_undetectable_logical_errors) - [`stim.Circuit.shortest_graphlike_error`](#stim.Circuit.shortest_graphlike_error) - [`stim.Circuit.to_file`](#stim.Circuit.to_file) + - [`stim.Circuit.to_qasm`](#stim.Circuit.to_qasm) + - [`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) - [`stim.CircuitErrorLocation`](#stim.CircuitErrorLocation) @@ -2359,6 +2361,130 @@ def to_file( """ ``` + +```python +# stim.Circuit.to_qasm + +# (in class stim.Circuit) +def to_qasm( + self, + *, + open_qasm_version: int, + skip_dets_and_obs: bool = False, +) -> str: + """Creates an equivalent OpenQASM implementation of the circuit. + + Args: + open_qasm_version: Defaults to 3. The version of OpenQASM to target. + This should be set to 2 or to 3. + + Differences between the versions are: + - Support for operations on classical bits operations (only version + 3). This means DETECTOR and OBSERVABLE_INCLUDE only work with + version 3. + - Support for feedback operations (only version 3). + - Support for subroutines (only version 3). Without subroutines, + non-standard dissipative gates like MR and RX need to decompose + inline every single time they're used. + - Minor name changes (e.g. creg -> bit, qelib1.inc -> stdgates.inc). + skip_dets_and_obs: Defaults to False. When set to False, the output will + include a `dets` register and an `obs` register (assuming the circuit + has detectors and observables). These registers will be computed as part + of running the circuit. This requires performing a simulation of the + circuit, in order to correctly account for the expected value of + measurements. + + When set to True, the `dets` and `obs` registers are not included in the + output, and no simulation of the circuit is performed. + + Returns: + The OpenQASM code as a string. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(""" + ... R 0 1 + ... H 0 + ... CX 0 1 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... """); + >>> qasm = circuit.to_qasm(); + >>> print(qasm.strip().replace('\n\n', '\n')) + OPENQASM 3.0; + include "stdgates.inc"; + qubit q[2]; + bit m[2]; + bit dets[1]; + reset q[0]; + reset q[1]; + h q[0]; + cx q[0], q[1]; + measure q[0] -> m[0]; + measure q[1] -> m[1]; + dets[0] = rec[1] ^ rec[0] ^ 0; + """ +``` + + +```python +# stim.Circuit.to_tableau + +# (in class stim.Circuit) +def to_tableau( + self, + *, + ignore_noise: bool = False, + ignore_measurement: bool = False, + ignore_reset: bool = False, +) -> stim.Tableau: + """Converts the circuit into an equivalent stabilizer tableau. + + Args: + ignore_noise: Defaults to False. When False, any noise operations in the + circuit will cause the conversion to fail with an exception. When True, + noise operations are skipped over as if they weren't even present in the + circuit. + ignore_measurement: Defaults to False. When False, any measurement + operations in the circuit will cause the conversion to fail with an + exception. When True, measurement operations are skipped over as if they + weren't even present in the circuit. + ignore_reset: Defaults to False. When False, any reset operations in the + circuit will cause the conversion to fail with an exception. When True, + reset operations are skipped over as if they weren't even present in the + circuit. + + Returns: + A tableau equivalent to the circuit (up to global phase). + + Raises: + ValueError: + The circuit contains noise operations but ignore_noise=False. + OR + The circuit contains measurement operations but + ignore_measurement=False. + OR + The circuit contains reset operations but ignore_reset=False. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... ''').to_tableau() + stim.Tableau.from_conjugated_generators( + xs=[ + stim.PauliString("+Z_"), + stim.PauliString("+_X"), + ], + zs=[ + stim.PauliString("+XX"), + stim.PauliString("+ZZ"), + ], + ) + """ +``` + ```python # stim.Circuit.with_inlined_feedback diff --git a/doc/stim.pyi b/doc/stim.pyi index bb3b2f8cd..16246528a 100644 --- a/doc/stim.pyi +++ b/doc/stim.pyi @@ -1715,6 +1715,116 @@ class Circuit: >>> contents 'H 5\nX 0\n' """ + def to_qasm( + self, + *, + open_qasm_version: int, + skip_dets_and_obs: bool = False, + ) -> str: + """Creates an equivalent OpenQASM implementation of the circuit. + + Args: + open_qasm_version: Defaults to 3. The version of OpenQASM to target. + This should be set to 2 or to 3. + + Differences between the versions are: + - Support for operations on classical bits operations (only version + 3). This means DETECTOR and OBSERVABLE_INCLUDE only work with + version 3. + - Support for feedback operations (only version 3). + - Support for subroutines (only version 3). Without subroutines, + non-standard dissipative gates like MR and RX need to decompose + inline every single time they're used. + - Minor name changes (e.g. creg -> bit, qelib1.inc -> stdgates.inc). + skip_dets_and_obs: Defaults to False. When set to False, the output will + include a `dets` register and an `obs` register (assuming the circuit + has detectors and observables). These registers will be computed as part + of running the circuit. This requires performing a simulation of the + circuit, in order to correctly account for the expected value of + measurements. + + When set to True, the `dets` and `obs` registers are not included in the + output, and no simulation of the circuit is performed. + + Returns: + The OpenQASM code as a string. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(""" + ... R 0 1 + ... H 0 + ... CX 0 1 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... """); + >>> qasm = circuit.to_qasm(); + >>> print(qasm.strip().replace('\n\n', '\n')) + OPENQASM 3.0; + include "stdgates.inc"; + qubit q[2]; + bit m[2]; + bit dets[1]; + reset q[0]; + reset q[1]; + h q[0]; + cx q[0], q[1]; + measure q[0] -> m[0]; + measure q[1] -> m[1]; + dets[0] = rec[1] ^ rec[0] ^ 0; + """ + def to_tableau( + self, + *, + ignore_noise: bool = False, + ignore_measurement: bool = False, + ignore_reset: bool = False, + ) -> stim.Tableau: + """Converts the circuit into an equivalent stabilizer tableau. + + Args: + ignore_noise: Defaults to False. When False, any noise operations in the + circuit will cause the conversion to fail with an exception. When True, + noise operations are skipped over as if they weren't even present in the + circuit. + ignore_measurement: Defaults to False. When False, any measurement + operations in the circuit will cause the conversion to fail with an + exception. When True, measurement operations are skipped over as if they + weren't even present in the circuit. + ignore_reset: Defaults to False. When False, any reset operations in the + circuit will cause the conversion to fail with an exception. When True, + reset operations are skipped over as if they weren't even present in the + circuit. + + Returns: + A tableau equivalent to the circuit (up to global phase). + + Raises: + ValueError: + The circuit contains noise operations but ignore_noise=False. + OR + The circuit contains measurement operations but + ignore_measurement=False. + OR + The circuit contains reset operations but ignore_reset=False. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... ''').to_tableau() + stim.Tableau.from_conjugated_generators( + xs=[ + stim.PauliString("+Z_"), + stim.PauliString("+_X"), + ], + zs=[ + stim.PauliString("+XX"), + stim.PauliString("+ZZ"), + ], + ) + """ def with_inlined_feedback( self, ) -> stim.Circuit: diff --git a/file_lists/source_files_no_main b/file_lists/source_files_no_main index 042c535fd..a0b00cf76 100644 --- a/file_lists/source_files_no_main +++ b/file_lists/source_files_no_main @@ -2,6 +2,7 @@ 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_data.cc src/stim/circuit/gate_data_annotations.cc src/stim/circuit/gate_data_blocks.cc diff --git a/file_lists/test_files b/file_lists/test_files index 3fd76c0d8..42fc44604 100644 --- a/file_lists/test_files +++ b/file_lists/test_files @@ -1,6 +1,7 @@ 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_data.test.cc src/stim/circuit/gate_decomposition.test.cc src/stim/circuit/gate_target.test.cc diff --git a/glue/python/src/stim/__init__.pyi b/glue/python/src/stim/__init__.pyi index bb3b2f8cd..16246528a 100644 --- a/glue/python/src/stim/__init__.pyi +++ b/glue/python/src/stim/__init__.pyi @@ -1715,6 +1715,116 @@ class Circuit: >>> contents 'H 5\nX 0\n' """ + def to_qasm( + self, + *, + open_qasm_version: int, + skip_dets_and_obs: bool = False, + ) -> str: + """Creates an equivalent OpenQASM implementation of the circuit. + + Args: + open_qasm_version: Defaults to 3. The version of OpenQASM to target. + This should be set to 2 or to 3. + + Differences between the versions are: + - Support for operations on classical bits operations (only version + 3). This means DETECTOR and OBSERVABLE_INCLUDE only work with + version 3. + - Support for feedback operations (only version 3). + - Support for subroutines (only version 3). Without subroutines, + non-standard dissipative gates like MR and RX need to decompose + inline every single time they're used. + - Minor name changes (e.g. creg -> bit, qelib1.inc -> stdgates.inc). + skip_dets_and_obs: Defaults to False. When set to False, the output will + include a `dets` register and an `obs` register (assuming the circuit + has detectors and observables). These registers will be computed as part + of running the circuit. This requires performing a simulation of the + circuit, in order to correctly account for the expected value of + measurements. + + When set to True, the `dets` and `obs` registers are not included in the + output, and no simulation of the circuit is performed. + + Returns: + The OpenQASM code as a string. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(""" + ... R 0 1 + ... H 0 + ... CX 0 1 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... """); + >>> qasm = circuit.to_qasm(); + >>> print(qasm.strip().replace('\n\n', '\n')) + OPENQASM 3.0; + include "stdgates.inc"; + qubit q[2]; + bit m[2]; + bit dets[1]; + reset q[0]; + reset q[1]; + h q[0]; + cx q[0], q[1]; + measure q[0] -> m[0]; + measure q[1] -> m[1]; + dets[0] = rec[1] ^ rec[0] ^ 0; + """ + def to_tableau( + self, + *, + ignore_noise: bool = False, + ignore_measurement: bool = False, + ignore_reset: bool = False, + ) -> stim.Tableau: + """Converts the circuit into an equivalent stabilizer tableau. + + Args: + ignore_noise: Defaults to False. When False, any noise operations in the + circuit will cause the conversion to fail with an exception. When True, + noise operations are skipped over as if they weren't even present in the + circuit. + ignore_measurement: Defaults to False. When False, any measurement + operations in the circuit will cause the conversion to fail with an + exception. When True, measurement operations are skipped over as if they + weren't even present in the circuit. + ignore_reset: Defaults to False. When False, any reset operations in the + circuit will cause the conversion to fail with an exception. When True, + reset operations are skipped over as if they weren't even present in the + circuit. + + Returns: + A tableau equivalent to the circuit (up to global phase). + + Raises: + ValueError: + The circuit contains noise operations but ignore_noise=False. + OR + The circuit contains measurement operations but + ignore_measurement=False. + OR + The circuit contains reset operations but ignore_reset=False. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... ''').to_tableau() + stim.Tableau.from_conjugated_generators( + xs=[ + stim.PauliString("+Z_"), + stim.PauliString("+_X"), + ], + zs=[ + stim.PauliString("+XX"), + stim.PauliString("+ZZ"), + ], + ) + """ def with_inlined_feedback( self, ) -> stim.Circuit: diff --git a/src/stim.h b/src/stim.h index 6c5a2a040..b64047f3e 100644 --- a/src/stim.h +++ b/src/stim.h @@ -6,6 +6,7 @@ #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_data.h" #include "stim/circuit/gate_data_table.h" #include "stim/circuit/gate_decomposition.h" diff --git a/src/stim/circuit/circuit.pybind.cc b/src/stim/circuit/circuit.pybind.cc index 335ba4499..43d28ded1 100644 --- a/src/stim/circuit/circuit.pybind.cc +++ b/src/stim/circuit/circuit.pybind.cc @@ -18,6 +18,7 @@ #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" @@ -38,6 +39,7 @@ #include "stim/simulators/measurements_to_detection_events.pybind.h" #include "stim/simulators/tableau_simulator.h" #include "stim/simulators/transform_without_feedback.h" +#include "stim/stabilizers/conversions.h" using namespace stim; using namespace stim_pybind; @@ -1318,6 +1320,130 @@ void stim_pybind::pybind_circuit_methods(pybind11::module &, pybind11::class_(circuit, ignore_noise, ignore_measurement, ignore_reset); + }, + pybind11::kw_only(), + pybind11::arg("ignore_noise") = false, + pybind11::arg("ignore_measurement") = false, + pybind11::arg("ignore_reset") = false, + clean_doc_string(R"DOC( + @signature def to_tableau(self, *, ignore_noise: bool = False, ignore_measurement: bool = False, ignore_reset: bool = False) -> stim.Tableau: + Converts the circuit into an equivalent stabilizer tableau. + + Args: + ignore_noise: Defaults to False. When False, any noise operations in the + circuit will cause the conversion to fail with an exception. When True, + noise operations are skipped over as if they weren't even present in the + circuit. + ignore_measurement: Defaults to False. When False, any measurement + operations in the circuit will cause the conversion to fail with an + exception. When True, measurement operations are skipped over as if they + weren't even present in the circuit. + ignore_reset: Defaults to False. When False, any reset operations in the + circuit will cause the conversion to fail with an exception. When True, + reset operations are skipped over as if they weren't even present in the + circuit. + + Returns: + A tableau equivalent to the circuit (up to global phase). + + Raises: + ValueError: + The circuit contains noise operations but ignore_noise=False. + OR + The circuit contains measurement operations but + ignore_measurement=False. + OR + The circuit contains reset operations but ignore_reset=False. + + Examples: + >>> import stim + >>> stim.Circuit(''' + ... H 0 + ... CNOT 0 1 + ... ''').to_tableau() + stim.Tableau.from_conjugated_generators( + xs=[ + stim.PauliString("+Z_"), + stim.PauliString("+_X"), + ], + zs=[ + stim.PauliString("+XX"), + stim.PauliString("+ZZ"), + ], + ) + )DOC") + .data()); + + c.def( + "to_qasm", + [](const Circuit &self, int open_qasm_version, bool skip_dets_and_obs) -> std::string { + std::stringstream out; + export_open_qasm(self, out, open_qasm_version, skip_dets_and_obs); + return out.str(); + }, + pybind11::kw_only(), + pybind11::arg("open_qasm_version") = 3, + pybind11::arg("skip_dets_and_obs") = false, + clean_doc_string(R"DOC( + @signature def to_qasm(self, *, open_qasm_version: int, skip_dets_and_obs: bool = False) -> str: + Creates an equivalent OpenQASM implementation of the circuit. + + Args: + open_qasm_version: Defaults to 3. The version of OpenQASM to target. + This should be set to 2 or to 3. + + Differences between the versions are: + - Support for operations on classical bits operations (only version + 3). This means DETECTOR and OBSERVABLE_INCLUDE only work with + version 3. + - Support for feedback operations (only version 3). + - Support for subroutines (only version 3). Without subroutines, + non-standard dissipative gates like MR and RX need to decompose + inline every single time they're used. + - Minor name changes (e.g. creg -> bit, qelib1.inc -> stdgates.inc). + skip_dets_and_obs: Defaults to False. When set to False, the output will + include a `dets` register and an `obs` register (assuming the circuit + has detectors and observables). These registers will be computed as part + of running the circuit. This requires performing a simulation of the + circuit, in order to correctly account for the expected value of + measurements. + + When set to True, the `dets` and `obs` registers are not included in the + output, and no simulation of the circuit is performed. + + Returns: + The OpenQASM code as a string. + + Examples: + >>> import stim + >>> circuit = stim.Circuit(""" + ... R 0 1 + ... H 0 + ... CX 0 1 + ... M 0 1 + ... DETECTOR rec[-1] rec[-2] + ... """); + >>> qasm = circuit.to_qasm(); + >>> print(qasm.strip().replace('\n\n', '\n')) + OPENQASM 3.0; + include "stdgates.inc"; + qubit q[2]; + bit m[2]; + bit dets[1]; + reset q[0]; + reset q[1]; + h q[0]; + cx q[0], q[1]; + measure q[0] -> m[0]; + measure q[1] -> m[1]; + dets[0] = rec[1] ^ rec[0] ^ 0; + )DOC") + .data()); + c.def( "__len__", [](const Circuit &self) { diff --git a/src/stim/circuit/circuit_pybind_test.py b/src/stim/circuit/circuit_pybind_test.py index 70772b4cc..8d369b5ac 100644 --- a/src/stim/circuit/circuit_pybind_test.py +++ b/src/stim/circuit/circuit_pybind_test.py @@ -1573,3 +1573,63 @@ def test_detslice_filter_coords_flexibility(): assert str(d1) == str(d3) assert str(d1) == str(d4) assert str(d1) == str(d5) + + +def test_to_qasm(): + c = stim.Circuit(""" + RX 0 1 + TICK + H 1 + CX 0 1 + TICK + M 0 1 + DETECTOR rec[-1] rec[-2] + C_XYZ 0 + """) + + assert c.to_qasm().strip() == """ +OPENQASM 3.0; + +include "stdgates.inc"; +gate cxyz q0 { U(pi/2, 0, pi/2) q0; } +def rx(qubit q0) { reset q0; h q0; } + +qubit q[2]; +bit m[2]; +bit dets[1]; + +rx(q[0]); +rx(q[1]); +barrier q; + +h q[1]; +cx q[0], q[1]; +barrier q; + +measure q[0] -> m[0]; +measure q[1] -> m[1]; +dets[0] = rec[1] ^ rec[0] ^ 0; +cxyz q[0]; + """.strip() + + assert c.to_qasm(open_qasm_version=2, skip_dets_and_obs=True).strip() == """ +OPENQASM 2.0; + +include "qelib1.inc"; +gate cxyz q0 { U(pi/2, 0, pi/2) q0; } + +qreg q[2]; +creg m[2]; + +reset q[0]; h q[0]; // decomposed RX +reset q[1]; h q[1]; // decomposed RX +barrier q; + +h q[1]; +cx q[0], q[1]; +barrier q; + +measure q[0] -> m[0]; +measure q[1] -> m[1]; +cxyz q[0]; + """.strip() diff --git a/src/stim/circuit/export_qasm.cc b/src/stim/circuit/export_qasm.cc new file mode 100644 index 000000000..019fcda3c --- /dev/null +++ b/src/stim/circuit/export_qasm.cc @@ -0,0 +1,530 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stim/circuit/export_qasm.h" + +#include + +#include "stim/simulators/tableau_simulator.h" + +using namespace stim; + +static void do_decomposed( + GateType g, const char *q0_name, const char *q1_name, const char *m_name, std::ostream &out) { + auto q2n = [&](GateTarget t) { + return t.qubit_value() == 0 ? q0_name : q1_name; + }; + bool first = true; + for (const auto &inst : Circuit(GATE_DATA[g].h_s_cx_m_r_decomposition).operations) { + switch (inst.gate_type) { + case GateType::S: + for (const auto &t : inst.targets) { + if (!first) { + out << " "; + } + first = false; + out << "s " << q2n(t) << ";"; + } + break; + case GateType::H: + for (const auto &t : inst.targets) { + if (!first) { + out << " "; + } + first = false; + out << "h " << q2n(t) << ";"; + } + break; + case GateType::R: + for (const auto &t : inst.targets) { + if (!first) { + out << " "; + } + first = false; + out << "reset " << q2n(t) << ";"; + } + break; + case GateType::CX: + for (size_t k = 0; k < inst.targets.size(); k += 2) { + if (!first) { + out << " "; + } + first = false; + auto t1 = inst.targets[k]; + auto t2 = inst.targets[k + 1]; + out << "cx " << q2n(t1) << ", " << q2n(t2) << ";"; + } + break; + case GateType::M: + for (const auto &t : inst.targets) { + if (!first) { + out << " "; + } + first = false; + out << "measure " << q2n(t) << " -> " << m_name << ";"; + } + break; + default: + throw std::invalid_argument("Unhandled: " + inst.str()); + } + } +} + +static void do_qasm_decompose_mpp( + const CircuitInstruction &inst, size_t num_qubits, uint64_t &measurement_offset, std::ostream &out) { + decompose_mpp_operation( + inst, + num_qubits, + [&](const CircuitInstruction &h_xz, + const CircuitInstruction &h_yz, + const CircuitInstruction &cnot, + const CircuitInstruction &meas) { + for (const auto t : h_xz.targets) { + out << "h q[" << t.qubit_value() << "];"; + } + for (const auto t : h_yz.targets) { + out << "sx q[" << t.qubit_value() << "];"; + } + for (size_t k = 0; k < cnot.targets.size(); k += 2) { + auto t1 = cnot.targets[k]; + auto t2 = cnot.targets[k + 1]; + out << "cx q[" << t1.qubit_value() << "],q[" << t2.qubit_value() << "];"; + } + for (auto t : meas.targets) { + out << "measure q[" << t.qubit_value() << "] -> m[" << measurement_offset << "];"; + measurement_offset++; + } + for (size_t k = 0; k < cnot.targets.size(); k += 2) { + auto t1 = cnot.targets[k]; + auto t2 = cnot.targets[k + 1]; + out << "cx q[" << t1.qubit_value() << "],q[" << t2.qubit_value() << "];"; + } + for (const auto t : h_yz.targets) { + out << "sxdg q[" << t.qubit_value() << "];"; + } + for (const auto t : h_xz.targets) { + out << "h q[" << t.qubit_value() << "];"; + } + out << " // decomposed MPP\n"; + }); +} + +static void qasm_output_decomposed_inline( + const CircuitInstruction &instruction, + const std::array &qasm_names, + uint64_t &measurement_offset, + std::ostream &out, + bool decompose_inline) { + auto f = GATE_DATA[instruction.gate_type].flags; + std::stringstream q0; + std::stringstream q1; + std::stringstream m; + auto step = (f & GATE_TARGETS_PAIRS) ? 2 : 1; + for (size_t k = 0; k < instruction.targets.size(); k += step) { + auto t0 = instruction.targets[k]; + auto t1 = instruction.targets[k + step - 1]; + if (decompose_inline) { + q0.str(""); + q1.str(""); + q0 << "q[" << t0.qubit_value() << "]"; + q1 << "q[" << t1.qubit_value() << "]"; + if (f & GATE_PRODUCES_RESULTS) { + m.str(""); + m << "m[" << measurement_offset << "]"; + measurement_offset++; + } + do_decomposed( + instruction.gate_type, q0.str().data(), q1.str().data(), m.str().data(), out); + out << " // decomposed " << GATE_DATA[instruction.gate_type].name << "\n"; + } else { + if (f & GATE_PRODUCES_RESULTS) { + out << "m[" << measurement_offset << "] = "; + measurement_offset++; + } + out << qasm_names[(int)instruction.gate_type] << "("; + out << "q[" << t0.qubit_value() << "]"; + if (step == 2) { + out << ", q[" << t1.qubit_value() << "]"; + } + out << ");\n"; + } + } +} + +static void qasm_output_two_qubit_unitary( + const CircuitInstruction &instruction, + const std::array &qasm_names, + uint64_t &measurement_offset, + int open_qasm_version, + std::ostream &out) { + for (size_t k = 0; k < instruction.targets.size(); k += 2) { + auto t1 = instruction.targets[k]; + auto t2 = instruction.targets[k + 1]; + if (t1.is_qubit_target() && t2.is_qubit_target()) { + out << qasm_names[(int)instruction.gate_type] << " q[" << t1.qubit_value() << "], q[" << t2.qubit_value() + << "];\n"; + } else if (t1.is_qubit_target() || t2.is_qubit_target()) { + if (open_qasm_version == 2) { + throw std::invalid_argument( + "The circuit contains feedback, but OPENQASM 2 doesn't support feedback.\n" + "You can use `stim.Circuit.with_inlined_feedback` to drop feedback operations.\n" + "Alternatively, pass the argument `open_qasm_version=3`."); + } + GateTarget control; + GateTarget target; + char basis; + switch (instruction.gate_type) { + case GateType::CX: + basis = 'X'; + control = t1; + target = t2; + break; + case GateType::CY: + basis = 'Y'; + control = t1; + target = t2; + break; + case GateType::CZ: + basis = 'Z'; + control = t1; + target = t2; + if (control.is_qubit_target()) { + std::swap(control, target); + } + break; + case GateType::XCZ: + basis = 'X'; + control = t2; + target = t1; + break; + case GateType::YCZ: + basis = 'Y'; + control = t2; + target = t1; + break; + default: + throw std::invalid_argument("Not implemented: " + instruction.str()); + } + out << "if ("; + if (t1.is_measurement_record_target()) { + out << "ms[" << (measurement_offset + t1.rec_offset()) << "]"; + } else if (t1.is_sweep_bit_target()) { + out << "sweep[" << t1.value() << "]"; + } else { + throw std::invalid_argument("Not implemented: " + instruction.str()); + } + out << ") {\n"; + out << " " << basis << " q[" << target.qubit_value() << "];\n"; + out << "}\n"; + } + } +} + +static void define_custom_single_qubit_gate( + GateType g, + const char *name, + std::array &qasm_names, + const std::bitset &used, + std::ostream &out) { + const auto &gate = GATE_DATA[g]; + qasm_names[(int)g] = name; + if (!used[(int)g]) { + return; + } + + out << "gate " << name << " q0 { U("; + auto xyz = gate.to_euler_angles(); + std::array angles{"0", "pi/2", "pi", "-pi/2"}; + out << angles[round(xyz[0] / 3.14159265359f)]; + out << ", " << angles[round(xyz[1] / 3.14159265359f)]; + out << ", " << angles[round(xyz[2] / 3.14159265359f)]; + out << ") q0; }\n"; +} + +static void define_custom_decomposed_gate( + GateType g, + const char *name, + std::array &qasm_names, + const std::bitset &used, + std::ostream &out, + int open_qasm_version) { + const auto &gate = GATE_DATA[g]; + qasm_names[(int)g] = name; + if (!used[(int)g]) { + return; + } + + Circuit c(gate.h_s_cx_m_r_decomposition); + bool is_unitary = true; + for (const auto &inst : c.operations) { + is_unitary &= (GATE_DATA[inst.gate_type].flags & GATE_IS_UNITARY) != 0; + } + auto num_measurements = c.count_measurements(); + if (is_unitary) { + out << "gate " << name; + out << " q0"; + if (gate.flags & GateFlags::GATE_TARGETS_PAIRS) { + out << ", q1"; + } + out << " { "; + } else { + if (open_qasm_version == 2) { + // Have to decompose inline in the circuit. + return; + } + out << "def " << name << "(qubit q0"; + if (gate.flags & GateFlags::GATE_TARGETS_PAIRS) { + out << ", qubit q1"; + } + out << ")"; + if (num_measurements > 1) { + throw std::invalid_argument("Multiple measurement gates not supported."); + } else if (num_measurements == 1) { + out << " -> bit { bit b; "; + } else { + out << " { "; + } + } + + do_decomposed(g, "q0", "q1", "b", out); + if (num_measurements > 0) { + out << " return b;"; + } + out << " }\n"; +} + +static void collect_used_gates(const Circuit &c, std::bitset &used) { + for (const auto &inst : c.operations) { + used[(int)inst.gate_type] = true; + if (inst.gate_type == GateType::REPEAT) { + collect_used_gates(inst.repeat_block_body(c), used); + } + } +} + +static std::array define_qasm_gates( + std::ostream &out, const std::bitset &used, int open_qasm_version) { + std::array qasm_names; + + if (open_qasm_version == 2) { + out << "include \"qelib1.inc\";\n"; + } else if (open_qasm_version == 3) { + out << "include \"stdgates.inc\";\n"; + } else { + throw std::invalid_argument("Unrecognized open_qasm_version."); + } + qasm_names[(int)GateType::I] = "id"; + qasm_names[(int)GateType::X] = "x"; + qasm_names[(int)GateType::Y] = "y"; + qasm_names[(int)GateType::Z] = "z"; + qasm_names[(int)GateType::SQRT_X] = "sx"; + qasm_names[(int)GateType::SQRT_X_DAG] = "sxdg"; + qasm_names[(int)GateType::S] = "s"; + qasm_names[(int)GateType::S_DAG] = "sdg"; + qasm_names[(int)GateType::CX] = "cx"; + qasm_names[(int)GateType::CY] = "cy"; + qasm_names[(int)GateType::CZ] = "cz"; + qasm_names[(int)GateType::SWAP] = "swap"; + qasm_names[(int)GateType::H] = "h"; + define_custom_single_qubit_gate(GateType::C_XYZ, "cxyz", qasm_names, used, out); + define_custom_single_qubit_gate(GateType::C_ZYX, "czyx", qasm_names, used, out); + define_custom_single_qubit_gate(GateType::SQRT_Y, "sy", qasm_names, used, out); + define_custom_single_qubit_gate(GateType::SQRT_Y_DAG, "sydg", qasm_names, used, out); + define_custom_single_qubit_gate(GateType::H_XY, "hxy", qasm_names, used, out); + define_custom_single_qubit_gate(GateType::H_YZ, "hyz", qasm_names, used, out); + define_custom_decomposed_gate(GateType::XCX, "xcx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::XCY, "xcy", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::XCZ, "xcz", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::YCX, "ycx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::YCY, "ycy", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::YCZ, "ycz", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_XX, "sxx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_XX_DAG, "sxxdg", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_YY, "syy", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_YY_DAG, "syydg", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_ZZ, "szz", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SQRT_ZZ_DAG, "szzdg", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::ISWAP, "iswap", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::CXSWAP, "cxswap", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::SWAPCX, "swapcx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::ISWAP_DAG, "iswapdg", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MX, "mx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MY, "my", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MRX, "mrx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MRY, "mry", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MR, "mr", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::RX, "rx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::RY, "ry", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MXX, "mxx", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MYY, "myy", qasm_names, used, out, open_qasm_version); + define_custom_decomposed_gate(GateType::MZZ, "mzz", qasm_names, used, out, open_qasm_version); + out << "\n"; + + return qasm_names; +} + +void stim::export_open_qasm(const Circuit &circuit, std::ostream &out, int open_qasm_version, bool skip_dets_and_obs) { + auto stats = circuit.compute_stats(); + std::bitset used; + collect_used_gates(circuit, used); + if (open_qasm_version != 2 && open_qasm_version != 3) { + throw std::invalid_argument("Only open_qasm_version=2 and open_qasm_version=3 are supported."); + } + + // Header. + if (open_qasm_version == 2) { + out << "OPENQASM 2.0;\n"; + } else { + out << "OPENQASM 3.0;\n"; + } + out << "\n"; + + // Gate definitions. + std::array qasm_names = define_qasm_gates(out, used, open_qasm_version); + + // Storage. + const char *qubit_decl = "qubit"; + const char *bit_decl = "bit"; + if (open_qasm_version == 2) { + qubit_decl = "qreg"; + bit_decl = "creg"; + } + if (stats.num_qubits > 0) { + out << qubit_decl << " q[" << stats.num_qubits << "];\n"; + } + if (stats.num_measurements > 0) { + out << bit_decl << " m[" << stats.num_measurements << "];\n"; + } + if (stats.num_detectors > 0 && !skip_dets_and_obs) { + out << bit_decl << " dets[" << stats.num_detectors << "];\n"; + } + if (stats.num_observables > 0 && !skip_dets_and_obs) { + out << bit_decl << " obs[" << stats.num_observables << "];\n"; + } + if (stats.num_sweep_bits > 0) { + out << bit_decl << " sweep[" << stats.num_sweep_bits << "];\n"; + } + out << "\n"; + + simd_bits<64> reference_sample(stats.num_measurements); + if (stats.num_detectors > 0 || stats.num_observables > 0) { + reference_sample = TableauSimulator<64>::reference_sample_circuit(circuit); + } + + // Body. + uint64_t measurement_offset = 0; + uint64_t detector_offset = 0; + circuit.for_each_operation([&](const CircuitInstruction &instruction) { + GateFlags f = GATE_DATA[instruction.gate_type].flags; + + switch (instruction.gate_type) { + case GateType::QUBIT_COORDS: + case GateType::SHIFT_COORDS: + // Skipped. + return; + + case GateType::MPAD: + measurement_offset += instruction.count_measurement_results(); + return; + + case GateType::TICK: + out << "barrier q;\n\n"; + return; + + case GateType::M: + for (const auto &t : instruction.targets) { + out << "measure q[" << t.qubit_value() << "] -> m[" << measurement_offset << "];\n"; + measurement_offset++; + } + return; + case GateType::R: + for (const auto &t : instruction.targets) { + out << "reset q[" << t.qubit_value() << "];\n"; + } + return; + case GateType::DETECTOR: + case GateType::OBSERVABLE_INCLUDE: { + if (skip_dets_and_obs) { + return; + } + if (open_qasm_version == 2) { + throw std::invalid_argument( + "The circuit contains detectors or observables, but OPENQASM 2 doesn't support the operations " + "needed for accumulating detector and observable values.\n" + "To simply ignore detectors and observables, pass the argument `skip_dets_and_obs=True`.\n" + "Alternatively, pass the argument `open_qasm_version=3`."); + } + if (instruction.gate_type == GateType::DETECTOR) { + out << "dets[" << detector_offset << "] = "; + detector_offset++; + } else { + out << "obs[" << (int)instruction.args[0] << "] = obs[" << (int)instruction.args[0] << "] ^ "; + } + + int ref_value = 0; + for (auto t : instruction.targets) { + assert(t.is_measurement_record_target()); + auto i = measurement_offset + t.rec_offset(); + ref_value ^= reference_sample[i]; + out << "rec[" << (measurement_offset + t.rec_offset()) << "] ^ "; + } + out << ref_value << ";\n"; + return; + } + + 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: + throw std::invalid_argument( + "The circuit contains noise, but OPENQASM 2 doesn't support noise operations.\n" + "Use `stim.Circuit.without_noise` to get a version of the circuit without noise."); + + case GateType::MPP: + do_qasm_decompose_mpp(instruction, stats.num_qubits, measurement_offset, out); + return; + + default: + break; + } + + if (f & (stim::GATE_IS_RESET | stim::GATE_PRODUCES_RESULTS)) { + qasm_output_decomposed_inline(instruction, qasm_names, measurement_offset, out, open_qasm_version == 2); + return; + } + + if (f & stim::GATE_IS_UNITARY) { + if (f & stim::GATE_IS_SINGLE_QUBIT_GATE) { + for (const auto &t : instruction.targets) { + assert(t.is_qubit_target()); + out << qasm_names[(int)instruction.gate_type] << " q[" << t.qubit_value() << "];\n"; + } + return; + } + if (f & stim::GATE_TARGETS_PAIRS) { + qasm_output_two_qubit_unitary(instruction, qasm_names, measurement_offset, skip_dets_and_obs, out); + return; + } + } + + throw std::invalid_argument("Not implemented: " + instruction.str()); + }); +} diff --git a/src/stim/circuit/export_qasm.h b/src/stim/circuit/export_qasm.h new file mode 100644 index 000000000..01454325d --- /dev/null +++ b/src/stim/circuit/export_qasm.h @@ -0,0 +1,28 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _STIM_CIRCUIT_EXPORT_CIRCUIT_H +#define _STIM_CIRCUIT_EXPORT_CIRCUIT_H + +#include "stim/circuit/circuit.h" + +namespace stim { + +void export_open_qasm(const Circuit &circuit, std::ostream &out, int open_qasm_version, bool skip_dets_and_obs); + +} // namespace stim + +#endif diff --git a/src/stim/circuit/export_qasm.test.cc b/src/stim/circuit/export_qasm.test.cc new file mode 100644 index 000000000..07b25b004 --- /dev/null +++ b/src/stim/circuit/export_qasm.test.cc @@ -0,0 +1,482 @@ +#include "stim/circuit/export_qasm.h" + +#include "gtest/gtest.h" + +#include "stim/circuit/circuit.test.h" +#include "stim/simulators/transform_without_feedback.h" + +using namespace stim; + +TEST(export_circuit, export_open_qasm_feedback) { + Circuit c(R"CIRCUIT( + H 0 + CX 0 1 + C_XYZ 1 + M 0 + CX rec[-1] 1 + CX sweep[5] 1 + TICK + H 0 + )CIRCUIT"); + std::stringstream out; + export_open_qasm(c, out, 3, false); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 3.0; + +include "stdgates.inc"; +gate cxyz q0 { U(pi/2, 0, pi/2) q0; } + +qubit q[2]; +bit m[1]; +bit sweep[6]; + +h q[0]; +cx q[0], q[1]; +cxyz q[1]; +measure q[0] -> m[0]; +if (ms[0]) { + X q[1]; +} +if (sweep[5]) { + X q[1]; +} +barrier q; + +h q[0]; +)QASM"); +} + +TEST(export_circuit, export_open_qasm_qec) { + Circuit c(R"CIRCUIT( + R 0 1 2 + TICK + X 0 + TICK + CX 0 1 + TICK + CX 2 1 + TICK + M 1 + DETECTOR rec[-1] + TICK + R 1 + TICK + CX 0 1 + TICK + CX 2 1 + TICK + M 0 1 2 + DETECTOR rec[-2] rec[-4] + DETECTOR rec[-1] rec[-2] rec[-3] + OBSERVABLE_INCLUDE(0) rec[-1] + )CIRCUIT"); + + std::stringstream out; + export_open_qasm(c, out, 3, false); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 3.0; + +include "stdgates.inc"; + +qubit q[3]; +bit m[4]; +bit dets[3]; +bit obs[1]; + +reset q[0]; +reset q[1]; +reset q[2]; +barrier q; + +x q[0]; +barrier q; + +cx q[0], q[1]; +barrier q; + +cx q[2], q[1]; +barrier q; + +measure q[1] -> m[0]; +dets[0] = rec[0] ^ 1; +barrier q; + +reset q[1]; +barrier q; + +cx q[0], q[1]; +barrier q; + +cx q[2], q[1]; +barrier q; + +measure q[0] -> m[1]; +measure q[1] -> m[2]; +measure q[2] -> m[3]; +dets[1] = rec[2] ^ rec[0] ^ 0; +dets[2] = rec[3] ^ rec[2] ^ rec[1] ^ 0; +obs[0] = obs[0] ^ rec[3] ^ 0; +)QASM"); + + out.str(""); + export_open_qasm(c, out, 2, true); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 2.0; + +include "qelib1.inc"; + +qreg q[3]; +creg m[4]; + +reset q[0]; +reset q[1]; +reset q[2]; +barrier q; + +x q[0]; +barrier q; + +cx q[0], q[1]; +barrier q; + +cx q[2], q[1]; +barrier q; + +measure q[1] -> m[0]; +barrier q; + +reset q[1]; +barrier q; + +cx q[0], q[1]; +barrier q; + +cx q[2], q[1]; +barrier q; + +measure q[0] -> m[1]; +measure q[1] -> m[2]; +measure q[2] -> m[3]; +)QASM"); +} + +TEST(export_circuit, export_qasm_decomposed_operations) { + Circuit c(R"CIRCUIT( + R 3 + RX 0 1 + MX 2 + TICK + + MXX 0 1 + DETECTOR rec[-1] + TICK + + M 2 + MR 3 + MRX 4 + )CIRCUIT"); + + std::stringstream out; + export_open_qasm(c, out, 3, false); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 3.0; + +include "stdgates.inc"; +def mx(qubit q0) -> bit { bit b; h q0; measure q0 -> b; h q0; return b; } +def mrx(qubit q0) -> bit { bit b; h q0; measure q0 -> b; reset q0; h q0; return b; } +def mr(qubit q0) -> bit { bit b; measure q0 -> b; reset q0; return b; } +def rx(qubit q0) { reset q0; h q0; } +def mxx(qubit q0, qubit q1) -> bit { bit b; cx q0, q1; h q0; measure q0 -> b; h q0; cx q0, q1; return b; } + +qubit q[5]; +bit m[5]; +bit dets[1]; + +reset q[3]; +rx(q[0]); +rx(q[1]); +m[0] = mx(q[2]); +barrier q; + +m[1] = mxx(q[0], q[1]); +dets[0] = rec[1] ^ 0; +barrier q; + +measure q[2] -> m[2]; +m[3] = mr(q[3]); +m[4] = mrx(q[4]); +)QASM"); + + out.str(""); + export_open_qasm(c, out, 2, true); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 2.0; + +include "qelib1.inc"; + +qreg q[5]; +creg m[5]; + +reset q[3]; +reset q[0]; h q[0]; // decomposed RX +reset q[1]; h q[1]; // decomposed RX +h q[2]; measure q[2] -> m[0]; h q[2]; // decomposed MX +barrier q; + +cx q[0], q[1]; h q[0]; measure q[0] -> m[1]; h q[0]; cx q[0], q[1]; // decomposed MXX +barrier q; + +measure q[2] -> m[2]; +measure q[3] -> m[3]; reset q[3]; // decomposed MR +h q[4]; measure q[4] -> m[4]; reset q[4]; h q[4]; // decomposed MRX +)QASM"); +} + +TEST(export_circuit, export_qasm_all_operations) { + Circuit c = generate_test_circuit_with_all_operations(); + c = c.without_noise(); + c = circuit_with_inlined_feedback(c); + + std::stringstream out; + export_open_qasm(c, out, 3, false); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 3.0; + +include "stdgates.inc"; +gate cxyz q0 { U(pi/2, 0, pi/2) q0; } +gate czyx q0 { U(pi/2, pi/2, pi/2) q0; } +gate sy q0 { U(pi/2, 0, 0) q0; } +gate sydg q0 { U(pi/2, pi/2, pi/2) q0; } +gate hxy q0 { U(pi/2, 0, pi/2) q0; } +gate hyz q0 { U(pi/2, pi/2, pi/2) q0; } +gate xcx q0, q1 { h q0; cx q0, q1; h q0; } +gate xcy q0, q1 { h q0; s q1; s q1; s q1; cx q0, q1; h q0; s q1; } +gate xcz q0, q1 { cx q1, q0; } +gate ycx q0, q1 { s q0; s q0; s q0; h q1; cx q1, q0; s q0; h q1; } +gate ycy q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q0; cx q0, q1; h q0; s q0; s q1; } +gate ycz q0, q1 { s q0; s q0; s q0; cx q1, q0; s q0; } +gate sxx q0, q1 { h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; } +gate sxxdg q0, q1 { h q0; cx q0, q1; h q1; s q0; s q0; s q0; s q1; s q1; s q1; h q0; h q1; } +gate syy q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; s q0; s q1; } +gate syydg q0, q1 { s q0; s q0; s q0; s q1; h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; s q0; s q1; s q1; s q1; } +gate szz q0, q1 { h q1; cx q0, q1; h q1; s q0; s q1; } +gate szzdg q0, q1 { h q1; cx q0, q1; h q1; s q0; s q0; s q0; s q1; s q1; s q1; } +gate iswap q0, q1 { h q0; cx q0, q1; cx q1, q0; h q1; s q1; s q0; } +gate cxswap q0, q1 { cx q1, q0; cx q0, q1; } +gate swapcx q0, q1 { cx q0, q1; cx q1, q0; } +gate iswapdg q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q1; cx q1, q0; cx q0, q1; h q0; } +def mx(qubit q0) -> bit { bit b; h q0; measure q0 -> b; h q0; return b; } +def my(qubit q0) -> bit { bit b; s q0; s q0; s q0; h q0; measure q0 -> b; h q0; s q0; return b; } +def mrx(qubit q0) -> bit { bit b; h q0; measure q0 -> b; reset q0; h q0; return b; } +def mry(qubit q0) -> bit { bit b; s q0; s q0; s q0; h q0; measure q0 -> b; reset q0; h q0; s q0; return b; } +def mr(qubit q0) -> bit { bit b; measure q0 -> b; reset q0; return b; } +def rx(qubit q0) { reset q0; h q0; } +def ry(qubit q0) { reset q0; h q0; s q0; } +def mxx(qubit q0, qubit q1) -> bit { bit b; cx q0, q1; h q0; measure q0 -> b; h q0; cx q0, q1; return b; } +def myy(qubit q0, qubit q1) -> bit { bit b; s q0; s q1; cx q0, q1; h q0; measure q0 -> b; s q1; s q1; h q0; cx q0, q1; s q0; s q1; return b; } +def mzz(qubit q0, qubit q1) -> bit { bit b; cx q0, q1; measure q1 -> b; cx q0, q1; return b; } + +qubit q[18]; +bit m[20]; +bit dets[1]; +bit obs[1]; + +id q[0]; +x q[1]; +y q[2]; +z q[3]; +barrier q; + +cxyz q[0]; +czyx q[1]; +hxy q[2]; +h q[3]; +hyz q[4]; +sx q[0]; +sxdg q[1]; +sy q[2]; +sydg q[3]; +s q[4]; +sdg q[5]; +barrier q; + +cxswap q[0], q[1]; +iswap q[2], q[3]; +iswapdg q[4], q[5]; +swap q[6], q[7]; +swapcx q[8], q[9]; +sxx q[0], q[1]; +sxxdg q[2], q[3]; +syy q[4], q[5]; +syydg q[6], q[7]; +szz q[8], q[9]; +szzdg q[10], q[11]; +xcx q[0], q[1]; +xcy q[2], q[3]; +xcz q[4], q[5]; +ycx q[6], q[7]; +ycy q[8], q[9]; +ycz q[10], q[11]; +cx q[12], q[13]; +cy q[14], q[15]; +cz q[16], q[17]; +barrier q; + +barrier q; + +h q[0];sx q[1];cx q[1],q[0];cx q[2],q[0];measure q[0] -> m[2];cx q[1],q[0];cx q[2],q[0];sxdg q[1];h q[0]; // decomposed MPP +cx q[1],q[0];measure q[0] -> m[3];cx q[1],q[0]; // decomposed MPP +m[4] = mrx(q[0]); +m[5] = mry(q[1]); +m[6] = mr(q[2]); +m[7] = mx(q[3]); +m[8] = my(q[4]); +measure q[5] -> m[9]; +measure q[6] -> m[10]; +rx(q[7]); +ry(q[8]); +reset q[9]; +barrier q; + +m[11] = mxx(q[0], q[1]); +m[12] = mxx(q[2], q[3]); +m[13] = myy(q[4], q[5]); +m[14] = mzz(q[6], q[7]); +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +barrier q; + +m[15] = mr(q[0]); +m[16] = mr(q[0]); +dets[0] = rec[16] ^ 0; +obs[0] = obs[0] ^ rec[16] ^ 0; +barrier q; + +)QASM"); + + out.str(""); + export_open_qasm(c, out, 2, true); + ASSERT_EQ(out.str(), R"QASM(OPENQASM 2.0; + +include "qelib1.inc"; +gate cxyz q0 { U(pi/2, 0, pi/2) q0; } +gate czyx q0 { U(pi/2, pi/2, pi/2) q0; } +gate sy q0 { U(pi/2, 0, 0) q0; } +gate sydg q0 { U(pi/2, pi/2, pi/2) q0; } +gate hxy q0 { U(pi/2, 0, pi/2) q0; } +gate hyz q0 { U(pi/2, pi/2, pi/2) q0; } +gate xcx q0, q1 { h q0; cx q0, q1; h q0; } +gate xcy q0, q1 { h q0; s q1; s q1; s q1; cx q0, q1; h q0; s q1; } +gate xcz q0, q1 { cx q1, q0; } +gate ycx q0, q1 { s q0; s q0; s q0; h q1; cx q1, q0; s q0; h q1; } +gate ycy q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q0; cx q0, q1; h q0; s q0; s q1; } +gate ycz q0, q1 { s q0; s q0; s q0; cx q1, q0; s q0; } +gate sxx q0, q1 { h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; } +gate sxxdg q0, q1 { h q0; cx q0, q1; h q1; s q0; s q0; s q0; s q1; s q1; s q1; h q0; h q1; } +gate syy q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; s q0; s q1; } +gate syydg q0, q1 { s q0; s q0; s q0; s q1; h q0; cx q0, q1; h q1; s q0; s q1; h q0; h q1; s q0; s q1; s q1; s q1; } +gate szz q0, q1 { h q1; cx q0, q1; h q1; s q0; s q1; } +gate szzdg q0, q1 { h q1; cx q0, q1; h q1; s q0; s q0; s q0; s q1; s q1; s q1; } +gate iswap q0, q1 { h q0; cx q0, q1; cx q1, q0; h q1; s q1; s q0; } +gate cxswap q0, q1 { cx q1, q0; cx q0, q1; } +gate swapcx q0, q1 { cx q0, q1; cx q1, q0; } +gate iswapdg q0, q1 { s q0; s q0; s q0; s q1; s q1; s q1; h q1; cx q1, q0; cx q0, q1; h q0; } + +qreg q[18]; +creg m[20]; + +id q[0]; +x q[1]; +y q[2]; +z q[3]; +barrier q; + +cxyz q[0]; +czyx q[1]; +hxy q[2]; +h q[3]; +hyz q[4]; +sx q[0]; +sxdg q[1]; +sy q[2]; +sydg q[3]; +s q[4]; +sdg q[5]; +barrier q; + +cxswap q[0], q[1]; +iswap q[2], q[3]; +iswapdg q[4], q[5]; +swap q[6], q[7]; +swapcx q[8], q[9]; +sxx q[0], q[1]; +sxxdg q[2], q[3]; +syy q[4], q[5]; +syydg q[6], q[7]; +szz q[8], q[9]; +szzdg q[10], q[11]; +xcx q[0], q[1]; +xcy q[2], q[3]; +xcz q[4], q[5]; +ycx q[6], q[7]; +ycy q[8], q[9]; +ycz q[10], q[11]; +cx q[12], q[13]; +cy q[14], q[15]; +cz q[16], q[17]; +barrier q; + +barrier q; + +h q[0];sx q[1];cx q[1],q[0];cx q[2],q[0];measure q[0] -> m[2];cx q[1],q[0];cx q[2],q[0];sxdg q[1];h q[0]; // decomposed MPP +cx q[1],q[0];measure q[0] -> m[3];cx q[1],q[0]; // decomposed MPP +h q[0]; measure q[0] -> m[4]; reset q[0]; h q[0]; // decomposed MRX +s q[1]; s q[1]; s q[1]; h q[1]; measure q[1] -> m[5]; reset q[1]; h q[1]; s q[1]; // decomposed MRY +measure q[2] -> m[6]; reset q[2]; // decomposed MR +h q[3]; measure q[3] -> m[7]; h q[3]; // decomposed MX +s q[4]; s q[4]; s q[4]; h q[4]; measure q[4] -> m[8]; h q[4]; s q[4]; // decomposed MY +measure q[5] -> m[9]; +measure q[6] -> m[10]; +reset q[7]; h q[7]; // decomposed RX +reset q[8]; h q[8]; s q[8]; // decomposed RY +reset q[9]; +barrier q; + +cx q[0], q[1]; h q[0]; measure q[0] -> m[11]; h q[0]; cx q[0], q[1]; // decomposed MXX +cx q[2], q[3]; h q[2]; measure q[2] -> m[12]; h q[2]; cx q[2], q[3]; // decomposed MXX +s q[4]; s q[5]; cx q[4], q[5]; h q[4]; measure q[4] -> m[13]; s q[5]; s q[5]; h q[4]; cx q[4], q[5]; s q[4]; s q[5]; // decomposed MYY +cx q[6], q[7]; measure q[7] -> m[14]; cx q[6], q[7]; // decomposed MZZ +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +h q[0]; +cx q[0], q[1]; +s q[1]; +barrier q; + +barrier q; + +measure q[0] -> m[15]; reset q[0]; // decomposed MR +measure q[0] -> m[16]; reset q[0]; // decomposed MR +barrier q; + +)QASM"); +} diff --git a/src/stim/circuit/gate_data_collapsing.cc b/src/stim/circuit/gate_data_collapsing.cc index 3f9a8b896..9eb273a41 100644 --- a/src/stim/circuit/gate_data_collapsing.cc +++ b/src/stim/circuit/gate_data_collapsing.cc @@ -379,7 +379,6 @@ Parens Arguments: .unitary_data = {}, .flow_data = {"1 -> +X"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( -H 0 R 0 H 0 )CIRCUIT", @@ -417,10 +416,6 @@ Parens Arguments: .unitary_data = {}, .flow_data = {"1 -> +Y"}, .h_s_cx_m_r_decomposition = R"CIRCUIT( -S 0 -S 0 -S 0 -H 0 R 0 H 0 S 0