From cec0dcbce6c56b72b44443168750d3266e1ef2d6 Mon Sep 17 00:00:00 2001 From: GoiBasia Date: Mon, 15 Jun 2026 19:47:03 +0800 Subject: [PATCH] Support parameterized QASM3 export --- .../notes/qasm3-exporter-parameter-146.yaml | 18 + src/circuit/parameter.hpp | 406 +++++----- src/circuit/quantumcircuit.hpp | 1 + src/circuit/quantumcircuit_def.hpp | 562 ++++--------- src/qasm3/qasm3_exporter.hpp | 762 ++++++++++++++++++ test/test_circuit.cpp | 689 ++++++++++++++++ 6 files changed, 1838 insertions(+), 600 deletions(-) create mode 100644 releasenotes/notes/qasm3-exporter-parameter-146.yaml create mode 100644 src/qasm3/qasm3_exporter.hpp diff --git a/releasenotes/notes/qasm3-exporter-parameter-146.yaml b/releasenotes/notes/qasm3-exporter-parameter-146.yaml new file mode 100644 index 0000000..9f60519 --- /dev/null +++ b/releasenotes/notes/qasm3-exporter-parameter-146.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + Add OpenQASM 3 export support for parameterized circuits with explicit + `input float[64]` declarations. + + `QuantumCircuit::to_qasm3()` and `Qiskit::qasm3::dumps(circuit)` use + `QuantumCircuit::parameter_symbols()` to emit original parameter symbol + names by default. `Qiskit::qasm3::dumps(circuit, parameter_names)` remains + available for callers that need to provide names explicitly. Explicit + parameter names must be valid OpenQASM identifiers and must not conflict + with reserved OpenQASM or standard-gate identifiers. + + `QuantumCircuit::parameter_symbols()` currently derives names from + instruction parameter expression strings and cross-checks the recovered + count against Qiskit C. Until Qiskit C exposes canonical parameter names + or UUIDs to C++, circuits cannot contain distinct `Parameter` objects with + the same symbol name. diff --git a/src/circuit/parameter.hpp b/src/circuit/parameter.hpp index de40654..f3b4c53 100644 --- a/src/circuit/parameter.hpp +++ b/src/circuit/parameter.hpp @@ -17,8 +17,12 @@ #ifndef __qiskitcpp_circuit_parameter_hpp__ #define __qiskitcpp_circuit_parameter_hpp__ +#include #include #include +#include +#include +#include #include "utils/types.hpp" #include "qiskit.h" @@ -34,7 +38,17 @@ class Parameter { friend class QuantumCircuit; protected: - std::shared_ptr qiskit_param_ = nullptr; + std::shared_ptr qiskit_param_ = nullptr; + + static void check_parameter_operation(QkExitCode ret) + { + if (ret == QkExitCode_Success) { + return; + } + throw std::invalid_argument( + "Duplicate parameter symbols are not supported in Qiskit C++ " + "until the Qiskit C API can identify parameters by UUID."); + } public: /// @brief Create a new Parameter Parameter() @@ -65,18 +79,18 @@ class Parameter /// @brief Create a new symbol /// @param name a name of symbol - Parameter(std::string name) - { - qiskit_param_ = std::shared_ptr(qk_param_new_symbol((char *)name.c_str()), qk_param_free); - } + Parameter(std::string name) + { + qiskit_param_ = std::shared_ptr(qk_param_new_symbol((char *)name.c_str()), qk_param_free); + } /// @brief Create a new Parameter from other Parameter /// @param prm source Parameter - Parameter(const Parameter &prm) - { - // copying reference - qiskit_param_ = prm.qiskit_param_; - } + Parameter(const Parameter &prm) + { + // copying reference + qiskit_param_ = prm.qiskit_param_; + } /// @brief Create a new Parameter from QkParam /// @param prm source Parameter @@ -111,207 +125,207 @@ class Parameter /// @brief Negate a Parameter /// @return a new Parameter (-this) - Parameter operator-() - { - Parameter ret; - qk_param_neg(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter operator-() + { + Parameter ret; + check_parameter_operation(qk_param_neg(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief add parameters /// @param prm a Parameter to be added /// @return a new Parameter (this + prm) - Parameter operator+(const Parameter &prm) - { - Parameter ret; - qk_param_add(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get()); - return ret; - } + Parameter operator+(const Parameter &prm) + { + Parameter ret; + check_parameter_operation(qk_param_add(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get())); + return ret; + } /// @brief subtract parameters /// @param prm a Parameter to be subtracted /// @return a new Parameter (this - prm) - Parameter operator-(const Parameter &prm) - { - Parameter ret; - qk_param_sub(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get()); - return ret; - } + Parameter operator-(const Parameter &prm) + { + Parameter ret; + check_parameter_operation(qk_param_sub(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get())); + return ret; + } /// @brief multiply parameters /// @param prm a Parameter to be multiplied /// @return a new Parameter (this * prm) - Parameter operator*(const Parameter &prm) - { - Parameter ret; - qk_param_mul(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get()); - return ret; - } + Parameter operator*(const Parameter &prm) + { + Parameter ret; + check_parameter_operation(qk_param_mul(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get())); + return ret; + } /// @brief divide by a Parameter /// @param prm a Parameter to be divided by /// @return a new Parameter (this / prm) - Parameter operator/(const Parameter &prm) - { - Parameter ret; - qk_param_div(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get()); - return ret; - } + Parameter operator/(const Parameter &prm) + { + Parameter ret; + check_parameter_operation(qk_param_div(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get())); + return ret; + } /// @brief add a value /// @param prm a floating point value to be added /// @return a new Parameter (this + prm) - Parameter operator+(const double prm) - { - Parameter ret; - Parameter rhs(prm); - qk_param_add(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return ret; - } + Parameter operator+(const double prm) + { + Parameter ret; + Parameter rhs(prm); + check_parameter_operation(qk_param_add(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return ret; + } /// @brief subtract a value /// @param prm a floating point value to be subtracted /// @return a new Parameter (this - prm) - Parameter operator-(const double prm) - { - Parameter ret; - Parameter rhs(prm); - qk_param_sub(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return ret; - } + Parameter operator-(const double prm) + { + Parameter ret; + Parameter rhs(prm); + check_parameter_operation(qk_param_sub(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return ret; + } /// @brief multiply a value /// @param prm a floating point value to be multiplied /// @return a new Parameter (this * prm) - Parameter operator*(const double prm) - { - Parameter ret; - Parameter rhs(prm); - qk_param_mul(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return ret; - } + Parameter operator*(const double prm) + { + Parameter ret; + Parameter rhs(prm); + check_parameter_operation(qk_param_mul(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return ret; + } /// @brief divide by a value /// @param prm a floating point value to be divided by /// @return a new Parameter (this / prm) - Parameter operator/(const double prm) - { - Parameter ret; - Parameter rhs(prm); - qk_param_div(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return ret; - } + Parameter operator/(const double prm) + { + Parameter ret; + Parameter rhs(prm); + check_parameter_operation(qk_param_div(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return ret; + } /// @brief calculate this Parameter raised to the power other Parameter /// @param prm an exponent Parameter /// @return a new Parameter (this ^ prm) - Parameter pow(const Parameter &prm) - { - Parameter ret; - qk_param_pow(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get()); - return ret; - } + Parameter pow(const Parameter &prm) + { + Parameter ret; + check_parameter_operation(qk_param_pow(ret.qiskit_param_.get(), qiskit_param_.get(), prm.qiskit_param_.get())); + return ret; + } /// @brief calculate this Parameter raised to the power a value /// @param prm an exponent value /// @return a new Parameter (this ^ prm) - Parameter pow(const double prm) - { - Parameter ret; - Parameter rhs(prm); - qk_param_pow(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return ret; - } + Parameter pow(const double prm) + { + Parameter ret; + Parameter rhs(prm); + check_parameter_operation(qk_param_pow(ret.qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return ret; + } /// @brief Copy parameter from other /// @param prm source Parameter Parameter &operator=(const Parameter &prm) { - if (qiskit_param_) - qiskit_param_.reset(); - qiskit_param_ = std::shared_ptr(qk_param_copy(prm.qiskit_param_.get()), qk_param_free); - return *this; - } + if (qiskit_param_) + qiskit_param_.reset(); + qiskit_param_ = std::shared_ptr(qk_param_copy(prm.qiskit_param_.get()), qk_param_free); + return *this; + } /// @brief add other Parameter to this Parameter /// @param rhs a Parameter to be added /// @return a reference to this - Parameter &operator+=(const Parameter &rhs) - { - qk_param_add(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return *this; - } + Parameter &operator+=(const Parameter &rhs) + { + check_parameter_operation(qk_param_add(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return *this; + } /// @brief add a value to this Parameter /// @param rhs a value to be added /// @return a reference to this Parameter &operator+=(const double rhs) - { - qiskit_param* rval = qk_param_from_double(rhs); - qk_param_add(qiskit_param_.get(), qiskit_param_.get(), rval); - qk_param_free(rval); - return *this; - } + { + qiskit_param* rval = qk_param_from_double(rhs); + check_parameter_operation(qk_param_add(qiskit_param_.get(), qiskit_param_.get(), rval)); + qk_param_free(rval); + return *this; + } /// @brief subtract this Parameter and other Parameter /// @param rhs a Parameter to be subtracted /// @return a reference to this - Parameter &operator-=(const Parameter &rhs) - { - qk_param_sub(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return *this; - } + Parameter &operator-=(const Parameter &rhs) + { + check_parameter_operation(qk_param_sub(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return *this; + } /// @brief subtract this Parameter and a value /// @param rhs a value to be subtracted /// @return a reference to this Parameter &operator-=(const double rhs) - { - qiskit_param* rval = qk_param_from_double(rhs); - qk_param_sub(qiskit_param_.get(), qiskit_param_.get(), rval); - qk_param_free(rval); - return *this; - } + { + qiskit_param* rval = qk_param_from_double(rhs); + check_parameter_operation(qk_param_sub(qiskit_param_.get(), qiskit_param_.get(), rval)); + qk_param_free(rval); + return *this; + } /// @brief multiply other Parameter to this Parameter /// @param rhs a Parameter to be multiplied /// @return a reference to this - Parameter &operator*=(const Parameter &rhs) - { - qk_param_mul(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return *this; - } + Parameter &operator*=(const Parameter &rhs) + { + check_parameter_operation(qk_param_mul(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return *this; + } /// @brief multiply a value to this Parameter /// @param rhs a value to be multiplied /// @return a reference to this Parameter &operator*=(const double rhs) - { - qiskit_param* rval = qk_param_from_double(rhs); - qk_param_mul(qiskit_param_.get(), qiskit_param_.get(), rval); - qk_param_free(rval); - return *this; - } + { + qiskit_param* rval = qk_param_from_double(rhs); + check_parameter_operation(qk_param_mul(qiskit_param_.get(), qiskit_param_.get(), rval)); + qk_param_free(rval); + return *this; + } /// @brief divide this Parameter by other Parameter /// @param rhs a Parameter to be divided by /// @return a reference to this - Parameter &operator/=(const Parameter &rhs) - { - qk_param_div(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get()); - return *this; - } + Parameter &operator/=(const Parameter &rhs) + { + check_parameter_operation(qk_param_div(qiskit_param_.get(), qiskit_param_.get(), rhs.qiskit_param_.get())); + return *this; + } /// @brief divide this Parameter by a value /// @param rhs a value to be divided by /// @return a reference to this Parameter &operator/=(const double rhs) - { - qiskit_param* rval = qk_param_from_double(rhs); - qk_param_div(qiskit_param_.get(), qiskit_param_.get(), rval); - qk_param_free(rval); - return *this; - } + { + qiskit_param* rval = qk_param_from_double(rhs); + check_parameter_operation(qk_param_div(qiskit_param_.get(), qiskit_param_.get(), rval)); + qk_param_free(rval); + return *this; + } /// @brief compare 2 Parameters /// @param rhs a Parameter to be compared @@ -350,102 +364,102 @@ class Parameter /// @brief calculate exponent of this Parameter /// @return a new Parameter for the result - Parameter exp(void) - { - Parameter ret; - qk_param_exp(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter exp(void) + { + Parameter ret; + check_parameter_operation(qk_param_exp(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate log of this Parameter /// @return a new Parameter for the result - Parameter log(void) - { - Parameter ret; - qk_param_log(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter log(void) + { + Parameter ret; + check_parameter_operation(qk_param_log(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate absolute of this Parameter /// @return a new Parameter for the result - Parameter abs(void) - { - Parameter ret; - qk_param_abs(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter abs(void) + { + Parameter ret; + check_parameter_operation(qk_param_abs(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate sine of this Parameter /// @return a new Parameter for the result - Parameter sin(void) - { - Parameter ret; - qk_param_sin(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter sin(void) + { + Parameter ret; + check_parameter_operation(qk_param_sin(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate cosine of this Parameter /// @return a new Parameter for the result - Parameter cos(void) - { - Parameter ret; - qk_param_cos(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter cos(void) + { + Parameter ret; + check_parameter_operation(qk_param_cos(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate tangent of this Parameter /// @return a new Parameter for the result - Parameter tan(void) - { - Parameter ret; - qk_param_tan(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter tan(void) + { + Parameter ret; + check_parameter_operation(qk_param_tan(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate arcsine of this Parameter /// @return a new Parameter for the result - Parameter asin(void) - { - Parameter ret; - qk_param_asin(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter asin(void) + { + Parameter ret; + check_parameter_operation(qk_param_asin(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate arccosine of this Parameter /// @return a new Parameter for the result - Parameter acos(void) - { - Parameter ret; - qk_param_acos(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter acos(void) + { + Parameter ret; + check_parameter_operation(qk_param_acos(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate arctangent of this Parameter /// @return a new Parameter for the result - Parameter atan(void) - { - Parameter ret; - qk_param_atan(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter atan(void) + { + Parameter ret; + check_parameter_operation(qk_param_atan(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate sign of this Parameter /// @return a new Parameter for the result - Parameter sign(void) - { - Parameter ret; - qk_param_sign(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter sign(void) + { + Parameter ret; + check_parameter_operation(qk_param_sign(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } /// @brief calculate conjugate of this Parameter /// @return a new Parameter for the result - Parameter conjugate(void) - { - Parameter ret; - qk_param_conjugate(ret.qiskit_param_.get(), qiskit_param_.get()); - return ret; - } + Parameter conjugate(void) + { + Parameter ret; + check_parameter_operation(qk_param_conjugate(ret.qiskit_param_.get(), qiskit_param_.get())); + return ret; + } #ifdef QISKIT_CAPI_HAS_SUBS /// @brief bind a value to a symbol diff --git a/src/circuit/quantumcircuit.hpp b/src/circuit/quantumcircuit.hpp index f1b6ebf..160384b 100644 --- a/src/circuit/quantumcircuit.hpp +++ b/src/circuit/quantumcircuit.hpp @@ -19,6 +19,7 @@ #define __qiskitcpp_circuit_quantum_circuit_hpp__ #include "circuit/quantumcircuit_def.hpp" +#include "qasm3/qasm3_exporter.hpp" #include "circuit/quantumcircuit_impl.hpp" #endif // __qiskitcpp_circuit_quantum_circuit_hpp__ diff --git a/src/circuit/quantumcircuit_def.hpp b/src/circuit/quantumcircuit_def.hpp index 83ab98a..876f3b9 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -22,9 +22,11 @@ #include #include #include +#include #include #include #include +#include #include "utils/types.hpp" #include "circuit/parameter.hpp" @@ -53,6 +55,18 @@ using rust_circuit = ::QkCircuit; namespace Qiskit { +namespace circuit +{ +class QuantumCircuit; +} + +namespace qasm3 +{ +class Qasm3Exporter; +std::string dumps(circuit::QuantumCircuit &circuit); +std::string dumps(circuit::QuantumCircuit &circuit, const std::vector ¶meter_names); +} + namespace circuit { @@ -65,6 +79,7 @@ static Parameter null_param; /// @brief Qiskit representation of a quantum circuit. class QuantumCircuit { + friend class Qiskit::qasm3::Qasm3Exporter; protected: uint_t num_qubits_; // number of qubits uint_t num_clbits_; // number of classical bits @@ -79,6 +94,101 @@ class QuantumCircuit reg_t qubit_map_; // qubit map caused by transpiling std::vector> measure_map_; // a list of pair of qubit and clbit for measure + + static bool is_parameter_symbol_start(unsigned char c) + { + return std::isalpha(c) || c == '_'; + } + + static bool is_parameter_symbol_char(unsigned char c) + { + return std::isalnum(c) || c == '_'; + } + + static bool is_parameter_function_name(const std::string &name) + { + static const char *function_names[] = { + "abs", "acos", "asin", "atan", "conj", "conjugate", "cos", + "exp", "log", "sign", "sin", "sqrt", "tan" + }; + return std::find(function_names, function_names + sizeof(function_names) / sizeof(function_names[0]), name) + != function_names + sizeof(function_names) / sizeof(function_names[0]); + } + + static bool is_function_call(const std::string &expression, std::size_t token_end, const std::string &token) + { + if (!is_parameter_function_name(token)) { + return false; + } + while (token_end < expression.size() && std::isspace(static_cast(expression[token_end]))) { + token_end++; + } + return token_end < expression.size() && expression[token_end] == '('; + } + + static void append_unique_parameter_symbol(std::vector ¶meter_symbols, const std::string &symbol) + { + if (std::find(parameter_symbols.begin(), parameter_symbols.end(), symbol) == parameter_symbols.end()) { + parameter_symbols.push_back(symbol); + } + } + + void collect_parameter_symbols_from_expression(const std::string &expression, std::vector ¶meter_symbols) const + { + for (std::size_t i = 0; i < expression.size();) { + const auto c = static_cast(expression[i]); + if (!is_parameter_symbol_start(c)) { + i++; + continue; + } + const std::size_t start = i; + i++; + while (i < expression.size() && is_parameter_symbol_char(static_cast(expression[i]))) { + i++; + } + const auto token = expression.substr(start, i - start); + if (!is_function_call(expression, i, token)) { + append_unique_parameter_symbol(parameter_symbols, token); + } + } + } + + std::vector collect_parameter_symbols_from_instructions(void) const + { + std::vector parameter_symbols; + const auto nops = qk_circuit_num_instructions(rust_circuit_.get()); + for (uint_t i = 0; i < nops; i++) { + QkCircuitInstruction op; + qk_circuit_get_instruction(rust_circuit_.get(), i, &op); + for (uint_t j = 0; j < op.num_params; j++) { + char *expression = qk_param_str(op.params[j]); + collect_parameter_symbols_from_expression(expression, parameter_symbols); + qk_str_free(expression); + } + qk_circuit_instruction_clear(&op); + } + return parameter_symbols; + } + + static void check_circuit_parameter_operation(QkExitCode ret) + { + if (ret == QkExitCode_Success) { + return; + } + if (ret == QkExitCode_ParameterNameConflict || ret == QkExitCode_ParameterError) { + throw std::invalid_argument( + "Duplicate parameter symbols are not supported in Qiskit C++ " + "until the Qiskit C API can identify parameters by UUID."); + } + throw std::runtime_error("Qiskit C parameterized gate insertion failed."); + } + + QkExitCode add_parameterized_gate(QkGate gate, const std::uint32_t *qubits, QkParam **params) + { + QkExitCode ret = qk_circuit_parameterized_gate(rust_circuit_.get(), gate, qubits, params); + check_circuit_parameter_operation(ret); + return ret; + } public: /// @brief Create a new QuantumCircuit QuantumCircuit() {} @@ -407,7 +517,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {phase.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_Phase, qubits, params); + add_parameterized_gate(QkGate_Phase, qubits, params); } /// @brief Apply RGate @@ -431,7 +541,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_R, qubits, params); + add_parameterized_gate(QkGate_R, qubits, params); } /// @brief Apply RXGate @@ -452,7 +562,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RX, qubits, params); + add_parameterized_gate(QkGate_RX, qubits, params); } /// @brief Apply RYGate @@ -473,7 +583,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RY, qubits, params); + add_parameterized_gate(QkGate_RY, qubits, params); } /// @brief Apply RZGate @@ -494,7 +604,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZ, qubits, params); + add_parameterized_gate(QkGate_RZ, qubits, params); } /// @brief Apply SGate @@ -574,7 +684,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U, qubits, params); + add_parameterized_gate(QkGate_U, qubits, params); } /// @brief Apply U1Gate @@ -595,7 +705,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U1, qubits, params); + add_parameterized_gate(QkGate_U1, qubits, params); } /// @brief Apply U2Gate @@ -619,7 +729,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U2, qubits, params); + add_parameterized_gate(QkGate_U2, qubits, params); } /// @brief Apply U3Gate @@ -645,7 +755,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U3, qubits, params); + add_parameterized_gate(QkGate_U3, qubits, params); } /// @brief Apply unitary gate specified by unitary to qubits @@ -761,7 +871,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {phase.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CPhase, qubits, params); + add_parameterized_gate(QkGate_CPhase, qubits, params); } /// @brief Apply controlled RXGate @@ -784,7 +894,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRX, qubits, params); + add_parameterized_gate(QkGate_CRX, qubits, params); } /// @brief Apply controlled RYGate @@ -807,7 +917,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRY, qubits, params); + add_parameterized_gate(QkGate_CRY, qubits, params); } /// @brief Apply controlled RZGate @@ -830,7 +940,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRZ, qubits, params); + add_parameterized_gate(QkGate_CRZ, qubits, params); } /// @brief Apply CSGate @@ -888,7 +998,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU, qubits, params); + add_parameterized_gate(QkGate_CU, qubits, params); } /// @brief Apply CU1Gate @@ -912,7 +1022,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU1, qubits, params); + add_parameterized_gate(QkGate_CU1, qubits, params); } /// @brief Apply CU3Gate @@ -940,7 +1050,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU3, qubits, params); + add_parameterized_gate(QkGate_CU3, qubits, params); } /// @brief Apply RXXGate @@ -963,7 +1073,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RXX, qubits, params); + add_parameterized_gate(QkGate_RXX, qubits, params); } /// @brief Apply RYYGate @@ -986,7 +1096,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RYY, qubits, params); + add_parameterized_gate(QkGate_RYY, qubits, params); } /// @brief Apply RZZGate @@ -1009,7 +1119,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZZ, qubits, params); + add_parameterized_gate(QkGate_RZZ, qubits, params); } /// @brief Apply RZXGate @@ -1032,7 +1142,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZX, qubits, params); + add_parameterized_gate(QkGate_RZX, qubits, params); } /// @brief Apply XXminusYY @@ -1058,7 +1168,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get(), beta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_XXMinusYY, qubits, params); + add_parameterized_gate(QkGate_XXMinusYY, qubits, params); } /// @brief Apply XXplusYY @@ -1084,7 +1194,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get(), beta.qiskit_param_.get()}; pre_add_gate(); - qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_XXPlusYY, qubits, params); + add_parameterized_gate(QkGate_XXPlusYY, qubits, params); } /// @brief Apply CCXGate @@ -1254,6 +1364,20 @@ class QuantumCircuit return qk_circuit_num_param_symbols(rust_circuit_.get()); } + /// @brief Return the original parameter symbol names used in this circuit. + /// @return a list of parameter symbol names + std::vector parameter_symbols(void) const + { + const auto parameter_symbols = collect_parameter_symbols_from_instructions(); + const auto num_symbols = qk_circuit_num_param_symbols(rust_circuit_.get()); + if (parameter_symbols.size() != static_cast(num_symbols)) { + throw std::invalid_argument( + "Qiskit C++ cannot recover unique circuit parameter symbols until " + "the Qiskit C API can enumerate parameter names."); + } + return parameter_symbols; + } + /// @brief Assign parameters to new parameters or values. /// @param keys a list of keys /// @param values a list of values @@ -1359,7 +1483,7 @@ class QuantumCircuit vclbits[j] = (std::uint32_t)clbits[op->clbits[j]]; } } - QkOperationKind kind = qk_circuit_instruction_kind(rust_circuit_.get(), i); + QkOperationKind kind = qk_circuit_instruction_kind(circ.rust_circuit_.get(), i); if (kind == QkOperationKind_Measure) { qk_circuit_measure(rust_circuit_.get(), vqubits[0], vclbits[0]); } else if (kind == QkOperationKind_Reset) { @@ -1367,7 +1491,7 @@ class QuantumCircuit } else if (kind == QkOperationKind_Barrier) { qk_circuit_barrier(rust_circuit_.get(), vqubits.data(), (uint32_t)vqubits.size()); } else if (kind == QkOperationKind_Gate) { - qk_circuit_parameterized_gate(rust_circuit_.get(), name_map[op->name].gate_map(), vqubits.data(), op->params); + add_parameterized_gate(name_map[op->name].gate_map(), vqubits.data(), op->params); } else if (kind == QkOperationKind_Unitary) { // TO DO : how we can get unitary matrix from Rust ? } @@ -1397,7 +1521,7 @@ class QuantumCircuit for (auto &p : op.params()) { params.push_back(p.qiskit_param_.get()); } - qk_circuit_parameterized_gate(rust_circuit_.get(), op.gate_map(), vqubits.data(), params.data()); + add_parameterized_gate(op.gate_map(), vqubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), op.gate_map(), vqubits.data(), nullptr); @@ -1426,7 +1550,7 @@ class QuantumCircuit for (auto &p : op.params()) { params.push_back(p.qiskit_param_.get()); } - qk_circuit_parameterized_gate(rust_circuit_.get(), op.gate_map(), qubits.data(), params.data()); + add_parameterized_gate(op.gate_map(), qubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), op.gate_map(), qubits.data(), nullptr); @@ -1460,7 +1584,7 @@ class QuantumCircuit for (auto &p : inst.instruction().params()) { params.push_back(p.qiskit_param_.get()); } - qk_circuit_parameterized_gate(rust_circuit_.get(), inst.instruction().gate_map(), vqubits.data(), params.data()); + add_parameterized_gate(inst.instruction().gate_map(), vqubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), inst.instruction().gate_map(), vqubits.data(), nullptr); @@ -1505,22 +1629,22 @@ class QuantumCircuit reg_t qubits; if (op->num_qubits > 0) { qubits.resize(op->num_qubits); - for (int i = 0; i < op->num_qubits; i++) - qubits[i] = op->qubits[i]; + for (int j = 0; j < op->num_qubits; j++) + qubits[j] = op->qubits[j]; } reg_t clbits; if (op->num_clbits > 0) { clbits.resize(op->num_clbits); - for (int i = 0; i < op->num_clbits; i++) - clbits[i] = op->clbits[i]; + for (int j = 0; j < op->num_clbits; j++) + clbits[j] = op->clbits[j]; } std::vector params; if (op->num_params > 0) { params.resize(op->num_params); - for (int i = 0; i < op->num_params; i++) - params[i] = Parameter(qk_param_copy(op->params[i])); + for (int j = 0; j < op->num_params; j++) + params[j] = Parameter(qk_param_copy(op->params[j])); } std::string name = op->name; qk_circuit_instruction_clear(op); @@ -1555,377 +1679,7 @@ class QuantumCircuit /// @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 combine them in a single quantum register "q" for ordinary - // circuits. Transpiled circuits should use physical qubit references. - const std::string qreg_name = "q"; - const bool physical_qubits = !qubit_map_.empty(); - auto qubit_ref = [physical_qubits, &qreg_name](uint_t index) -> std::string - { - if (physical_qubits) { - return std::string("$") + std::to_string(index); - } - return qreg_name + "[" + std::to_string(index) + "]"; - }; - - if (!physical_qubits) { - qasm3 << "qubit[" << num_qubits() << "] " << qreg_name << ";" << std::endl; - } - for(const auto& creg : cregs_) { - if (creg.size() == 0) { - continue; - } - 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 << " " << qubit_ref(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 << qubit_ref(op->qubits[j]); - if (j != op->num_qubits - 1) - qasm3 << ", "; - } - } - qasm3 << ";" << std::endl; - } - qk_circuit_instruction_clear(op); - } - - return qasm3.str(); + return Qiskit::qasm3::dumps(*this); } /// @brief print circuit (this is for debug) diff --git a/src/qasm3/qasm3_exporter.hpp b/src/qasm3/qasm3_exporter.hpp new file mode 100644 index 0000000..bfaeb5a --- /dev/null +++ b/src/qasm3/qasm3_exporter.hpp @@ -0,0 +1,762 @@ +/* +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# 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 exporter + +#ifndef __qiskitcpp_qasm3_qasm3_exporter_hpp__ +#define __qiskitcpp_qasm3_qasm3_exporter_hpp__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "circuit/quantumcircuit_def.hpp" + +namespace Qiskit +{ +namespace qasm3 +{ + +class Qasm3Exporter +{ +private: + class OpCounts + { + private: + QkOpCounts counts_; + + public: + explicit OpCounts(QkCircuit *circuit) : counts_(qk_circuit_count_ops(circuit)) {} + + ~OpCounts() + { + qk_opcounts_clear(&counts_); + } + + OpCounts(const OpCounts &) = delete; + OpCounts &operator=(const OpCounts &) = delete; + + const QkOpCounts &get(void) const + { + return counts_; + } + }; + + class CircuitInstruction + { + private: + QkCircuitInstruction instruction_; + + public: + CircuitInstruction(QkCircuit *circuit, uint_t index) + { + qk_circuit_get_instruction(circuit, index, &instruction_); + } + + ~CircuitInstruction() + { + qk_circuit_instruction_clear(&instruction_); + } + + CircuitInstruction(const CircuitInstruction &) = delete; + CircuitInstruction &operator=(const CircuitInstruction &) = delete; + + QkCircuitInstruction *get(void) + { + return &instruction_; + } + }; + + class ParamString + { + private: + char *value_; + + public: + explicit ParamString(QkParam *param) : value_(qk_param_str(param)) {} + + ~ParamString() + { + qk_str_free(value_); + } + + ParamString(const ParamString &) = delete; + ParamString &operator=(const ParamString &) = delete; + + const char *c_str(void) const + { + return value_; + } + }; + + static bool is_identifier_start(unsigned char c) + { + return std::isalpha(c) || c == '_'; + } + + static bool is_identifier_char(unsigned char c) + { + return std::isalnum(c) || c == '_'; + } + + static std::string sanitize_identifier(const std::string &preferred, const std::string &fallback_base) + { + const auto &source = preferred.empty() ? fallback_base : preferred; + std::string sanitized; + for (const auto ch : source) { + const auto value = static_cast(ch); + sanitized.push_back(is_identifier_char(value) ? ch : '_'); + } + if (sanitized.empty()) { + sanitized = fallback_base.empty() ? "id" : fallback_base; + } + if (!is_identifier_start(static_cast(sanitized[0]))) { + sanitized.insert(sanitized.begin(), '_'); + } + return sanitized; + } + + class IdentifierAllocator + { + private: + std::set used_; + + public: + void reserve(const std::string &name) + { + used_.insert(name); + } + + std::string allocate(const std::string &preferred, const std::string &fallback_base) + { + const auto base = sanitize_identifier(preferred, fallback_base); + if (used_.find(base) == used_.end()) { + used_.insert(base); + return base; + } + for (std::size_t suffix = 0;; suffix++) { + const auto candidate = base + "_" + std::to_string(suffix); + if (used_.find(candidate) == used_.end()) { + used_.insert(candidate); + return candidate; + } + } + } + }; + + struct ExportContext + { + std::vector parameter_symbols; + std::vector parameter_names; + std::map parameter_name_map; + std::string qreg_name; + std::vector creg_names; + }; + + static bool is_parameter_function_name(const std::string &name) + { + static const char *function_names[] = { + "abs", "acos", "asin", "atan", "conj", "conjugate", "cos", + "exp", "log", "sign", "sin", "sqrt", "tan" + }; + return std::find(function_names, function_names + sizeof(function_names) / sizeof(function_names[0]), name) + != function_names + sizeof(function_names) / sizeof(function_names[0]); + } + + static bool is_function_call(const std::string &expression, std::size_t token_end, const std::string &token) + { + if (!is_parameter_function_name(token)) { + return false; + } + while (token_end < expression.size() && std::isspace(static_cast(expression[token_end]))) { + token_end++; + } + return token_end < expression.size() && expression[token_end] == '('; + } + + void reserve_openqasm_identifiers(IdentifierAllocator &allocator) const + { + static const char *reserved_words[] = { + "OPENQASM", "include", "def", "extern", "box", "let", "break", + "continue", "if", "else", "end", "return", "for", "while", "in", + "cal", "defcal", "defcalgrammar", "gphase", "inv", "pow", "ctrl", + "negctrl", "durationof", "sizeof", "stretch", + "input", "output", "const", "readonly", "mutable", "qreg", "qubit", + "creg", "bool", "bit", "int", "uint", "float", "angle", "complex", + "array", "void", "duration", "delay", "reset", "measure", "barrier", + "gate", "true", "false", "pi", "tau", "euler", "U" + }; + for (const auto word : reserved_words) { + allocator.reserve(word); + } + + static const char *function_names[] = { + "abs", "acos", "asin", "atan", "conj", "conjugate", "cos", + "exp", "log", "sign", "sin", "sqrt", "tan" + }; + for (const auto name : function_names) { + allocator.reserve(name); + } + + for (const auto &entry : circuit::get_standard_gate_name_mapping()) { + allocator.reserve(entry.first); + } + allocator.reserve("_gate_q_0"); + allocator.reserve("_gate_q_1"); + allocator.reserve("_gate_q_2"); + allocator.reserve("_gate_q_3"); + } + + void validate_parameter_names(circuit::QuantumCircuit &circuit, const std::vector ¶meter_names) const + { + const auto num_symbols = qk_circuit_num_param_symbols(circuit.rust_circuit_.get()); + if (parameter_names.size() != static_cast(num_symbols)) { + throw std::invalid_argument("OpenQASM 3 parameter-name count does not match circuit free-parameter count"); + } + + std::set seen; + for (const auto &name : parameter_names) { + if (name.empty()) { + throw std::invalid_argument("OpenQASM 3 parameter names cannot be empty"); + } + if (!seen.insert(name).second) { + throw std::invalid_argument("OpenQASM 3 parameter names cannot contain duplicates"); + } + const auto first = static_cast(name[0]); + if (!is_identifier_start(first)) { + throw std::invalid_argument("invalid OpenQASM identifier: " + name); + } + for (const auto ch : name) { + const auto value = static_cast(ch); + if (!is_identifier_char(value)) { + throw std::invalid_argument("invalid OpenQASM identifier: " + name); + } + } + } + } + + ExportContext build_export_context( + circuit::QuantumCircuit &circuit, + const std::vector ¶meter_names, + bool explicit_parameter_names) const + { + if (explicit_parameter_names) { + validate_parameter_names(circuit, parameter_names); + } + + ExportContext context; + context.parameter_symbols = circuit.parameter_symbols(); + const auto num_symbols = qk_circuit_num_param_symbols(circuit.rust_circuit_.get()); + if (context.parameter_symbols.size() != static_cast(num_symbols)) { + throw std::invalid_argument( + "Qiskit C++ cannot recover unique circuit parameter symbols until " + "the Qiskit C API can enumerate parameter names."); + } + + IdentifierAllocator allocator; + reserve_openqasm_identifiers(allocator); + + context.parameter_names.reserve(context.parameter_symbols.size()); + for (std::size_t i = 0; i < context.parameter_symbols.size(); i++) { + const auto &preferred = explicit_parameter_names ? parameter_names[i] : context.parameter_symbols[i]; + const auto emitted = allocator.allocate(preferred, "theta"); + if (explicit_parameter_names && emitted != preferred) { + throw std::invalid_argument("OpenQASM 3 explicit parameter name conflicts with a reserved identifier: " + preferred); + } + context.parameter_names.push_back(emitted); + context.parameter_name_map[context.parameter_symbols[i]] = emitted; + } + + const bool physical_qubits = !circuit.qubit_map_.empty(); + if (!physical_qubits) { + context.qreg_name = allocator.allocate("q", "q"); + } + + context.creg_names.resize(circuit.cregs_.size()); + for (std::size_t i = 0; i < circuit.cregs_.size(); i++) { + if (circuit.cregs_[i].size() == 0) { + continue; + } + context.creg_names[i] = allocator.allocate(circuit.cregs_[i].name(), "c"); + } + return context; + } + + std::string format_parameter_expression( + const std::string &expression, + const ExportContext &context) const + { + std::string formatted; + for (std::size_t i = 0; i < expression.size();) { + const auto c = static_cast(expression[i]); + if (!is_identifier_start(c)) { + formatted.push_back(expression[i]); + i++; + continue; + } + + const std::size_t start = i; + i++; + while (i < expression.size() && is_identifier_char(static_cast(expression[i]))) { + i++; + } + + const auto token = expression.substr(start, i - start); + const auto mapped = context.parameter_name_map.find(token); + if (mapped != context.parameter_name_map.end() && !is_function_call(expression, i, token)) { + formatted += mapped->second; + } else { + formatted += token; + } + } + return formatted; + } + + void emit_operation_parameters( + std::stringstream &qasm3, + QkCircuitInstruction *op, + const ExportContext &context) const + { + if (op->num_params == 0) { + return; + } + qasm3 << "("; + for (uint_t j = 0; j < op->num_params; j++) { + ParamString param(op->params[j]); + qasm3 << format_parameter_expression(param.c_str(), context); + if (j != op->num_params - 1) + qasm3 << ", "; + } + qasm3 << ")"; + } + + void emit_custom_gate_definitions(std::stringstream &qasm3, circuit::QuantumCircuit &circuit) const + { + auto name_map = circuit::get_standard_gate_name_mapping(); + bool cs = false; + bool sxdg = false; + OpCounts opcounts(circuit.rust_circuit_.get()); + for (int i = 0; i < opcounts.get().len; i++) { + if (opcounts.get().data[i].count != 0) { + auto op = name_map[opcounts.get().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; + } + } + } + } + + void emit_parameter_declarations(std::stringstream &qasm3, const std::vector ¶meter_names) const + { + for (const auto &symbol : parameter_names) { + qasm3 << "input float[64] " << symbol << ";" << std::endl; + } + } + + void emit_registers_and_operations(std::stringstream &qasm3, circuit::QuantumCircuit &circuit, const ExportContext &context) const + { + const uint_t nops = qk_circuit_num_instructions(circuit.rust_circuit_.get()); + + const bool physical_qubits = !circuit.qubit_map_.empty(); + auto qubit_ref = [physical_qubits, &context](uint_t index) -> std::string + { + if (physical_qubits) { + return std::string("$") + std::to_string(index); + } + return context.qreg_name + "[" + std::to_string(index) + "]"; + }; + + if (!physical_qubits) { + qasm3 << "qubit[" << circuit.num_qubits() << "] " << context.qreg_name << ";" << std::endl; + } + for (std::size_t i = 0; i < circuit.cregs_.size(); i++) { + if (circuit.cregs_[i].size() == 0) { + continue; + } + qasm3 << "bit[" << circuit.cregs_[i].size() << "] " << context.creg_names[i] << ";" << std::endl; + } + + auto recover_reg_data = [&circuit, &context](uint_t index) -> std::pair + { + auto it = std::upper_bound(circuit.cregs_.begin(), circuit.cregs_.end(), index, + [](uint_t v, const circuit::ClassicalRegister ®) { return v < reg.base_index(); }); + assert(it != circuit.cregs_.begin()); + it = std::prev(it); + const auto reg_index = static_cast(std::distance(circuit.cregs_.begin(), it)); + return std::make_pair(context.creg_names[reg_index], index - it->base_index()); + }; + + for (uint_t i = 0; i < nops; i++) { + CircuitInstruction instruction(circuit.rust_circuit_.get(), i); + auto op = instruction.get(); + const auto kind = qk_circuit_instruction_kind(circuit.rust_circuit_.get(), i); + if (op->num_clbits > 0) { + if (kind != QkOperationKind_Measure || op->num_qubits != op->num_clbits) { + throw std::runtime_error( + std::string("OpenQASM 3 export does not support clbit operation: ") + op->name); + } + 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 << "] = measure " << qubit_ref(op->qubits[j]) << ";" << std::endl; + } + } else { + if (std::strcmp(op->name, "global_phase") == 0) { + qasm3 << "gphase"; + emit_operation_parameters(qasm3, op, context); + qasm3 << ";" << std::endl; + continue; + } + if (std::strcmp(op->name, "u") == 0) { + qasm3 << "U"; + } else { + qasm3 << op->name; + } + emit_operation_parameters(qasm3, op, context); + if (op->num_qubits > 0) { + qasm3 << " "; + for (uint_t j = 0; j < op->num_qubits; j++) { + qasm3 << qubit_ref(op->qubits[j]); + if (j != op->num_qubits - 1) + qasm3 << ", "; + } + } + qasm3 << ";" << std::endl; + } + } + } + +public: + std::string dumps(circuit::QuantumCircuit &circuit) const + { + circuit.add_pending_control_flow_op(); + return dumps(circuit, {}, false); + } + + std::string dumps(circuit::QuantumCircuit &circuit, const std::vector ¶meter_names) const + { + circuit.add_pending_control_flow_op(); + return dumps(circuit, parameter_names, true); + } + +private: + std::string dumps(circuit::QuantumCircuit &circuit, const std::vector ¶meter_names, bool explicit_parameter_names) const + { + const auto context = build_export_context(circuit, parameter_names, explicit_parameter_names); + std::stringstream qasm3; + qasm3 << std::setprecision(18); + qasm3 << "OPENQASM 3.0;" << std::endl; + qasm3 << "include \"stdgates.inc\";" << std::endl; + + emit_custom_gate_definitions(qasm3, circuit); + emit_parameter_declarations(qasm3, context.parameter_names); + emit_registers_and_operations(qasm3, circuit, context); + + return qasm3.str(); + } +}; + +inline std::string dumps(circuit::QuantumCircuit &circuit) +{ + return Qasm3Exporter().dumps(circuit); +} + +inline std::string dumps(circuit::QuantumCircuit &circuit, const std::vector ¶meter_names) +{ + return Qasm3Exporter().dumps(circuit, parameter_names); +} + +} // namespace qasm3 +} // namespace Qiskit + +#endif // __qiskitcpp_qasm3_qasm3_exporter_hpp__ diff --git a/test/test_circuit.cpp b/test/test_circuit.cpp index 53bb692..b6eabe2 100644 --- a/test/test_circuit.cpp +++ b/test/test_circuit.cpp @@ -12,10 +12,13 @@ #include #include +#include #include "common.hpp" #include "circuit/quantumcircuit.hpp" +#include "circuit/parameter.hpp" +#include "qasm3/qasm3_exporter.hpp" using namespace Qiskit; using namespace Qiskit::circuit; @@ -612,6 +615,64 @@ static int test_compose(void) { return Ok; } +static int test_compose_parameterized_circuit(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + QuantumCircuit sub(qreg, creg); + + Parameter theta("theta"); + sub.rx(theta, 0); + + circ.compose(sub, reg_t({0}), reg_t({0})); + + const auto actual = circ.to_qasm3(); + 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 << " compose_parameterized_circuit test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_operator_index_parameterized_append(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit src(qreg, creg); + QuantumCircuit dst(qreg, creg); + + Parameter theta("theta"); + Parameter phi("phi"); + src.rx(theta, 0); + src.ry(phi, 0); + + dst.append(src[0]); + + const auto actual = dst.to_qasm3(); + 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 << " operator_index_parameterized_append test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + static int test_to_qasm3_multi_regs(void) { auto qreg1 = QuantumRegister(2, std::string("q1")); auto qreg2 = QuantumRegister(1, std::string("q2")); @@ -664,6 +725,615 @@ static int test_to_qasm3_physical_qubits(void) { return Ok; } +static int test_to_qasm3_parameterized_circuit(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto symbols = circ.parameter_symbols(); + if (symbols != std::vector({"theta"})) { + std::cerr << " to_qasm3_parameterized_circuit test : unexpected parameter symbol list" << std::endl; + return EqualityError; + } + + const auto actual = circ.to_qasm3(); + 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 << " to_qasm3_parameterized_circuit test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_shallow_copy_parameter_symbols(void) { + QuantumCircuit original(1, 0); + + Parameter theta("theta"); + original.rx(theta, 0); + + QuantumCircuit alias(original); + Parameter phi("phi"); + alias.ry(phi, 0); + + QuantumCircuit assigned(1, 0); + assigned = original; + Parameter lam("lam"); + assigned.rz(lam, 0); + + const auto actual = original.to_qasm3(); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "input float[64] phi;\n" + "input float[64] lam;\n" + "qubit[1] q;\n" + "rx(theta) q[0];\n" + "ry(phi) q[0];\n" + "rz(lam) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_shallow_copy_parameter_symbols test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_parameterized_physical_qubits(void) { + auto circ = QuantumCircuit(156, 0); + Parameter theta("theta"); + circ.rx(theta, 21); + + std::vector layout({21}); + circ.set_qiskit_circuit(circ.get_rust_circuit(), layout); + + const auto actual = circ.to_qasm3(); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "rx(theta) $21;\n"; + if (actual != expected) { + std::cerr << " qasm3_parameterized_physical_qubits test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_parameter_expression(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + Parameter phi("phi"); + circ.rz(theta + phi, 0); + + const auto actual = qasm3::dumps(circ); + 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" + "rz(phi + theta) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_parameter_expression test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_multiple_parameters(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter phi("phi"); + Parameter theta("theta"); + circ.rx(phi, 0); + circ.ry(theta, 0); + + const auto actual = qasm3::dumps(circ); + 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(phi) q[0];\n" + "ry(theta) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_multiple_parameters test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_numeric_parameter(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + circ.rx(0.25, 0); + + const auto actual = qasm3::dumps(circ); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(0.25) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_numeric_parameter test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_parameterized_u(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + Parameter phi("phi"); + Parameter lam("lam"); + circ.u(theta, phi, lam, 0); + + const auto actual = qasm3::dumps(circ); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "input float[64] phi;\n" + "input float[64] lam;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "U(theta, phi, lam) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_parameterized_u test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_explicit_parameter_names(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto actual = 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_dumps_explicit_parameter_names test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_explicit_parameter_rename(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto actual = qasm3::dumps(circ, {"theta_0"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta_0;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(theta_0) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_explicit_parameter_rename test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_explicit_parameter_expression_rename(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + Parameter phi("phi"); + circ.rz(theta + phi, 0); + + const auto actual = qasm3::dumps(circ, {"phi_0", "theta_0"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] phi_0;\n" + "input float[64] theta_0;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rz(phi_0 + theta_0) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_explicit_parameter_expression_rename test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_parameter_prefix_rename(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + Parameter theta2("theta2"); + circ.rx(theta, 0); + circ.ry(theta2, 0); + + const auto actual = qasm3::dumps(circ, {"alpha", "beta"}); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] alpha;\n" + "input float[64] beta;\n" + "qubit[1] q;\n" + "bit[1] c;\n" + "rx(alpha) q[0];\n" + "ry(beta) q[0];\n"; + if (actual != expected) { + std::cerr << " qasm3_dumps_parameter_prefix_rename test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_parameter_function_names(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + + Parameter theta("theta"); + circ.rz(theta.sin(), 0); + + const auto actual = qasm3::dumps(circ); + if (actual.find("input float[64] theta;\n") == std::string::npos || + actual.find("input float[64] sin;\n") != std::string::npos || + actual.find("rz(sin(theta)) q[0];\n") == std::string::npos) { + std::cerr << " qasm3_dumps_parameter_function_names test : unexpected output:\n" + << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_default_parameter_names(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + Parameter theta("theta"); + circ.rx(theta, 0); + + const auto actual = qasm3::dumps(circ); + 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_dumps_default_parameter_names test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_identifier_deconflicts(void) { + auto qreg = QuantumRegister(1, std::string("qr")); + + auto creg_q = ClassicalRegister(1, std::string("q")); + QuantumCircuit classical_q(qreg, creg_q); + classical_q.measure(0, 0); + const auto actual_classical_q = qasm3::dumps(classical_q); + const std::string expected_classical_q = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "bit[1] q_0;\n" + "q_0[0] = measure q[0];\n"; + if (actual_classical_q != expected_classical_q) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : classical q expected:\n" + << expected_classical_q << "\n actual:\n" << actual_classical_q << std::endl; + return EqualityError; + } + + auto creg_input = ClassicalRegister(1, std::string("input")); + QuantumCircuit classical_input(qreg, creg_input); + classical_input.measure(0, 0); + const auto actual_classical_input = qasm3::dumps(classical_input); + const std::string expected_classical_input = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "bit[1] input_0;\n" + "input_0[0] = measure q[0];\n"; + if (actual_classical_input != expected_classical_input) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : classical keyword expected:\n" + << expected_classical_input << "\n actual:\n" << actual_classical_input << std::endl; + return EqualityError; + } + + QuantumCircuit parameter_q(1, 0); + Parameter q("q"); + parameter_q.rx(q, 0); + const auto actual_parameter_q = qasm3::dumps(parameter_q); + const std::string expected_parameter_q = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] q;\n" + "qubit[1] q_0;\n" + "rx(q) q_0[0];\n"; + if (actual_parameter_q != expected_parameter_q) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : parameter q expected:\n" + << expected_parameter_q << "\n actual:\n" << actual_parameter_q << std::endl; + return EqualityError; + } + + QuantumCircuit parameter_rx(1, 0); + Parameter rx("rx"); + parameter_rx.ry(rx, 0); + const auto actual_parameter_rx = qasm3::dumps(parameter_rx); + const std::string expected_parameter_rx = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] rx_0;\n" + "qubit[1] q;\n" + "ry(rx_0) q[0];\n"; + if (actual_parameter_rx != expected_parameter_rx) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : parameter rx expected:\n" + << expected_parameter_rx << "\n actual:\n" << actual_parameter_rx << std::endl; + return EqualityError; + } + + QuantumCircuit parameter_defcal(1, 0); + Parameter defcal("defcal"); + parameter_defcal.rz(defcal, 0); + const auto actual_parameter_defcal = qasm3::dumps(parameter_defcal); + const std::string expected_parameter_defcal = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] defcal_0;\n" + "qubit[1] q;\n" + "rz(defcal_0) q[0];\n"; + if (actual_parameter_defcal != expected_parameter_defcal) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : parameter defcal expected:\n" + << expected_parameter_defcal << "\n actual:\n" << actual_parameter_defcal << std::endl; + return EqualityError; + } + + auto creg_theta = ClassicalRegister(1, std::string("theta")); + QuantumCircuit shared_theta(qreg, creg_theta); + Parameter theta("theta"); + shared_theta.rx(theta, 0); + shared_theta.measure(0, 0); + const auto actual_shared_theta = qasm3::dumps(shared_theta); + const std::string expected_shared_theta = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "qubit[1] q;\n" + "bit[1] theta_0;\n" + "rx(theta) q[0];\n" + "theta_0[0] = measure q[0];\n"; + if (actual_shared_theta != expected_shared_theta) { + std::cerr << " qasm3_dumps_identifier_deconflicts test : shared theta expected:\n" + << expected_shared_theta << "\n actual:\n" << actual_shared_theta << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_controlled_gate_definitions(void) { + QuantumCircuit circ(2, 0); + circ.cu1(0.5, 0, 1); + circ.cu3(0.1, 0.2, 0.3, 0, 1); + + const auto actual = qasm3::dumps(circ); + const std::string cu1_definition = + "gate cu1(p0) _gate_q_0, _gate_q_1 {\n" + " cp(p0) _gate_q_0, _gate_q_1;\n" + "}\n"; + const std::string cu3_definition = + "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {\n" + " cu(p0, p1, p2, 0) _gate_q_0, _gate_q_1;\n" + "}\n"; + if (actual.find(cu1_definition) == std::string::npos || + actual.find(cu3_definition) == std::string::npos || + actual.find("_gate_q_0 _gate_q_1") != std::string::npos) { + std::cerr << " qasm3_dumps_controlled_gate_definitions test : unexpected output:\n" + << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_global_phase(void) { + QuantumCircuit numeric_phase(1, 0, 0.5); + const auto actual_numeric = qasm3::dumps(numeric_phase); + const std::string expected_numeric = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "gphase(0.5);\n"; + if (actual_numeric != expected_numeric) { + std::cerr << " qasm3_dumps_global_phase test : numeric phase expected:\n" + << expected_numeric << "\n actual:\n" << actual_numeric << std::endl; + return EqualityError; + } + + QuantumCircuit parameterized_phase(1, 0); + QkParam *theta = qk_param_new_symbol("theta"); + const QkParam *params[] = {theta}; + const auto ret = qk_circuit_parameterized_gate( + parameterized_phase.get_rust_circuit().get(), QkGate_GlobalPhase, nullptr, params); + qk_param_free(theta); + if (ret != QkExitCode_Success) { + std::cerr << " qasm3_dumps_global_phase test : parameterized global phase insertion failed" << std::endl; + return RuntimeError; + } + + const auto actual_parameterized = qasm3::dumps(parameterized_phase); + const std::string expected_parameterized = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] theta;\n" + "qubit[1] q;\n" + "gphase(theta);\n"; + if (actual_parameterized != expected_parameterized) { + std::cerr << " qasm3_dumps_global_phase test : parameterized phase expected:\n" + << expected_parameterized << "\n actual:\n" << actual_parameterized << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_qasm3_dumps_invalid_parameter_names(void) { + QuantumCircuit one_param(1, 1); + Parameter theta("theta"); + one_param.rx(theta, 0); + + bool threw = false; + try { + qasm3::dumps(one_param, {}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : wrong count did not throw" << std::endl; + return RuntimeError; + } + + threw = false; + try { + qasm3::dumps(one_param, {""}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : empty name did not throw" << std::endl; + return RuntimeError; + } + + threw = false; + try { + qasm3::dumps(one_param, {"1theta"}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : invalid first character did not throw" << std::endl; + return RuntimeError; + } + + threw = false; + try { + qasm3::dumps(one_param, {"theta-1"}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : invalid character did not throw" << std::endl; + return RuntimeError; + } + + threw = false; + try { + qasm3::dumps(one_param, {"input"}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : reserved keyword did not throw" << std::endl; + return RuntimeError; + } + + threw = false; + try { + qasm3::dumps(one_param, {"rx"}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : standard gate name did not throw" << std::endl; + return RuntimeError; + } + + QuantumCircuit two_params(1, 1); + Parameter phi("phi"); + two_params.rx(theta, 0); + two_params.ry(phi, 0); + + threw = false; + try { + qasm3::dumps(two_params, {"theta", "theta"}); + } catch (const std::invalid_argument &) { + threw = true; + } + if (!threw) { + std::cerr << " qasm3_dumps_invalid_parameter_names test : duplicate names did not throw" << std::endl; + return RuntimeError; + } + + return Ok; +} + #if defined(_WIN32) int test_circuit(int argc, char** const argv) { #else @@ -674,8 +1344,27 @@ 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_compose_parameterized_circuit); + num_failed += RUN_TEST(test_operator_index_parameterized_append); num_failed += RUN_TEST(test_to_qasm3_multi_regs); num_failed += RUN_TEST(test_to_qasm3_physical_qubits); + num_failed += RUN_TEST(test_to_qasm3_parameterized_circuit); + num_failed += RUN_TEST(test_qasm3_shallow_copy_parameter_symbols); + num_failed += RUN_TEST(test_qasm3_parameterized_physical_qubits); + num_failed += RUN_TEST(test_qasm3_dumps_parameter_expression); + num_failed += RUN_TEST(test_qasm3_dumps_multiple_parameters); + num_failed += RUN_TEST(test_qasm3_dumps_numeric_parameter); + num_failed += RUN_TEST(test_qasm3_dumps_parameterized_u); + num_failed += RUN_TEST(test_qasm3_dumps_explicit_parameter_names); + num_failed += RUN_TEST(test_qasm3_dumps_explicit_parameter_rename); + num_failed += RUN_TEST(test_qasm3_dumps_explicit_parameter_expression_rename); + num_failed += RUN_TEST(test_qasm3_dumps_parameter_prefix_rename); + num_failed += RUN_TEST(test_qasm3_dumps_parameter_function_names); + num_failed += RUN_TEST(test_qasm3_dumps_default_parameter_names); + num_failed += RUN_TEST(test_qasm3_dumps_identifier_deconflicts); + num_failed += RUN_TEST(test_qasm3_dumps_controlled_gate_definitions); + num_failed += RUN_TEST(test_qasm3_dumps_global_phase); + num_failed += RUN_TEST(test_qasm3_dumps_invalid_parameter_names); std::cerr << "=== Number of failed subtests: " << num_failed << std::endl; return num_failed;