From e48363db6540303396ad7a8f0eda4293cacb8b8c Mon Sep 17 00:00:00 2001 From: Roll249 Date: Thu, 4 Jun 2026 01:37:21 +0700 Subject: [PATCH] feat(qasm3): refactor to_qasm3 out of QuantumCircuit with parameter support Extract the `to_qasm3` implementation into a new `Qasm3Exporter` class (src/circuit/qasm3_exporter.hpp, src/circuit/qasm3_exporter.cpp) so that: 1. The `QuantumCircuit` class no longer needs to know about the textual QASM representation. 2. Parameterized circuits can now be serialized: `qk_param_str` is used to convert each gate parameter, preserving symbolic expressions (e.g. `rx(theta) q[0];`) and numeric values alike. New tests cover: - Symbolic parameter export (`rx(theta)`) - Compound parameter expressions (`rx(1.5 + 2*theta)`) - Numeric parameter export (unchanged behavior) - The `U` gate special-case remains intact Fixes: #146 Co-authored-by: Cursor --- .../notes/qasm3-parameterized-export-146.yaml | 20 + src/circuit/classicalregister.hpp | 1 + src/circuit/qasm3_exporter.cpp | 418 +++++++++++++++++ src/circuit/qasm3_exporter.hpp | 87 ++++ src/circuit/qasm3_exporter_impl.hpp | 422 ++++++++++++++++++ src/circuit/quantumcircuit_def.hpp | 369 +-------------- src/circuit/quantumregister.hpp | 1 + test/CMakeLists.txt | 3 +- test/test_circuit.cpp | 82 ++++ 9 files changed, 1041 insertions(+), 362 deletions(-) create mode 100644 releasenotes/notes/qasm3-parameterized-export-146.yaml create mode 100644 src/circuit/qasm3_exporter.cpp create mode 100644 src/circuit/qasm3_exporter.hpp create mode 100644 src/circuit/qasm3_exporter_impl.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..af57f2c --- /dev/null +++ b/releasenotes/notes/qasm3-parameterized-export-146.yaml @@ -0,0 +1,20 @@ +--- +features: + - | + Added support for exporting parameterized circuits in OpenQASM 3.0. The + ``QuantumCircuit::to_qasm3`` method now serializes a ``Parameter`` (or any + expression built from ``Parameter``s) as a symbolic identifier (e.g. + ``rx(theta) q[0];``) instead of attempting to stringify an unbound value. + +fixes: + - | + ``QuantumCircuit::to_qasm3`` could not export circuits containing + parameterized gates, which made parameterized circuits impossible to + round-trip through OpenQASM 3.0. Gate parameters are now serialized using + ``qk_param_str`` and therefore carry their symbolic expression (or numeric + value) faithfully. + + This also refactors the OpenQASM 3.0 export implementation out of the + ``QuantumCircuit`` class into a new ``Qasm3Exporter`` class + (``src/circuit/qasm3_exporter.hpp``), so the circuit class no longer needs + to know about the textual QASM representation. diff --git a/src/circuit/classicalregister.hpp b/src/circuit/classicalregister.hpp index bdcc146..774f6bf 100644 --- a/src/circuit/classicalregister.hpp +++ b/src/circuit/classicalregister.hpp @@ -18,6 +18,7 @@ #define __qiskitcpp_circuit_classical_register_hpp__ #include +#include #include "circuit/register.hpp" #include "qiskit.h" diff --git a/src/circuit/qasm3_exporter.cpp b/src/circuit/qasm3_exporter.cpp new file mode 100644 index 0000000..adaae3e --- /dev/null +++ b/src/circuit/qasm3_exporter.cpp @@ -0,0 +1,418 @@ +/* +# 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. +*/ + +// Implementation of `Qasm3Exporter` and `QuantumCircuit::to_qasm3` +// (refactored out of `QuantumCircuit::to_qasm3` per issue #146). + +#include "circuit/qasm3_exporter.hpp" +#include "circuit/quantumcircuit_def.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "circuit/classicalregister.hpp" +#include "circuit/library/standard_gates/standard_gates.hpp" + +namespace Qiskit { +namespace circuit { + +Qasm3Exporter::Qasm3Exporter(QuantumCircuit *circ) : circ_(circ) {} + +std::string Qasm3Exporter::run(void) { + std::stringstream out; + out << std::setprecision(18); + write_header(out); + write_gate_definitions(out); + write_registers(out); + write_instructions(out); + return out.str(); +} + +rust_circuit *Qasm3Exporter::rust_circuit_ptr(void) const { + return circ_->get_rust_circuit(false).get(); +} + +std::pair Qasm3Exporter::recover_creg_data( + const std::vector &cregs, uint_t index) { + 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()); +} + +std::string Qasm3Exporter::param_to_string(const QkParam *param) { + char *raw = qk_param_str(param); + std::string result; + if (raw != nullptr) { + result = raw; + qk_str_free(raw); + } + return result; +} + +void Qasm3Exporter::write_header(std::stringstream &out) const { + out << "OPENQASM 3.0;" << std::endl; + out << "include \"stdgates.inc\";" << std::endl; +} + +void Qasm3Exporter::write_gate_definitions(std::stringstream &out) { + auto name_map = get_standard_gate_name_mapping(); + bool cs = false; + bool sxdg = false; + + QkOpCounts opcounts = qk_circuit_count_ops(rust_circuit_ptr()); + for (int i = 0; i < opcounts.len; i++) { + if (opcounts.data[i].count == 0) { + continue; + } + const auto op = name_map[opcounts.data[i].name].gate_map(); + switch (op) { + case QkGate_R: + out << "gate r(p0, p1) _gate_q_0 {" << std::endl; + out << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_SXdg: + case QkGate_RYY: + case QkGate_XXPlusYY: + case QkGate_XXMinusYY: + if (!sxdg) { + out << "gate sxdg _gate_q_0 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << "}" << std::endl; + sxdg = true; + } + if (op == QkGate_RYY) { + out << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " sxdg _gate_q_0;" << std::endl; + out << " sxdg _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " sx _gate_q_0;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + if (op == QkGate_XXPlusYY) { + out << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + out << " rz(p1) _gate_q_0;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_0;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sxdg _gate_q_1;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " rz(-p1) _gate_q_0;" << std::endl; + out << "}" << std::endl; + } + if (op == QkGate_XXMinusYY) { + out << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + out << " rz(-p1) _gate_q_1;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sx _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " ry(0.5*p0) _gate_q_0;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sxdg _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " rz(p1) _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + break; + case QkGate_DCX: + out << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_ECR: + out << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " x _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_ISwap: + out << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CSX: + case QkGate_CS: + if (!cs) { + out << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; + out << " t _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " tdg _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " t _gate_q_1;" << std::endl; + out << "}" << std::endl; + cs = true; + } + if (op == QkGate_CSX) { + out << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cs _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + break; + case QkGate_CSdg: + out << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; + out << " tdg _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " t _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " tdg _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CCZ: + out << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RXX: + out << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RZX: + out << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RZZ: + out << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RCCX: + out << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << " t _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " tdg _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " t _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " tdg _gate_q_2;" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_C3X: + out << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_0;" << std::endl; + out << " p(pi/8) _gate_q_1;" << std::endl; + out << " p(pi/8) _gate_q_2;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " p(-pi/8) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " p(-pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " p(pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " p(-pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_C3SX: + out << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RC3X: + out << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CU1: + out << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cp(p0) _gate_q_0 _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CU3: + out << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cu(p0, p1, p2, 0) _gate_q_0 _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + default: + break; + } + } + qk_opcounts_clear(&opcounts); +} + +void Qasm3Exporter::write_registers(std::stringstream &out) const { + const std::string qreg_name = "q"; + out << "qubit[" << circ_->num_qubits() << "] " << qreg_name << ";" << std::endl; + for (const auto& creg : circ_->cregs()) { + out << "bit[" << creg.size() << "] " << creg.name() << ";" << std::endl; + } +} + +void Qasm3Exporter::write_instructions(std::stringstream &out) { + const std::string qreg_name = "q"; + const uint_t nops = qk_circuit_num_instructions(rust_circuit_ptr()); + + for (uint_t i = 0; i < nops; i++) { + QkCircuitInstruction *op = new QkCircuitInstruction; + qk_circuit_get_instruction(rust_circuit_ptr(), i, op); + + if (op->num_clbits > 0 && op->num_qubits == op->num_clbits) { + for (uint_t j = 0; j < op->num_qubits; j++) { + const auto creg_data = recover_creg_data(circ_->cregs(), op->clbits[j]); + out << creg_data.first << "[" << creg_data.second + << "] = " << op->name << " " + << qreg_name << "[" << op->qubits[j] << "];" << std::endl; + } + } else { + if (std::strcmp(op->name, "u") == 0) { + out << "U"; + } else { + out << op->name; + } + if (op->num_params > 0) { + out << "("; + for (uint_t j = 0; j < op->num_params; j++) { + out << param_to_string(op->params[j]); + if (j != op->num_params - 1) { + out << ", "; + } + } + out << ")"; + } + if (op->num_qubits > 0) { + out << " "; + for (uint_t j = 0; j < op->num_qubits; j++) { + out << qreg_name << "[" << op->qubits[j] << "]"; + if (j != op->num_qubits - 1) { + out << ", "; + } + } + } + out << ";" << std::endl; + } + qk_circuit_instruction_clear(op); + } +} + +// QuantumCircuit::to_qasm3 implementation. +std::string QuantumCircuit::to_qasm3(void) { + add_pending_control_flow_op(); + return Qasm3Exporter(this).run(); +} + +} // namespace circuit +} // namespace Qiskit diff --git a/src/circuit/qasm3_exporter.hpp b/src/circuit/qasm3_exporter.hpp new file mode 100644 index 0000000..b8cd19a --- /dev/null +++ b/src/circuit/qasm3_exporter.hpp @@ -0,0 +1,87 @@ +/* +# 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. +*/ + +// OpenQASM 3.0 exporter for QuantumCircuit. +// +// Refactored out of `QuantumCircuit::to_qasm3` (issue #146) so that the exporter +// logic lives outside the (very large) `QuantumCircuit` class definition. The +// exporter supports circuits that contain symbolic `Parameter` expressions, in +// addition to purely numeric ones, by serializing the `QkParam` value of each +// gate parameter with `qk_param_str` (which already returns the symbolic +// expression string for free symbols and their combinations). + +#ifndef __qiskitcpp_circuit_qasm3_exporter_hpp__ +#define __qiskitcpp_circuit_qasm3_exporter_hpp__ + +#include +#include +#include + +#include "qiskit.h" +#include "utils/types.hpp" +#include "circuit/classicalregister.hpp" + +// qiskit C-API circuit data +using rust_circuit = ::QkCircuit; + +// Forward declaration (must appear at global/namespace scope, outside any class). +namespace Qiskit { namespace circuit { class QuantumCircuit; } } + +// Exporter class declaration. +namespace Qiskit { +namespace circuit { + +/// @class Qasm3Exporter +/// @brief Serialize a `QuantumCircuit` as an OpenQASM 3.0 string. +/// +/// The exporter is intentionally implemented outside of `QuantumCircuit` so the +/// circuit class definition does not have to know about the textual QASM +/// representation. +/// +/// Currently the exporter fully supports all of the standard gates that +/// `QuantumCircuit` knows about, and works for both numeric and symbolic gate +/// parameters (i.e. `Parameter`, including compound expressions built from +/// free symbols). Free symbols are emitted as bare identifiers in the resulting +/// QASM source (e.g. `rx(theta) q[0];`). +class Qasm3Exporter { +public: + /// @brief Construct an exporter that targets the supplied circuit. + /// @param circ The circuit to serialize. + Qasm3Exporter(QuantumCircuit *circ); + + /// @brief Serialize the bound circuit as an OpenQASM 3.0 string. + /// @return The OpenQASM 3.0 representation of the circuit. + std::string run(void); + +private: + QuantumCircuit *circ_; + + rust_circuit *rust_circuit_ptr(void) const; + + static std::string param_to_string(const QkParam *param); + + void write_header(std::stringstream &out) const; + void write_gate_definitions(std::stringstream &out); + void write_registers(std::stringstream &out) const; + void write_instructions(std::stringstream &out); + // Note: using ::uint_t and ::ClassicalRegister since these are defined in + // the outer namespace scope by quantumcircuit_def.hpp after qiskit.h. + static std::pair recover_creg_data( + const std::vector &cregs, uint_t index); +}; + +} // namespace circuit +} // namespace Qiskit + +#endif // __qiskitcpp_circuit_qasm3_exporter_hpp__ diff --git a/src/circuit/qasm3_exporter_impl.hpp b/src/circuit/qasm3_exporter_impl.hpp new file mode 100644 index 0000000..1d933f8 --- /dev/null +++ b/src/circuit/qasm3_exporter_impl.hpp @@ -0,0 +1,422 @@ +/* +# 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. +*/ + +// Inline implementations for `Qasm3Exporter` (refactored out of +// `QuantumCircuit::to_qasm3` per issue #146). +// +// This file must be included AFTER the full `QuantumCircuit` class definition +// is available, but still within the `Qiskit::circuit` namespace scope. +// It is included at the end of `quantumcircuit_def.hpp`. + +#ifndef __qiskitcpp_circuit_qasm3_exporter_impl_hpp__ +#define __qiskitcpp_circuit_qasm3_exporter_impl_hpp__ + +#include "circuit/qasm3_exporter.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "circuit/classicalregister.hpp" +#include "circuit/library/standard_gates/standard_gates.hpp" + +namespace Qiskit { +namespace circuit { + +// Qasm3Exporter inline method implementations follow. + +inline Qasm3Exporter::Qasm3Exporter(QuantumCircuit *circ) : circ_(circ) {} + +inline std::string Qasm3Exporter::run(void) { + std::stringstream out; + out << std::setprecision(18); + write_header(out); + write_gate_definitions(out); + write_registers(out); + write_instructions(out); + return out.str(); +} + +inline rust_circuit *Qasm3Exporter::rust_circuit_ptr(void) { + return circ_->get_rust_circuit(false).get(); +} + +inline std::pair Qasm3Exporter::recover_creg_data( + const std::vector &cregs, uint_t index) { + 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()); +} + +inline std::string Qasm3Exporter::param_to_string(const QkParam *param) { + char *raw = qk_param_str(param); + std::string result; + if (raw != nullptr) { + result = raw; + qk_str_free(raw); + } + return result; +} + +inline void Qasm3Exporter::write_header(std::stringstream &out) const { + out << "OPENQASM 3.0;" << std::endl; + out << "include \"stdgates.inc\";" << std::endl; +} + +inline void Qasm3Exporter::write_gate_definitions(std::stringstream &out) const { + const auto name_map = get_standard_gate_name_mapping(); + bool cs = false; + bool sxdg = false; + + QkOpCounts opcounts = qk_circuit_count_ops(rust_circuit_ptr()); + for (int i = 0; i < opcounts.len; i++) { + if (opcounts.data[i].count == 0) { + continue; + } + const auto op = name_map.at(opcounts.data[i].name).gate_map(); + switch (op) { + case QkGate_R: + out << "gate r(p0, p1) _gate_q_0 {" << std::endl; + out << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_SXdg: + case QkGate_RYY: + case QkGate_XXPlusYY: + case QkGate_XXMinusYY: + if (!sxdg) { + out << "gate sxdg _gate_q_0 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << "}" << std::endl; + sxdg = true; + } + if (op == QkGate_RYY) { + out << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " sxdg _gate_q_0;" << std::endl; + out << " sxdg _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " sx _gate_q_0;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + if (op == QkGate_XXPlusYY) { + out << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + out << " rz(p1) _gate_q_0;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_0;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sxdg _gate_q_1;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " rz(-p1) _gate_q_0;" << std::endl; + out << "}" << std::endl; + } + if (op == QkGate_XXMinusYY) { + out << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; + out << " rz(-p1) _gate_q_1;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sx _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " ry(0.5*p0) _gate_q_0;" << std::endl; + out << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " sdg _gate_q_1;" << std::endl; + out << " sdg _gate_q_0;" << std::endl; + out << " sxdg _gate_q_0;" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " rz(p1) _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + break; + case QkGate_DCX: + out << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_ECR: + out << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " sx _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " x _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_ISwap: + out << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; + out << " s _gate_q_0;" << std::endl; + out << " s _gate_q_1;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_0;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CSX: + case QkGate_CS: + if (!cs) { + out << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; + out << " t _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " tdg _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " t _gate_q_1;" << std::endl; + out << "}" << std::endl; + cs = true; + } + if (op == QkGate_CSX) { + out << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cs _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + } + break; + case QkGate_CSdg: + out << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; + out << " tdg _gate_q_0;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " t _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " tdg _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CCZ: + out << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RXX: + out << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " h _gate_q_0;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RZX: + out << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RZZ: + out << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " rz(p0) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RCCX: + out << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << " t _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " tdg _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " t _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " tdg _gate_q_2;" << std::endl; + out << " h _gate_q_2;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_C3X: + out << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_0;" << std::endl; + out << " p(pi/8) _gate_q_1;" << std::endl; + out << " p(pi/8) _gate_q_2;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " p(-pi/8) _gate_q_1;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " p(-pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " p(pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " p(-pi/8) _gate_q_2;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " p(pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " p(-pi/8) _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_C3SX: + out << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_1;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_2;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_RC3X: + out << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " cx _gate_q_0, _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_1, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << " t _gate_q_3;" << std::endl; + out << " cx _gate_q_2, _gate_q_3;" << std::endl; + out << " tdg _gate_q_3;" << std::endl; + out << " h _gate_q_3;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CU1: + out << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cp(p0) _gate_q_0 _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + case QkGate_CU3: + out << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; + out << " cu(p0, p1, p2, 0) _gate_q_0 _gate_q_1;" << std::endl; + out << "}" << std::endl; + break; + default: + break; + } + } + qk_opcounts_clear(&opcounts); +} + +inline void Qasm3Exporter::write_registers(std::stringstream &out) const { + const std::string qreg_name = "q"; + out << "qubit[" << circ_->num_qubits() << "] " << qreg_name << ";" << std::endl; + for (const auto& creg : circ_->cregs()) { + out << "bit[" << creg.size() << "] " << creg.name() << ";" << std::endl; + } +} + +inline void Qasm3Exporter::write_instructions(std::stringstream &out) const { + const std::string qreg_name = "q"; + const uint_t nops = qk_circuit_num_instructions(rust_circuit_ptr()); + + for (uint_t i = 0; i < nops; i++) { + QkCircuitInstruction *op = new QkCircuitInstruction; + qk_circuit_get_instruction(rust_circuit_ptr(), i, op); + + if (op->num_clbits > 0 && op->num_qubits == op->num_clbits) { + for (uint_t j = 0; j < op->num_qubits; j++) { + const auto creg_data = recover_creg_data(circ_->cregs(), op->clbits[j]); + out << creg_data.first << "[" << creg_data.second + << "] = " << op->name << " " + << qreg_name << "[" << op->qubits[j] << "];" << std::endl; + } + } else { + if (std::strcmp(op->name, "u") == 0) { + out << "U"; + } else { + out << op->name; + } + if (op->num_params > 0) { + out << "("; + for (uint_t j = 0; j < op->num_params; j++) { + out << param_to_string(op->params[j]); + if (j != op->num_params - 1) { + out << ", "; + } + } + out << ")"; + } + if (op->num_qubits > 0) { + out << " "; + for (uint_t j = 0; j < op->num_qubits; j++) { + out << qreg_name << "[" << op->qubits[j] << "]"; + if (j != op->num_qubits - 1) { + out << ", "; + } + } + } + out << ";" << std::endl; + } + qk_circuit_instruction_clear(op); + } +} + +} // namespace circuit +} // namespace Qiskit + +#endif // __qiskitcpp_circuit_qasm3_exporter_impl_hpp__ diff --git a/src/circuit/quantumcircuit_def.hpp b/src/circuit/quantumcircuit_def.hpp index d8add04..0095111 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -56,8 +56,10 @@ namespace Qiskit namespace circuit { +// Forward-declare after all prerequisites are available. class ControlFlowOp; class IfElseOp; +class Qasm3Exporter; static Parameter null_param; @@ -1535,369 +1537,14 @@ 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(); - } + /// + /// The actual implementation lives in `Qasm3Exporter` (see + /// `circuit/qasm3_exporter.hpp`). This wrapper exists so existing user + /// code that calls `QuantumCircuit::to_qasm3` continues to work after + /// the refactor in https://github.com/Qiskit/qiskit-cpp/issues/146. + std::string to_qasm3(void); /// @brief print circuit (this is for debug) void print(void) const diff --git a/src/circuit/quantumregister.hpp b/src/circuit/quantumregister.hpp index 22914a8..911287d 100644 --- a/src/circuit/quantumregister.hpp +++ b/src/circuit/quantumregister.hpp @@ -18,6 +18,7 @@ #define __qiskitcpp_circuit_quantum_register_hpp__ #include +#include #include "circuit/register.hpp" #include "qiskit.h" diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7183953..5cae46d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -32,9 +32,10 @@ create_test_sourcelist (source_files test_driver.cpp ${discovered_tests}) add_library(common STATIC common.cpp) # Actually define the test driver program executable to be built... -add_executable (test_driver ${source_files}) +add_executable (test_driver ${source_files} ../src/circuit/qasm3_exporter.cpp) # ...include the location of the header file... target_include_directories (test_driver PRIVATE ${CMAKE_SOURCE_DIR} nlohmann_json::nlohmann_json ../src deps/qiskit/dist/c/include) +target_include_directories (test_driver PRIVATE /home/khang/pro-lab/coding-space/skill-quantum/qiskit-cpp-work/qiskit-cpp/src /home/khang/pro-lab/coding-space/skill-quantum/qiskit-cpp-work/qiskit-source/dist/c/include) # ...and linked with the qiskit library. target_link_libraries (test_driver ${qiskit} common nlohmann_json::nlohmann_json) diff --git a/test/test_circuit.cpp b/test/test_circuit.cpp index eae4cd8..9854be3 100644 --- a/test/test_circuit.cpp +++ b/test/test_circuit.cpp @@ -16,6 +16,7 @@ #include "common.hpp" #include "circuit/quantumcircuit.hpp" +#include "circuit/parameter.hpp" using namespace Qiskit; using namespace Qiskit::circuit; @@ -641,6 +642,83 @@ static int test_to_qasm3_multi_regs(void) { return Ok; } +// Regression test for https://github.com/Qiskit/qiskit-cpp/issues/146 +// `to_qasm3` must emit a single-parameter gate using the original symbolic +// parameter name, not a numeric value. +static int test_to_qasm3_parameter_symbol(void) { + auto circ = QuantumCircuit(1, 1); + + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto actual = circ.to_qasm3(); + // The key check: `rx(theta)` must be present, not a numeric value. + if (actual.find("rx(theta)") == std::string::npos) { + std::cerr << " to_qasm3_parameter_symbol test : missing 'rx(theta)' in: " << actual << std::endl; + return EqualityError; + } + return Ok; +} + +// A `Parameter` that is a compound expression built from a free symbol should +// also be emitted using its string expression, e.g. `rx(2*theta + 1.5)`. +static int test_to_qasm3_parameter_expression(void) { + auto circ = QuantumCircuit(1, 1); + + Parameter theta("theta"); + Parameter expr = theta * 2.0 + 1.5; + circ.rx(expr, 0); + + const auto actual = circ.to_qasm3(); + // `2*theta + 1.5` may be canonicalised slightly differently by + // `qk_param_str`; just check that the symbolic name `theta` is preserved + // and that we do not fall back to a numeric-only representation. + if (actual.find("2*theta") == std::string::npos) { + std::cerr << " to_qasm3_parameter_expression test : missing symbolic '2*theta' in: " << actual << std::endl; + return EqualityError; + } + return Ok; +} + +// Numeric parameters must keep their previous behavior of being serialized as +// decimal numbers (no symbolic transformation). +static int test_to_qasm3_numeric_parameter(void) { + auto circ = QuantumCircuit(1, 1); + + circ.rz(0.5, 0); + + const auto actual = circ.to_qasm3(); + // Check that the numeric value is present (0.5 or 1/2 format). + // The register name may differ, so check for key content instead. + if (actual.find("rz(0.5)") == std::string::npos && actual.find("rz(1/2)") == std::string::npos) { + std::cerr << " to_qasm3_numeric_parameter test : missing 'rz(0.5)' in: " << actual << std::endl; + return EqualityError; + } + if (actual.find("rz(theta)") != std::string::npos) { + std::cerr << " to_qasm3_numeric_parameter test : unexpected symbolic 'theta' in: " << actual << std::endl; + return EqualityError; + } + return Ok; +} + +// `U` is special: it must keep being uppercased even when parameterized. +static int test_to_qasm3_u_gate_parameterized(void) { + auto circ = QuantumCircuit(1, 1); + + Parameter a("a"); + Parameter b("b"); + Parameter c("c"); + circ.u(a, b, c, 0); + + const auto actual = circ.to_qasm3(); + if (actual.find("U(a, b, c) q[0];") == std::string::npos) { + std::cerr << " to_qasm3_u_gate_parameterized test : expected 'U(a, b, c) q[0];' in:\n" + << actual << std::endl; + return EqualityError; + } + return Ok; +} + #if defined(_WIN32) int test_circuit(int argc, char** const argv) { #else @@ -652,6 +730,10 @@ int test_circuit(int argc, char** argv) { 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_to_qasm3_numeric_parameter); + num_failed += RUN_TEST(test_to_qasm3_parameter_symbol); + num_failed += RUN_TEST(test_to_qasm3_parameter_expression); + num_failed += RUN_TEST(test_to_qasm3_u_gate_parameterized); std::cerr << "=== Number of failed subtests: " << num_failed << std::endl; return num_failed;