From 03b0c3671bd050e66732ea1dedf6d0c16d22a701 Mon Sep 17 00:00:00 2001 From: nathandelcid Date: Thu, 4 Jun 2026 16:18:43 -0500 Subject: [PATCH] feat: add parameterized QASM3 exporter --- .../notes/qasm3-parameterized-export-146.yaml | 14 + samples/circuit_test.cpp | 3 +- samples/parameterized_circuit_test.cpp | 3 + src/circuit/quantumcircuit_def.hpp | 367 ------------- src/primitives/containers/sampler_pub.hpp | 9 +- src/providers/sqc_backend.hpp | 3 +- src/qasm3/exporter.hpp | 505 ++++++++++++++++++ test/test_circuit.cpp | 148 ++++- test/test_parameter.cpp | 1 + tutorials/circuit.ipynb | 5 +- 10 files changed, 679 insertions(+), 379 deletions(-) create mode 100644 releasenotes/notes/qasm3-parameterized-export-146.yaml create mode 100644 src/qasm3/exporter.hpp diff --git a/releasenotes/notes/qasm3-parameterized-export-146.yaml b/releasenotes/notes/qasm3-parameterized-export-146.yaml new file mode 100644 index 0000000..5a1d5e2 --- /dev/null +++ b/releasenotes/notes/qasm3-parameterized-export-146.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added the header-only ``Qiskit::qasm3::dumps`` API for exporting quantum + circuits as OpenQASM 3. Parameterized circuits can supply their ordered + free-parameter names so the exporter emits valid ``input float[64]`` + declarations. Names must be supplied explicitly because the current + Qiskit C API exposes the number of free symbols, but cannot enumerate + their names. +upgrade: + - | + Removed ``QuantumCircuit::to_qasm3``. Include ``qasm3/exporter.hpp`` and + call ``Qiskit::qasm3::dumps(circuit)`` instead. Parameterized circuits must + additionally pass the ordered names of their free parameters. diff --git a/samples/circuit_test.cpp b/samples/circuit_test.cpp index 80f6f29..6dad86c 100644 --- a/samples/circuit_test.cpp +++ b/samples/circuit_test.cpp @@ -19,6 +19,7 @@ #include "circuit/quantumcircuit.hpp" #include "circuit/library/quantum_volume.hpp" +#include "qasm3/exporter.hpp" using namespace Qiskit::circuit; @@ -71,7 +72,7 @@ int main() circ.measure(qr, cr); circ.draw(); - std::cout << circ.to_qasm3(); + std::cout << Qiskit::qasm3::dumps(circ); return 0; } diff --git a/samples/parameterized_circuit_test.cpp b/samples/parameterized_circuit_test.cpp index d29eb5a..928bed7 100644 --- a/samples/parameterized_circuit_test.cpp +++ b/samples/parameterized_circuit_test.cpp @@ -21,6 +21,7 @@ #include #include "circuit/quantumcircuit.hpp" +#include "qasm3/exporter.hpp" using namespace Qiskit::circuit; @@ -38,6 +39,8 @@ int main() circ.measure(qr, cr); + std::cout << Qiskit::qasm3::dumps(circ, {"t"}) << std::endl; + circ.print(); circ.draw(); diff --git a/src/circuit/quantumcircuit_def.hpp b/src/circuit/quantumcircuit_def.hpp index d8add04..706f45b 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -22,9 +22,6 @@ #include #include #include -#include -#include -#include #include "utils/types.hpp" #include "circuit/parameter.hpp" @@ -1535,370 +1532,6 @@ class QuantumCircuit return CircuitInstruction(); } - // qasm3 - - /// @brief Serialize a QuantumCircuit object as an OpenQASM3 string. - /// @return An OpenQASM3 string. - std::string to_qasm3(void) - { - add_pending_control_flow_op(); - - std::stringstream qasm3; - qasm3 << std::setprecision(18); - qasm3 << "OPENQASM 3.0;" << std::endl; - qasm3 << "include \"stdgates.inc\";" << std::endl; - - auto name_map = get_standard_gate_name_mapping(); - // add header for non-standard gates - bool cs = false; - bool sxdg = false; - QkOpCounts opcounts = qk_circuit_count_ops(rust_circuit_.get()); - for (int i = 0; i < opcounts.len; i++) { - if (opcounts.data[i].count != 0) { - auto op = name_map[opcounts.data[i].name].gate_map(); - switch (op) - { - case QkGate_R: - qasm3 << "gate r(p0, p1) _gate_q_0 {" << std::endl; - qasm3 << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_SXdg: - case QkGate_RYY: - case QkGate_XXPlusYY: - case QkGate_XXMinusYY: - if (!sxdg) - { - qasm3 << "gate sxdg _gate_q_0 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - sxdg = true; - } - if (op == QkGate_RYY) - { - qasm3 << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " sxdg _gate_q_0;" << std::endl; - qasm3 << " sxdg _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " sx _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - if (op == QkGate_XXPlusYY) - { - qasm3 << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " rz(p1) _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sxdg _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " rz(-p1) _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - } - if (op == QkGate_XXMinusYY) - { - qasm3 << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " rz(-p1) _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " ry(0.5*p0) _gate_q_0;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sxdg _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " rz(p1) _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - break; - case QkGate_DCX: - qasm3 << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_ECR: - qasm3 << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " x _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_ISwap: - qasm3 << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CSX: - case QkGate_CS: - if (!cs) - { - qasm3 << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " t _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " tdg _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " t _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - cs = true; - } - if (op == QkGate_CSX) - { - qasm3 << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cs _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - break; - case QkGate_CSdg: - qasm3 << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " tdg _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " t _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " tdg _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CCZ: - qasm3 << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RXX: - qasm3 << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RZX: - qasm3 << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RZZ: - qasm3 << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RCCX: - qasm3 << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << " t _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " tdg _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " t _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " tdg _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_C3X: - qasm3 << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_0;" << std::endl; - qasm3 << " p(pi/8) _gate_q_1;" << std::endl; - qasm3 << " p(pi/8) _gate_q_2;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " p(pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_C3SX: - qasm3 << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RC3X: - qasm3 << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CU1: - qasm3 << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cp(p0) _gate_q_0 _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CU3: - qasm3 << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cu(p0, p1, p2, 0) _gate_q_0 _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - default: - break; - } - } - } - qk_opcounts_clear(&opcounts); - - // save ops - uint_t nops; - nops = qk_circuit_num_instructions(rust_circuit_.get()); - - // Declare registers - // After transpilation, qubit registers will be mapped to physical registers, - // so we need to combined them in a single quantum register "q"; - const std::string qreg_name = "q"; - qasm3 << "qubit[" << num_qubits() << "] " << qreg_name << ";" << std::endl; - for(const auto& creg : cregs_) { - qasm3 << "bit[" << creg.size() << "] " << creg.name() << ";" << std::endl; - } - - auto recover_reg_data = [this](uint_t index) -> std::pair - { - auto it = std::upper_bound(cregs_.begin(), cregs_.end(), index, - [](uint_t v, const ClassicalRegister& reg) { return v < reg.base_index(); }); - assert(it != cregs_.begin()); - it = std::prev(it); - return std::make_pair(it->name(), index - it->base_index()); - }; - - for (uint_t i = 0; i < nops; i++) { - QkCircuitInstruction *op = new QkCircuitInstruction; - qk_circuit_get_instruction(rust_circuit_.get(), i, op); - if (op->num_clbits > 0) { - if (op->num_qubits == op->num_clbits) { - for (uint_t j = 0; j < op->num_qubits; j++) { - const auto creg_data = recover_reg_data(op->clbits[j]); - qasm3 << creg_data.first << "[" << creg_data.second << "] = " << op->name << " " << qreg_name << "[" << op->qubits[j] << "];" << std::endl; - } - } - } else { - if (strcmp(op->name, "u") == 0) { - qasm3 << "U"; - } else { - qasm3 << op->name; - } - if (op->num_params > 0) { - qasm3 << "("; - for (uint_t j = 0; j < op->num_params; j++) { - char* param = qk_param_str(op->params[j]); - qasm3 << param; - qk_str_free(param); - if (j != op->num_params - 1) - qasm3 << ", "; - } - qasm3 << ")"; - } - if (op->num_qubits > 0) { - qasm3 << " "; - for (uint_t j = 0; j < op->num_qubits; j++) { - qasm3 << qreg_name << "[" << op->qubits[j] << "]"; - if (j != op->num_qubits - 1) - qasm3 << ", "; - } - } - qasm3 << ";" << std::endl; - } - qk_circuit_instruction_clear(op); - } - - return qasm3.str(); - } - /// @brief print circuit (this is for debug) void print(void) const { diff --git a/src/primitives/containers/sampler_pub.hpp b/src/primitives/containers/sampler_pub.hpp index 1a37f6b..81c5f88 100644 --- a/src/primitives/containers/sampler_pub.hpp +++ b/src/primitives/containers/sampler_pub.hpp @@ -20,7 +20,8 @@ #include using json = nlohmann::json; -#include "circuit/quantumcircuit.hpp" +#include "circuit/quantumcircuit.hpp" +#include "qasm3/exporter.hpp" namespace Qiskit { @@ -107,9 +108,9 @@ class SamplerPub { nlohmann::ordered_json params = json::array(); if (shots_ > 0) { - return json::array({circuit_.to_qasm3(), params, shots_}); - } - return json::array({circuit_.to_qasm3(), params}); + return json::array({qasm3::dumps(circuit_), params, shots_}); + } + return json::array({qasm3::dumps(circuit_), params}); } }; diff --git a/src/providers/sqc_backend.hpp b/src/providers/sqc_backend.hpp index d20a80c..af611b0 100644 --- a/src/providers/sqc_backend.hpp +++ b/src/providers/sqc_backend.hpp @@ -24,6 +24,7 @@ #include "transpiler/target.hpp" #include "primitives/containers/sampler_pub.hpp" #include "providers/sqc_job.hpp" +#include "qasm3/exporter.hpp" #include "sqc_ecode.h" #include "sqc_api.h" @@ -90,7 +91,7 @@ class SQCBackend : public BackendV2 { std::shared_ptr run(std::vector& input_pubs, uint_t shots) override { auto circuit = input_pubs[0].circuit(); - const auto qasm3_str = circuit.to_qasm3(); + const auto qasm3_str = qasm3::dumps(circuit); std::cout << "run qasm3: \n" << qasm3_str << std::endl; // special modification of QASM3 for SQC diff --git a/src/qasm3/exporter.hpp b/src/qasm3/exporter.hpp new file mode 100644 index 0000000..5da87dc --- /dev/null +++ b/src/qasm3/exporter.hpp @@ -0,0 +1,505 @@ +/* +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024, 2026. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. +*/ + +#ifndef __qiskitcpp_qasm3_exporter_hpp__ +#define __qiskitcpp_qasm3_exporter_hpp__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "circuit/quantumcircuit.hpp" + +namespace Qiskit { +namespace qasm3 { +namespace detail { + +using circuit::ClassicalRegister; +using circuit::get_standard_gate_name_mapping; + +class CircuitInstruction { +private: + QkCircuitInstruction instruction_{}; + +public: + CircuitInstruction() = default; + CircuitInstruction(const CircuitInstruction&) = delete; + CircuitInstruction& operator=(const CircuitInstruction&) = delete; + + ~CircuitInstruction() + { + qk_circuit_instruction_clear(&instruction_); + } + + QkCircuitInstruction* get(void) + { + return &instruction_; + } + + QkCircuitInstruction& value(void) + { + return instruction_; + } +}; + +class OpCounts { +private: + QkOpCounts opcounts_; + +public: + explicit OpCounts(QkCircuit* circuit) + : opcounts_(qk_circuit_count_ops(circuit)) + {} + + OpCounts(const OpCounts&) = delete; + OpCounts& operator=(const OpCounts&) = delete; + + ~OpCounts() + { + qk_opcounts_clear(&opcounts_); + } + + QkOpCounts* operator->(void) + { + return &opcounts_; + } +}; + +class Exporter { +private: + circuit::QuantumCircuit& circuit_; + const std::vector& input_parameters_; + + void validate_input_parameters(void) const + { + if (input_parameters_.size() != circuit_.num_parameters()) { + throw std::invalid_argument("The number of QASM3 input parameter names must match the circuit parameter count"); + } + + std::unordered_set names; + for (const auto& name : input_parameters_) { + if (name.empty()) { + throw std::invalid_argument("QASM3 input parameter names cannot be empty"); + } + if (!names.insert(name).second) { + throw std::invalid_argument("QASM3 input parameter names must be unique"); + } + } + } + +public: + Exporter(circuit::QuantumCircuit& circuit, const std::vector& input_parameters) + : circuit_(circuit), input_parameters_(input_parameters) + {} + + std::string run(void) + { + auto rust_circuit_ = circuit_.get_rust_circuit(); + validate_input_parameters(); + const auto& cregs_ = circuit_.cregs(); + auto num_qubits = [this]() { return circuit_.num_qubits(); }; + + std::stringstream qasm3; + qasm3 << std::setprecision(18); + qasm3 << "OPENQASM 3.0;" << std::endl; + qasm3 << "include \"stdgates.inc\";" << std::endl; + for (const auto& input_parameter : input_parameters_) { + qasm3 << "input float[64] " << input_parameter << ";" << std::endl; + } + auto name_map = get_standard_gate_name_mapping(); + // add header for non-standard gates + bool cs = false; + bool sxdg = false; + OpCounts opcounts(rust_circuit_.get()); + for (int i = 0; i < opcounts->len; i++) { + if (opcounts->data[i].count != 0) { + auto op = name_map[opcounts->data[i].name].gate_map(); + switch (op) + { + case QkGate_R: + qasm3 << "gate r(p0, p1) _gate_q_0 {" << std::endl; + qasm3 << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_SXdg: + case QkGate_RYY: + case QkGate_XXPlusYY: + case QkGate_XXMinusYY: + if (!sxdg) + { + qasm3 << "gate sxdg _gate_q_0 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + sxdg = true; + } + if (op == QkGate_RYY) + { + qasm3 << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " sxdg _gate_q_0;" << std::endl; + qasm3 << " sxdg _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " sx _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + if (op == QkGate_XXPlusYY) + { + qasm3 << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " rz(p1) _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sxdg _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " rz(-p1) _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + } + if (op == QkGate_XXMinusYY) + { + qasm3 << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " rz(-p1) _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " ry(0.5*p0) _gate_q_0;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sxdg _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " rz(p1) _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + break; + case QkGate_DCX: + qasm3 << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_ECR: + qasm3 << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " x _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_ISwap: + qasm3 << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CSX: + case QkGate_CS: + if (!cs) + { + qasm3 << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " t _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " tdg _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " t _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + cs = true; + } + if (op == QkGate_CSX) + { + qasm3 << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cs _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + break; + case QkGate_CSdg: + qasm3 << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " tdg _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " t _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " tdg _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CCZ: + qasm3 << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RXX: + qasm3 << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RZX: + qasm3 << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RZZ: + qasm3 << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RCCX: + qasm3 << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << " t _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " tdg _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " t _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " tdg _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_C3X: + qasm3 << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_0;" << std::endl; + qasm3 << " p(pi/8) _gate_q_1;" << std::endl; + qasm3 << " p(pi/8) _gate_q_2;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " p(pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_C3SX: + qasm3 << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RC3X: + qasm3 << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CU1: + qasm3 << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cp(p0) _gate_q_0 _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CU3: + qasm3 << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cu(p0, p1, p2, 0) _gate_q_0 _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + default: + break; + } + } + } + // save ops + uint_t nops; + nops = qk_circuit_num_instructions(rust_circuit_.get()); + + // Declare registers + // After transpilation, qubit registers will be mapped to physical registers, + // so we need to combined them in a single quantum register "q"; + const std::string qreg_name = "q"; + qasm3 << "qubit[" << num_qubits() << "] " << qreg_name << ";" << std::endl; + for(const auto& creg : cregs_) { + qasm3 << "bit[" << creg.size() << "] " << creg.name() << ";" << std::endl; + } + + auto recover_reg_data = [&cregs_](uint_t index) -> std::pair + { + auto it = std::upper_bound(cregs_.begin(), cregs_.end(), index, + [](uint_t v, const ClassicalRegister& reg) { return v < reg.base_index(); }); + assert(it != cregs_.begin()); + it = std::prev(it); + return std::make_pair(it->name(), index - it->base_index()); + }; + + for (uint_t i = 0; i < nops; i++) { + CircuitInstruction instruction; + auto& op = instruction.value(); + qk_circuit_get_instruction(rust_circuit_.get(), i, instruction.get()); + if (op.num_clbits > 0) { + if (op.num_qubits == op.num_clbits) { + for (uint_t j = 0; j < op.num_qubits; j++) { + const auto creg_data = recover_reg_data(op.clbits[j]); + qasm3 << creg_data.first << "[" << creg_data.second << "] = " << op.name << " " << qreg_name << "[" << op.qubits[j] << "];" << std::endl; + } + } + } else { + if (strcmp(op.name, "u") == 0) { + qasm3 << "U"; + } else { + qasm3 << op.name; + } + if (op.num_params > 0) { + qasm3 << "("; + for (uint_t j = 0; j < op.num_params; j++) { + char* param = qk_param_str(op.params[j]); + qasm3 << param; + qk_str_free(param); + if (j != op.num_params - 1) + qasm3 << ", "; + } + qasm3 << ")"; + } + if (op.num_qubits > 0) { + qasm3 << " "; + for (uint_t j = 0; j < op.num_qubits; j++) { + qasm3 << qreg_name << "[" << op.qubits[j] << "]"; + if (j != op.num_qubits - 1) + qasm3 << ", "; + } + } + qasm3 << ";" << std::endl; + } + } + + return qasm3.str(); + } +}; + +} // namespace detail + +/// @brief Serialize a QuantumCircuit object as an OpenQASM3 string. +/// +/// The Qiskit C API exposes the number of free parameter symbols in a circuit, +/// but cannot enumerate their names. Callers must therefore supply the names +/// used by parameter expressions in the circuit. +/// @param circuit The circuit to serialize. +/// @param input_parameters Ordered names for the circuit's free input parameters. Names must match +/// the symbols used by the circuit and be valid OpenQASM3 identifiers. +/// @return An OpenQASM3 string. +inline std::string dumps(circuit::QuantumCircuit& circuit, const std::vector& input_parameters) +{ + return detail::Exporter(circuit, input_parameters).run(); +} + +/// @brief Serialize a QuantumCircuit without free parameters as an OpenQASM3 string. +/// @param circuit The circuit to serialize. +/// @return An OpenQASM3 string. +inline std::string dumps(circuit::QuantumCircuit& circuit) +{ + return dumps(circuit, {}); +} + +} // namespace qasm3 +} // namespace Qiskit + +#endif // __qiskitcpp_qasm3_exporter_hpp__ diff --git a/test/test_circuit.cpp b/test/test_circuit.cpp index eae4cd8..826d9cd 100644 --- a/test/test_circuit.cpp +++ b/test/test_circuit.cpp @@ -16,6 +16,7 @@ #include "common.hpp" #include "circuit/quantumcircuit.hpp" +#include "qasm3/exporter.hpp" using namespace Qiskit; using namespace Qiskit::circuit; @@ -612,7 +613,7 @@ static int test_compose(void) { return Ok; } -static int test_to_qasm3_multi_regs(void) { +static int test_qasm3_multi_regs(void) { auto qreg1 = QuantumRegister(2, std::string("q1")); auto qreg2 = QuantumRegister(1, std::string("q2")); auto creg1 = ClassicalRegister(2, std::string("c1")); @@ -622,7 +623,7 @@ static int test_to_qasm3_multi_regs(void) { circ.measure(qreg2, creg2); circ.measure(qreg1, creg1); - const auto actual = circ.to_qasm3(); + const auto actual = Qiskit::qasm3::dumps(circ); const std::string expected = "OPENQASM 3.0;\n" "include \"stdgates.inc\";\n" @@ -633,7 +634,7 @@ static int test_to_qasm3_multi_regs(void) { "c1[0] = measure q[0];\n" "c1[1] = measure q[1];\n"; if (actual != expected) { - std::cerr << " to_qasm3_multi_regs test : \n expected:\n" << expected + std::cerr << " qasm3_multi_regs test : \n expected:\n" << expected << "\n actual:\n" << actual << std::endl; return EqualityError; } @@ -641,6 +642,140 @@ static int test_to_qasm3_multi_regs(void) { return Ok; } +static int test_qasm3_parameter_symbol(void) { + auto qreg = QuantumRegister(1, "qreg"); + auto creg = ClassicalRegister(1, "c"); + auto circ = QuantumCircuit(qreg, creg); + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto actual = Qiskit::qasm3::dumps(circ, {"theta"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(theta) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_parameter_symbol test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_parameter_expression(void) { + auto qreg = QuantumRegister(1, "qreg"); + auto creg = ClassicalRegister(1, "c"); + auto circ = QuantumCircuit(qreg, creg); + Parameter theta("theta"); + circ.rx(theta + 0.5, 0); + + const auto actual = Qiskit::qasm3::dumps(circ, {"theta"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(0.5 + theta) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_parameter_expression test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_parameter_declaration_order(void) { + auto qreg = QuantumRegister(1, "qreg"); + auto creg = ClassicalRegister(1, "c"); + auto circ = QuantumCircuit(qreg, creg); + Parameter theta("theta"); + Parameter phi("phi"); + circ.rx(theta, 0); + circ.ry(phi, 0); + + const auto actual = Qiskit::qasm3::dumps(circ, {"phi", "theta"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] phi;\n" + "input float[64] theta;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(theta) q[0];\n" + "ry(phi) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_parameter_declaration_order test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_numeric_parameter(void) { + auto qreg = QuantumRegister(1, "qreg"); + auto creg = ClassicalRegister(1, "c"); + auto circ = QuantumCircuit(qreg, creg); + circ.rz(0.5, 0); + + const auto actual = Qiskit::qasm3::dumps(circ); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rz(0.5) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_numeric_parameter test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_input_parameter_validation(void) { + auto parameterized = QuantumCircuit(1, 1); + Parameter theta("theta"); + Parameter phi("phi"); + parameterized.rx(theta, 0); + parameterized.ry(phi, 0); + + try { + Qiskit::qasm3::dumps(parameterized); + return EqualityError; + } catch (const std::invalid_argument&) {} + + try { + Qiskit::qasm3::dumps(parameterized, {"theta"}); + return EqualityError; + } catch (const std::invalid_argument&) {} + + try { + Qiskit::qasm3::dumps(parameterized, {"", "phi"}); + return EqualityError; + } catch (const std::invalid_argument&) {} + + try { + Qiskit::qasm3::dumps(parameterized, {"theta", "theta"}); + return EqualityError; + } catch (const std::invalid_argument&) {} + + auto numeric = QuantumCircuit(1, 1); + try { + Qiskit::qasm3::dumps(numeric, {"theta"}); + return EqualityError; + } catch (const std::invalid_argument&) {} + + return Ok; +} + #if defined(_WIN32) int test_circuit(int argc, char** const argv) { #else @@ -651,7 +786,12 @@ int test_circuit(int argc, char** argv) { num_failed += RUN_TEST(test_measure); num_failed += RUN_TEST(test_append); num_failed += RUN_TEST(test_compose); - num_failed += RUN_TEST(test_to_qasm3_multi_regs); + num_failed += RUN_TEST(test_qasm3_multi_regs); + num_failed += RUN_TEST(test_qasm3_parameter_symbol); + num_failed += RUN_TEST(test_qasm3_parameter_expression); + num_failed += RUN_TEST(test_qasm3_parameter_declaration_order); + num_failed += RUN_TEST(test_qasm3_numeric_parameter); + num_failed += RUN_TEST(test_qasm3_input_parameter_validation); std::cerr << "=== Number of failed subtests: " << num_failed << std::endl; return num_failed; diff --git a/test/test_parameter.cpp b/test/test_parameter.cpp index bfe24bf..be98635 100644 --- a/test/test_parameter.cpp +++ b/test/test_parameter.cpp @@ -16,6 +16,7 @@ #include "common.hpp" +#include "qasm3/exporter.hpp" #include "circuit/parameter.hpp" using namespace Qiskit; using namespace Qiskit::circuit; diff --git a/tutorials/circuit.ipynb b/tutorials/circuit.ipynb index f38b9ac..af2c64a 100644 --- a/tutorials/circuit.ipynb +++ b/tutorials/circuit.ipynb @@ -57,7 +57,8 @@ "metadata": {}, "outputs": [], "source": [ - "#include \"circuit/quantumcircuit.hpp\"" + "#include \"circuit/quantumcircuit.hpp\"\n", + "#include \"qasm3/exporter.hpp\"" ] }, { @@ -199,7 +200,7 @@ } ], "source": [ - "std::cout << circ_with_reg.to_qasm3() << std::endl;" + "std::cout << Qiskit::qasm3::dumps(circ_with_reg) << std::endl;" ] }, {