From 85275c14bcd5891fff71b44d18e03eed33075edd Mon Sep 17 00:00:00 2001 From: spital <11034264+spital@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:14:16 +0200 Subject: [PATCH 1/2] Support parameterized QASM3 export --- ...meterized-export-146-6f6f1d31c9e74d2b.yaml | 42 + src/circuit/qasm3_exporter.hpp | 755 ++++++++++++++++++ src/circuit/quantumcircuit.hpp | 1 + src/circuit/quantumcircuit_def.hpp | 511 +++--------- test/test_circuit.cpp | 625 +++++++++++++++ 5 files changed, 1523 insertions(+), 411 deletions(-) create mode 100644 releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml create mode 100644 src/circuit/qasm3_exporter.hpp diff --git a/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml b/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml new file mode 100644 index 0000000..20ea48f --- /dev/null +++ b/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml @@ -0,0 +1,42 @@ +--- +features: + - | + `QuantumCircuit::to_qasm3()` now uses a dedicated OpenQASM 3 exporter and + declares symbolic circuit parameters as OpenQASM 3 `input float[64]` + values before emitting parameterized gate operations. Generated input and + register names are adjusted when needed to avoid OpenQASM 3 global symbol + conflicts, including conflicts with custom gate definitions emitted by the + exporter. The exporter also emits valid OpenQASM 3 for controlled-U1, + controlled-U3, and global phase operations. + - | + Added `QuantumCircuit::parameter_symbols()`, which returns the sorted list + of unique parameter symbol names used in the circuit. The OpenQASM 3 + exporter consumes this list instead of collecting parameters on its own. + This is currently a stub: the authoritative parameter table lives on the + Rust side and there is no C-API yet to enumerate the symbols (only + `qk_circuit_num_param_symbols`, the count), so the names are derived from + the parameter expressions and the implementation is expected to be replaced + by a C-API when one becomes available. Until then, parameter names must be + recoverable as OpenQASM identifier tokens; the stub raises an exception if + the harvested names do not match the Rust-side parameter count. +fixes: + - | + Fixed `QuantumCircuit::compose()` reading each instruction's operation kind + from the destination circuit instead of the source circuit. Composing into a + circuit that has fewer instructions than the source could read past the end + of the destination, and otherwise mis-classified instructions. This also + ensures composed parameterized circuits export correctly with + `to_qasm3()`. + - | + Parameterized gate insertion now raises `std::invalid_argument` when the + Qiskit C API reports `QkExitCode_ParameterNameConflict`, so distinct + `Parameter` objects with the same symbol name are rejected before the C++ + circuit is mutated. +issues: + - | + `QuantumCircuit::parameter_symbols()` (and therefore the OpenQASM 3 + exporter) identifies parameters by their original symbol name. Distinct + `Parameter` objects that share a name are not supported, because qiskit-cpp + cannot yet disambiguate parameters by UUID through the C-API. The original + symbol name is used for the exported input name and for the + `parameter_symbols()` list. diff --git a/src/circuit/qasm3_exporter.hpp b/src/circuit/qasm3_exporter.hpp new file mode 100644 index 0000000..6e8deca --- /dev/null +++ b/src/circuit/qasm3_exporter.hpp @@ -0,0 +1,755 @@ +/* +# 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_circuit_qasm3_exporter_hpp__ +#define __qiskitcpp_circuit_qasm3_exporter_hpp__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "circuit/quantumcircuit_def.hpp" + +namespace Qiskit { +namespace circuit { + +class Qasm3Exporter { +protected: + QuantumCircuit &circuit_; + + // RAII guards for Qiskit C-API handles so cleanup is exception-safe and the + // manual qk_*_free calls cannot be missed on an early return/throw. + class ScopedOpCounts { + QkOpCounts counts_; + + public: + explicit ScopedOpCounts(const QkCircuit *circuit) + : counts_(qk_circuit_count_ops(circuit)) {} + ~ScopedOpCounts() { qk_opcounts_clear(&counts_); } + ScopedOpCounts(const ScopedOpCounts &) = delete; + ScopedOpCounts &operator=(const ScopedOpCounts &) = delete; + const QkOpCounts &get(void) const { return counts_; } + }; + + class ScopedInstruction { + QkCircuitInstruction instruction_; + + public: + ScopedInstruction(const QkCircuit *circuit, uint_t index) { + qk_circuit_get_instruction(circuit, index, &instruction_); + } + ~ScopedInstruction() { qk_circuit_instruction_clear(&instruction_); } + ScopedInstruction(const ScopedInstruction &) = delete; + ScopedInstruction &operator=(const ScopedInstruction &) = delete; + QkCircuitInstruction &get(void) { return instruction_; } + }; + + class ScopedParam { + QkParam *param_; + + public: + explicit ScopedParam(QkParam *param) : param_(param) {} + ~ScopedParam() { qk_param_free(param_); } + ScopedParam(const ScopedParam &) = delete; + ScopedParam &operator=(const ScopedParam &) = delete; + const QkParam *get(void) const { return param_; } + }; + + class ScopedParamStr { + char *value_; + + public: + explicit ScopedParamStr(const QkParam *param) : value_(qk_param_str(param)) {} + ~ScopedParamStr() { qk_str_free(value_); } + ScopedParamStr(const ScopedParamStr &) = delete; + ScopedParamStr &operator=(const ScopedParamStr &) = delete; + const char *c_str(void) const { return value_; } + }; + + static bool is_identifier_start(const unsigned char ch) { + return ch == '_' || std::isalpha(ch) || ch >= 0x80; + } + + static bool is_identifier_char(const unsigned char ch) { + return is_identifier_start(ch) || std::isdigit(ch); + } + + static bool is_qasm3_function_identifier(const std::string &token) { + return token == "sin" || token == "cos" || token == "tan" || + token == "asin" || token == "acos" || token == "atan" || + token == "log" || token == "exp" || token == "abs" || + token == "sign" || token == "conj" || token == "sqrt" || + token == "pow"; + } + + static bool is_function_call(const std::string &expression, + std::size_t token_end) { + while (token_end < expression.size() && + std::isspace(static_cast(expression[token_end]))) { + ++token_end; + } + return token_end < expression.size() && expression[token_end] == '('; + } + + static std::set reserved_global_identifiers(void) { + return std::set{ + "OPENQASM", "include", "defcalgrammar", "def", "cal", "defcal", + "gate", "extern", "box", "let", "break", "continue", "if", "else", + "end", "return", "for", "while", "in", "switch", "case", "default", + "input", "output", "const", "readonly", "mutable", "qreg", "qubit", + "creg", "bool", "bit", "int", "uint", "float", "angle", "complex", + "array", "void", "duration", "stretch", "gphase", "inv", "pow", + "ctrl", "negctrl", "durationof", "delay", "reset", "measure", + "barrier", "sizeof", "true", "false", "pi", "tau", "euler", "im", + "I", "h", "x", "y", "z", "s", "sdg", "sx", "sxdg", "t", "tdg", + "rx", "ry", "rz", "p", "u", "U", "cx", "cy", "cz", "ch", "swap", + "ccx", "cs", "csdg", "csx", "cp", "crx", "cry", "crz", "cswap", + "cu", "CX", "phase", "cphase", "id", "u1", "u2", "u3"}; + } + + static std::string unique_identifier(const std::string &base, + std::set &reserved) { + std::string candidate = base; + for (uint_t i = 0; reserved.count(candidate) != 0; i++) { + candidate = base + "_" + std::to_string(i); + } + reserved.insert(candidate); + return candidate; + } + + static void reserve_emitted_gate_definition_names( + QkGate op, std::set &reserved) { + switch (op) { + case QkGate_R: + reserved.insert("r"); + break; + case QkGate_SXdg: + reserved.insert("sxdg"); + break; + case QkGate_RYY: + reserved.insert("sxdg"); + reserved.insert("ryy"); + break; + case QkGate_XXPlusYY: + reserved.insert("sxdg"); + reserved.insert("xx_plus_yy"); + break; + case QkGate_XXMinusYY: + reserved.insert("sxdg"); + reserved.insert("xx_minus_yy"); + break; + case QkGate_DCX: + reserved.insert("dcx"); + break; + case QkGate_ECR: + reserved.insert("ecr"); + break; + case QkGate_ISwap: + reserved.insert("iswap"); + break; + case QkGate_CSX: + reserved.insert("cs"); + reserved.insert("csx"); + break; + case QkGate_CS: + reserved.insert("cs"); + break; + case QkGate_CSdg: + reserved.insert("csdg"); + break; + case QkGate_CCZ: + reserved.insert("ccz"); + break; + case QkGate_RXX: + reserved.insert("rxx"); + break; + case QkGate_RZX: + reserved.insert("rzx"); + break; + case QkGate_RZZ: + reserved.insert("rzz"); + break; + case QkGate_RCCX: + reserved.insert("rccx"); + break; + case QkGate_C3X: + reserved.insert("mcx"); + break; + case QkGate_C3SX: + reserved.insert("c3sx"); + break; + case QkGate_RC3X: + reserved.insert("rcccx"); + break; + case QkGate_CU1: + reserved.insert("cu1"); + break; + case QkGate_CU3: + reserved.insert("cu3"); + break; + default: + break; + } + } + + static void harvest_parameter_identifiers(const std::string &expression, + std::set &inputs) { + for (std::size_t i = 0; i < expression.size();) { + const unsigned char ch = static_cast(expression[i]); + if (!is_identifier_start(ch)) { + ++i; + continue; + } + + const std::size_t start = i++; + while (i < expression.size() && + is_identifier_char(static_cast(expression[i]))) { + ++i; + } + + if (start > 0 && + std::isdigit(static_cast(expression[start - 1]))) { + continue; + } + + const std::string token = expression.substr(start, i - start); + if (!(is_qasm3_function_identifier(token) && + is_function_call(expression, i))) { + inputs.insert(token); + } + } + } + + static void harvest_parameter(const QkParam *param, + std::set &inputs) { + ScopedParamStr param_str(param); + harvest_parameter_identifiers(param_str.c_str(), inputs); + } + +public: + /// @brief Collect the parameter symbol names used in a circuit. + /// @param circuit The circuit to inspect. + /// @return A sorted list of unique parameter symbol names. + /// @note Backs ``QuantumCircuit::parameter_symbols``; see that method for the + /// stub/limitation notes. The names are harvested from the parameter + /// expression strings (gate parameters and global phase) because the C-API + /// does not yet expose the symbol list. The harvested count is cross-checked + /// against the Rust-side count to avoid returning a misleading list when a + /// symbol name cannot be recovered as an OpenQASM identifier token. + static std::vector + collect_parameter_symbols(const QuantumCircuit &circuit) { + std::set inputs; + + const QkCircuit *rust_circuit = circuit.rust_circuit_.get(); + const uint_t nops = qk_circuit_num_instructions(rust_circuit); + for (uint_t i = 0; i < nops; i++) { + ScopedInstruction instruction(rust_circuit, i); + QkCircuitInstruction &op = instruction.get(); + for (uint_t j = 0; j < op.num_params; j++) { + harvest_parameter(op.params[j], inputs); + } + } + + ScopedParam global_phase(qk_circuit_global_phase(rust_circuit)); + harvest_parameter(global_phase.get(), inputs); + + const std::size_t rust_parameter_count = + qk_circuit_num_param_symbols(rust_circuit); + if (inputs.size() != rust_parameter_count) { + throw std::runtime_error( + "QuantumCircuit::parameter_symbols cannot derive the parameter " + "symbol list from the current C-API. The temporary implementation " + "requires parameter names that can be recovered as OpenQASM " + "identifier tokens."); + } + + return std::vector(inputs.begin(), inputs.end()); + } + +protected: + + std::map + allocate_input_names(const std::set &inputs, + std::set &reserved) const { + std::map names; + for (const auto &input : inputs) { + names[input] = unique_identifier(input, reserved); + } + return names; + } + + std::string format_parameter( + const QkParam *param, + const std::map &input_names) const { + ScopedParamStr param_str(param); + const std::string expression = param_str.c_str(); + + std::stringstream formatted; + for (std::size_t i = 0; i < expression.size();) { + const unsigned char ch = static_cast(expression[i]); + if (!is_identifier_start(ch)) { + formatted << expression[i++]; + continue; + } + + const std::size_t start = i++; + while (i < expression.size() && + is_identifier_char(static_cast(expression[i]))) { + ++i; + } + + const std::string token = expression.substr(start, i - start); + if (start > 0 && + std::isdigit(static_cast(expression[start - 1]))) { + formatted << token; + continue; + } + + const auto replacement = input_names.find(token); + if (replacement != input_names.end() && + !(is_qasm3_function_identifier(token) && + is_function_call(expression, i))) { + formatted << replacement->second; + } else { + formatted << token; + } + } + + return formatted.str(); + } + +public: + explicit Qasm3Exporter(QuantumCircuit &circuit) : circuit_(circuit) {} + + /// @brief Serialize a QuantumCircuit object as an OpenQASM3 string. + /// @return An OpenQASM3 string. + std::string to_qasm3(void) { + circuit_.add_pending_control_flow_op(); + + std::stringstream qasm3; + qasm3 << std::setprecision(18); + qasm3 << "OPENQASM 3.0;" << std::endl; + qasm3 << "include \"stdgates.inc\";" << std::endl; + std::set reserved = reserved_global_identifiers(); + auto name_map = get_standard_gate_name_mapping(); + ScopedOpCounts opcounts_guard(circuit_.rust_circuit_.get()); + const QkOpCounts &opcounts = opcounts_guard.get(); + for (std::size_t i = 0; i < opcounts.len; i++) { + if (opcounts.data[i].count != 0) { + const auto op = name_map[opcounts.data[i].name].gate_map(); + reserve_emitted_gate_definition_names(op, reserved); + } + } + + const auto symbols = circuit_.parameter_symbols(); + const std::set inputs(symbols.begin(), symbols.end()); + const auto input_names = allocate_input_names(inputs, reserved); + for (const auto &input : input_names) { + qasm3 << "input float[64] " << input.second << ";" << std::endl; + } + + // add header for non-standard gates + bool cs = false; + bool sxdg = false; + for (std::size_t i = 0; i < opcounts.len; i++) { + if (opcounts.data[i].count != 0) { + auto op = name_map[opcounts.data[i].name].gate_map(); + switch (op) { + case QkGate_R: + qasm3 << "gate r(p0, p1) _gate_q_0 {" << std::endl; + qasm3 << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_SXdg: + case QkGate_RYY: + case QkGate_XXPlusYY: + case QkGate_XXMinusYY: + if (!sxdg) { + qasm3 << "gate sxdg _gate_q_0 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + sxdg = true; + } + if (op == QkGate_RYY) { + qasm3 << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " sxdg _gate_q_0;" << std::endl; + qasm3 << " sxdg _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " sx _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + if (op == QkGate_XXPlusYY) { + qasm3 << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" + << std::endl; + qasm3 << " rz(p1) _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sxdg _gate_q_1;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " rz(-p1) _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + } + if (op == QkGate_XXMinusYY) { + qasm3 << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" + << std::endl; + qasm3 << " rz(-p1) _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " ry(0.5*p0) _gate_q_0;" << std::endl; + qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_1;" << std::endl; + qasm3 << " sdg _gate_q_0;" << std::endl; + qasm3 << " sxdg _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " rz(p1) _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + break; + case QkGate_DCX: + qasm3 << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_ECR: + qasm3 << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " sx _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " x _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_ISwap: + qasm3 << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " s _gate_q_0;" << std::endl; + qasm3 << " s _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CSX: + case QkGate_CS: + if (!cs) { + qasm3 << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " t _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " tdg _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " t _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + cs = true; + } + if (op == QkGate_CSX) { + qasm3 << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cs _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + } + break; + case QkGate_CSdg: + qasm3 << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " tdg _gate_q_0;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " t _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " tdg _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CCZ: + qasm3 << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RXX: + qasm3 << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_0;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RZX: + qasm3 << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RZZ: + qasm3 << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " rz(p0) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RCCX: + qasm3 << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << " t _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " tdg _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " t _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " tdg _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_2;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_C3X: + qasm3 << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" + << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_0;" << std::endl; + qasm3 << " p(pi/8) _gate_q_1;" << std::endl; + qasm3 << " p(pi/8) _gate_q_2;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " p(pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " p(pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_C3SX: + qasm3 << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" + << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_RC3X: + qasm3 << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" + << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << " t _gate_q_3;" << std::endl; + qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; + qasm3 << " tdg _gate_q_3;" << std::endl; + qasm3 << " h _gate_q_3;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CU1: + qasm3 << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cp(p0) _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + case QkGate_CU3: + qasm3 << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; + qasm3 << " cu(p0, p1, p2, 0) _gate_q_0, _gate_q_1;" << std::endl; + qasm3 << "}" << std::endl; + break; + default: + break; + } + } + } + + // save ops + uint_t nops; + nops = qk_circuit_num_instructions(circuit_.rust_circuit_.get()); + + // Declare registers + // After transpilation, qubit registers will be mapped to physical + // registers, so transpiled circuits emit physical qubit references ($n) + // instead of declaring a single quantum register "q". + const bool physical_qubits = !circuit_.qubit_map_.empty(); + const std::string qreg_name = + physical_qubits ? std::string("q") : unique_identifier("q", reserved); + 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[" << circuit_.num_qubits() << "] " << qreg_name << ";" + << std::endl; + } + std::vector creg_names; + for (const auto &creg : circuit_.cregs_) { + creg_names.push_back(unique_identifier(creg.name(), reserved)); + if (creg.size() == 0) { + continue; + } + qasm3 << "bit[" << creg.size() << "] " << creg_names.back() << ";" + << std::endl; + } + + auto recover_reg_data = + [this, &creg_names](uint_t index) -> std::pair { + auto it = + std::upper_bound(circuit_.cregs_.begin(), circuit_.cregs_.end(), + index, [](uint_t v, const 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(creg_names[reg_index], index - it->base_index()); + }; + + for (uint_t i = 0; i < nops; i++) { + ScopedInstruction instruction(circuit_.rust_circuit_.get(), i); + QkCircuitInstruction &op = instruction.get(); + if (op.num_clbits > 0) { + if (op.num_qubits == op.num_clbits) { + for (uint_t j = 0; j < op.num_qubits; j++) { + const auto creg_data = recover_reg_data(op.clbits[j]); + qasm3 << creg_data.first << "[" << creg_data.second + << "] = " << op.name << " " << qubit_ref(op.qubits[j]) << ";" + << std::endl; + } + } + } else { + if (strcmp(op.name, "u") == 0) { + qasm3 << "U"; + } else if (strcmp(op.name, "global_phase") == 0) { + qasm3 << "gphase"; + } else { + qasm3 << op.name; + } + if (op.num_params > 0) { + qasm3 << "("; + for (uint_t j = 0; j < op.num_params; j++) { + qasm3 << format_parameter(op.params[j], input_names); + 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; + } + } + + return qasm3.str(); + } +}; + +inline std::vector QuantumCircuit::parameter_symbols(void) const { + return Qasm3Exporter::collect_parameter_symbols(*this); +} + +inline std::string QuantumCircuit::to_qasm3(void) { + return Qasm3Exporter(*this).to_qasm3(); +} + +} // namespace circuit +} // namespace Qiskit + +#endif // __qiskitcpp_circuit_qasm3_exporter_hpp__ diff --git a/src/circuit/quantumcircuit.hpp b/src/circuit/quantumcircuit.hpp index f1b6ebf..3a38222 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 "circuit/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..977b68f 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "utils/types.hpp" #include "circuit/parameter.hpp" @@ -58,6 +59,7 @@ namespace circuit class ControlFlowOp; class IfElseOp; +class Qasm3Exporter; static Parameter null_param; @@ -65,6 +67,7 @@ static Parameter null_param; /// @brief Qiskit representation of a quantum circuit. class QuantumCircuit { + friend class Qasm3Exporter; protected: uint_t num_qubits_; // number of qubits uint_t num_clbits_; // number of classical bits @@ -79,6 +82,49 @@ 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 void check_parameterized_gate_result(const QkExitCode result) + { + if (result == QkExitCode_Success) { + return; + } + if (result == QkExitCode_ParameterNameConflict) { + 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("Failed to add parameterized gate to circuit."); + } + + void add_parameterized_gate(QkGate gate, const std::uint32_t *qubits, const QkParam *const *params) + { + std::shared_ptr preflight(qk_circuit_copy(rust_circuit_.get()), qk_circuit_free); + if (preflight == nullptr) { + throw std::runtime_error("Failed to copy circuit before adding parameterized gate."); + } + check_parameterized_gate_result(qk_circuit_parameterized_gate(preflight.get(), gate, qubits, params)); + check_parameterized_gate_result(qk_circuit_parameterized_gate(rust_circuit_.get(), gate, qubits, params)); + } + + class ScopedCircuitInstruction { + QkCircuitInstruction instruction_; + + public: + ScopedCircuitInstruction(const QkCircuit *circuit, const uint_t index) + { + qk_circuit_get_instruction(circuit, index, &instruction_); + } + ~ScopedCircuitInstruction() + { + qk_circuit_instruction_clear(&instruction_); + } + ScopedCircuitInstruction(const ScopedCircuitInstruction &) = delete; + ScopedCircuitInstruction &operator=(const ScopedCircuitInstruction &) = delete; + QkCircuitInstruction &get(void) + { + return instruction_; + } + }; public: /// @brief Create a new QuantumCircuit QuantumCircuit() {} @@ -407,7 +453,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 +477,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 +498,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 +519,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 +540,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 +620,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 +641,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 +665,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 +691,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 +807,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 +830,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 +853,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 +876,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 +934,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 +958,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 +986,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 +1009,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 +1032,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 +1055,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 +1078,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 +1104,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 +1130,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 @@ -1345,21 +1391,21 @@ class QuantumCircuit auto name_map = get_standard_gate_name_mapping(); for (uint_t i = 0; i < nops; i++) { - QkCircuitInstruction *op = new QkCircuitInstruction; - qk_circuit_get_instruction(circ.rust_circuit_.get(), i, op); + ScopedCircuitInstruction instruction(circ.rust_circuit_.get(), i); + QkCircuitInstruction &op = instruction.get(); - std::vector vqubits(op->num_qubits); + std::vector vqubits(op.num_qubits); std::vector vclbits; - for (uint_t j = 0; j < op->num_qubits; j++) { - vqubits[j] = (std::uint32_t)qubits[op->qubits[j]]; + for (uint_t j = 0; j < op.num_qubits; j++) { + vqubits[j] = (std::uint32_t)qubits[op.qubits[j]]; } - if (op->num_clbits > 0) { - vclbits.resize(op->num_clbits); - for (uint_t j = 0; j < op->num_clbits; j++) { - vclbits[j] = (std::uint32_t)clbits[op->clbits[j]]; + if (op.num_clbits > 0) { + vclbits.resize(op.num_clbits); + for (uint_t j = 0; j < op.num_clbits; j++) { + 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,11 +1413,10 @@ 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 ? } - qk_circuit_instruction_clear(op); } for (auto m : circ.measure_map_) { @@ -1397,7 +1442,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 +1471,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 +1505,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); @@ -1551,382 +1596,26 @@ class QuantumCircuit // qasm3 + /// @brief Get the list of parameter symbol names used in the circuit. + /// @return A sorted list of unique parameter symbol names. + /// @note This is a stub. The authoritative parameter table lives on the + /// Rust side and there is currently no C-API to enumerate the symbols + /// (only ``qk_circuit_num_param_symbols`` exposes the count), so the + /// names are derived from the parameter expressions here. Replace this + /// implementation with a C-API once one is available. Until then, names + /// must be recoverable as OpenQASM identifier tokens; this method throws + /// if the harvested names do not match the Rust-side parameter count. + /// @note Limitation: distinct ``Parameter`` objects that share a name are + /// treated as a single symbol. qiskit-cpp cannot disambiguate Parameters + /// by UUID through the C-API, and the Qiskit C-API reports same-name + /// distinct symbols with ``QkExitCode_ParameterNameConflict``. The + /// original symbol name is used (the same name keyed by SamplerPub / + /// EstimatorPub parameter sets). + std::vector parameter_symbols(void) const; + /// @brief Serialize a QuantumCircuit object as an OpenQASM3 string. /// @return An OpenQASM3 string. - std::string to_qasm3(void) - { - add_pending_control_flow_op(); - - std::stringstream qasm3; - qasm3 << std::setprecision(18); - qasm3 << "OPENQASM 3.0;" << std::endl; - qasm3 << "include \"stdgates.inc\";" << std::endl; - - auto name_map = get_standard_gate_name_mapping(); - // add header for non-standard gates - bool cs = false; - bool sxdg = false; - QkOpCounts opcounts = qk_circuit_count_ops(rust_circuit_.get()); - for (int i = 0; i < opcounts.len; i++) { - if (opcounts.data[i].count != 0) { - auto op = name_map[opcounts.data[i].name].gate_map(); - switch (op) - { - case QkGate_R: - qasm3 << "gate r(p0, p1) _gate_q_0 {" << std::endl; - qasm3 << " U(p0, -pi/2 + p1, pi/2 - p1) _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_SXdg: - case QkGate_RYY: - case QkGate_XXPlusYY: - case QkGate_XXMinusYY: - if (!sxdg) - { - qasm3 << "gate sxdg _gate_q_0 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - sxdg = true; - } - if (op == QkGate_RYY) - { - qasm3 << "gate ryy(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " sxdg _gate_q_0;" << std::endl; - qasm3 << " sxdg _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " sx _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - if (op == QkGate_XXPlusYY) - { - qasm3 << "gate xx_plus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " rz(p1) _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sxdg _gate_q_1;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " rz(-p1) _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - } - if (op == QkGate_XXMinusYY) - { - qasm3 << "gate xx_minus_yy(p0, p1) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " rz(-p1) _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " ry(0.5*p0) _gate_q_0;" << std::endl; - qasm3 << " ry((-0.5)*p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_1;" << std::endl; - qasm3 << " sdg _gate_q_0;" << std::endl; - qasm3 << " sxdg _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " rz(p1) _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - break; - case QkGate_DCX: - qasm3 << "gate dcx _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_ECR: - qasm3 << "gate ecr _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " sx _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " x _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_ISwap: - qasm3 << "gate iswap _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " s _gate_q_0;" << std::endl; - qasm3 << " s _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CSX: - case QkGate_CS: - if (!cs) - { - qasm3 << "gate cs _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " t _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " tdg _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " t _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - cs = true; - } - if (op == QkGate_CSX) - { - qasm3 << "gate csx _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cs _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - } - break; - case QkGate_CSdg: - qasm3 << "gate csdg _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " tdg _gate_q_0;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " t _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " tdg _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CCZ: - qasm3 << "gate ccz _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << " ccx _gate_q_0, _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RXX: - qasm3 << "gate rxx(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_0;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RZX: - qasm3 << "gate rzx(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RZZ: - qasm3 << "gate rzz(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " rz(p0) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RCCX: - qasm3 << "gate rccx _gate_q_0, _gate_q_1, _gate_q_2 {" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << " t _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " tdg _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " t _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " tdg _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_2;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_C3X: - qasm3 << "gate mcx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_0;" << std::endl; - qasm3 << " p(pi/8) _gate_q_1;" << std::endl; - qasm3 << " p(pi/8) _gate_q_2;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " p(pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " p(pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " p(-pi/8) _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_C3SX: - qasm3 << "gate c3sx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_1;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(-pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_2;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cp(pi/8) _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_RC3X: - qasm3 << "gate rcccx _gate_q_0, _gate_q_1, _gate_q_2, _gate_q_3 {" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_0, _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_1, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << " t _gate_q_3;" << std::endl; - qasm3 << " cx _gate_q_2, _gate_q_3;" << std::endl; - qasm3 << " tdg _gate_q_3;" << std::endl; - qasm3 << " h _gate_q_3;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CU1: - qasm3 << "gate cu1(p0) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cp(p0) _gate_q_0 _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - case QkGate_CU3: - qasm3 << "gate cu3(p0, p1, p2) _gate_q_0, _gate_q_1 {" << std::endl; - qasm3 << " cu(p0, p1, p2, 0) _gate_q_0 _gate_q_1;" << std::endl; - qasm3 << "}" << std::endl; - break; - default: - break; - } - } - } - qk_opcounts_clear(&opcounts); - - // save ops - uint_t nops; - nops = qk_circuit_num_instructions(rust_circuit_.get()); - - // Declare registers - // After transpilation, qubit registers will be mapped to physical registers, - // so we need to 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(); - } + std::string to_qasm3(void); /// @brief print circuit (this is for debug) void print(void) const diff --git a/test/test_circuit.cpp b/test/test_circuit.cpp index 53bb692..1ebe214 100644 --- a/test/test_circuit.cpp +++ b/test/test_circuit.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "common.hpp" @@ -641,6 +642,350 @@ static int test_to_qasm3_multi_regs(void) { return Ok; } +static int test_to_qasm3_parameterized(void) { + auto qreg = QuantumRegister(2, std::string("q")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + auto theta = Parameter("theta"); + auto phi = Parameter("phi"); + auto expr = theta + phi; + + circ.rx(theta, 0); + circ.rz(expr, 1); + circ.measure(0, 0); + + if (circ.num_parameters() != 2) { + std::cerr << " to_qasm3_parameterized test : number of parameters 2 != " << circ.num_parameters() << 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] phi;\n" + "input float[64] theta;\n" + "qubit[2] q;\n" + "bit[1] c;\n" + "rx(theta) q[0];\n" + "rz(phi + theta) q[1];\n" + "c[0] = measure q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_parameterized test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_parameter_name_conflicts(void) { + auto qreg = QuantumRegister(1, std::string("q")); + auto creg = ClassicalRegister(1, std::string("c")); + QuantumCircuit circ(qreg, creg); + auto qparam = Parameter("q"); + auto cparam = Parameter("c"); + auto rzparam = Parameter("rz"); + auto sinparam = Parameter("sin"); + auto measure_param = Parameter("measure"); + + circ.rx(qparam, 0); + circ.ry(cparam, 0); + circ.rz(rzparam, 0); + circ.p(sinparam, 0); + circ.rx(measure_param, 0); + circ.measure(0, 0); + + if (circ.num_parameters() != 5) { + std::cerr << " to_qasm3_parameter_name_conflicts test : number of parameters 5 != " << circ.num_parameters() << 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] c;\n" + "input float[64] measure_0;\n" + "input float[64] q;\n" + "input float[64] rz_0;\n" + "input float[64] sin;\n" + "qubit[1] q_0;\n" + "bit[1] c_0;\n" + "rx(q) q_0[0];\n" + "ry(c) q_0[0];\n" + "rz(rz_0) q_0[0];\n" + "p(sin) q_0[0];\n" + "rx(measure_0) q_0[0];\n" + "c_0[0] = measure q_0[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_parameter_name_conflicts test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_stdgate_name_conflicts(void) { + auto qreg = QuantumRegister(1, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + const std::vector names = { + "CX", "cphase", "crx", "cry", "crz", "cswap", + "cu", "id", "phase", "u1", "u2", "u3" + }; + + for (const auto &name : names) { + auto param = Parameter(name); + circ.rx(param, 0); + } + + if (circ.num_parameters() != names.size()) { + std::cerr << " to_qasm3_stdgate_name_conflicts test : number of parameters " << names.size() + << " != " << circ.num_parameters() << 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] CX_0;\n" + "input float[64] cphase_0;\n" + "input float[64] crx_0;\n" + "input float[64] cry_0;\n" + "input float[64] crz_0;\n" + "input float[64] cswap_0;\n" + "input float[64] cu_0;\n" + "input float[64] id_0;\n" + "input float[64] phase_0;\n" + "input float[64] u1_0;\n" + "input float[64] u2_0;\n" + "input float[64] u3_0;\n" + "qubit[1] q;\n" + "rx(CX_0) q[0];\n" + "rx(cphase_0) q[0];\n" + "rx(crx_0) q[0];\n" + "rx(cry_0) q[0];\n" + "rx(crz_0) q[0];\n" + "rx(cswap_0) q[0];\n" + "rx(cu_0) q[0];\n" + "rx(id_0) q[0];\n" + "rx(phase_0) q[0];\n" + "rx(u1_0) q[0];\n" + "rx(u2_0) q[0];\n" + "rx(u3_0) q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_stdgate_name_conflicts test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_keyword_and_constant_name_conflicts(void) { + auto qreg = QuantumRegister(1, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + const std::vector names = { + "I", "array", "false", "im", "pi", "qreg", "readonly", "switch", "true" + }; + + for (const auto &name : names) { + auto param = Parameter(name); + circ.rx(param, 0); + } + + if (circ.num_parameters() != names.size()) { + std::cerr << " to_qasm3_keyword_and_constant_name_conflicts test : number of parameters " + << names.size() << " != " << circ.num_parameters() << 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] I_0;\n" + "input float[64] array_0;\n" + "input float[64] false_0;\n" + "input float[64] im_0;\n" + "input float[64] pi_0;\n" + "input float[64] qreg_0;\n" + "input float[64] readonly_0;\n" + "input float[64] switch_0;\n" + "input float[64] true_0;\n" + "qubit[1] q;\n" + "rx(I_0) q[0];\n" + "rx(array_0) q[0];\n" + "rx(false_0) q[0];\n" + "rx(im_0) q[0];\n" + "rx(pi_0) q[0];\n" + "rx(qreg_0) q[0];\n" + "rx(readonly_0) q[0];\n" + "rx(switch_0) q[0];\n" + "rx(true_0) q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_keyword_and_constant_name_conflicts test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_classical_register_name_conflicts(void) { + auto qreg = QuantumRegister(1, std::string("q")); + auto stdgate_creg = ClassicalRegister(1, std::string("cu")); + auto keyword_creg = ClassicalRegister(1, std::string("creg")); + QuantumCircuit circ(std::vector({qreg}), std::vector({stdgate_creg, keyword_creg})); + + circ.measure(qreg, stdgate_creg); + circ.measure(qreg, keyword_creg); + + const auto actual = circ.to_qasm3(); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "bit[1] cu_0;\n" + "bit[1] creg_0;\n" + "cu_0[0] = measure q[0];\n" + "creg_0[0] = measure q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_classical_register_name_conflicts test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_custom_gate_name_conflicts(void) { + auto qreg = QuantumRegister(2, std::string("q")); + auto creg = ClassicalRegister(1, std::string("rzz")); + QuantumCircuit circ(qreg, creg); + auto rzzparam = Parameter("rzz"); + + circ.rzz(rzzparam, 0, 1); + circ.measure(0, 0); + + if (circ.num_parameters() != 1) { + std::cerr << " to_qasm3_custom_gate_name_conflicts test : number of parameters 1 != " << circ.num_parameters() << 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] rzz_0;\n" + "gate rzz(p0) _gate_q_0, _gate_q_1 {\n" + " cx _gate_q_0, _gate_q_1;\n" + " rz(p0) _gate_q_1;\n" + " cx _gate_q_0, _gate_q_1;\n" + "}\n" + "qubit[2] q;\n" + "bit[1] rzz_1;\n" + "rzz(rzz_0) q[0], q[1];\n" + "rzz_1[0] = measure q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_custom_gate_name_conflicts test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_cu1_definition(void) { + auto qreg = QuantumRegister(2, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + auto theta = Parameter("theta"); + + circ.cu1(theta, 0, 1); + + if (circ.num_parameters() != 1) { + std::cerr << " to_qasm3_cu1_definition test : number of parameters 1 != " << circ.num_parameters() << 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" + "gate cu1(p0) _gate_q_0, _gate_q_1 {\n" + " cp(p0) _gate_q_0, _gate_q_1;\n" + "}\n" + "qubit[2] q;\n" + "cu1(theta) q[0], q[1];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_cu1_definition test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_cu3_definition(void) { + auto qreg = QuantumRegister(2, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + auto theta = Parameter("theta"); + auto phi = Parameter("phi"); + auto lam = Parameter("lam"); + + circ.cu3(theta, phi, lam, 0, 1); + + if (circ.num_parameters() != 3) { + std::cerr << " to_qasm3_cu3_definition test : number of parameters 3 != " << circ.num_parameters() << 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] lam;\n" + "input float[64] phi;\n" + "input float[64] theta;\n" + "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" + "qubit[2] q;\n" + "cu3(theta, phi, lam) q[0], q[1];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_cu3_definition test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_to_qasm3_global_phase(void) { + auto qreg = QuantumRegister(1, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector(), 0.5); + + circ.h(0); + + const auto actual = circ.to_qasm3(); + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n" + "gphase(0.5);\n" + "h q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_global_phase test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + static int test_to_qasm3_physical_qubits(void) { auto circ = QuantumCircuit(156, 0); circ.h(21); @@ -664,6 +1009,272 @@ static int test_to_qasm3_physical_qubits(void) { return Ok; } +static int test_parameter_symbols(void) { + auto qreg = QuantumRegister(2, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + auto theta = Parameter("theta"); + auto phi = Parameter("phi"); + auto qparam = Parameter("q"); + auto sinparam = Parameter("sin"); + auto measure_param = Parameter("measure"); + + circ.rx(theta, 0); + circ.rz(theta + phi, 1); // reuse theta; function-style names must not leak + circ.p((phi * 2.0).sin(), 0); + circ.rx(qparam, 0); + circ.ry(sinparam, 1); + circ.rz(measure_param, 0); + + const auto symbols = circ.parameter_symbols(); + + // Names are original, unique, sorted, and exclude QASM math function tokens. + const std::vector expected = {"measure", "phi", "q", "sin", "theta"}; + if (symbols != expected) { + std::cerr << " parameter_symbols test : unexpected list, got:"; + for (const auto &s : symbols) std::cerr << " " << s; + std::cerr << std::endl; + return EqualityError; + } + + // The derived list must stay consistent with the Rust-side symbol count. + if (symbols.size() != circ.num_parameters()) { + std::cerr << " parameter_symbols test : size " << symbols.size() + << " != num_parameters " << circ.num_parameters() << std::endl; + return EqualityError; + } + + return Ok; +} + +static int test_parameter_symbols_unsupported_name(void) { + auto qreg = QuantumRegister(1, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + auto param = Parameter("a-b"); + + circ.rx(param, 0); + + try { + circ.parameter_symbols(); + } catch (const std::runtime_error &err) { + const std::string message = err.what(); + if (message.find("parameter_symbols cannot derive") == std::string::npos) { + std::cerr << " parameter_symbols_unsupported_name test : unexpected error: " + << message << std::endl; + return EqualityError; + } + return Ok; + } + + std::cerr << " parameter_symbols_unsupported_name test : expected runtime_error" + << std::endl; + return EqualityError; +} + +static int test_parameter_symbols_duplicate_name_conflict(void) { + auto expect_duplicate_error = [](const std::invalid_argument &err, const std::string &label) -> bool { + const std::string message = err.what(); + if (message.find("Duplicate parameter symbols are not supported") == std::string::npos) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : " << label + << " unexpected error: " << message << std::endl; + return false; + } + return true; + }; + + { + QuantumCircuit circ(2, 0); + auto a0 = Parameter("a"); + auto a1 = Parameter("a"); + circ.rx(a0, 0); + + try { + circ.ry(a1, 1); + } catch (const std::invalid_argument &err) { + if (!expect_duplicate_error(err, "direct gate")) { + return EqualityError; + } + } catch (...) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : direct gate wrong exception type" << std::endl; + return EqualityError; + } + + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] a;\n" + "qubit[2] q;\n" + "rx(a) q[0];\n"; + if (circ.to_qasm3() != expected) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : direct gate mutated circuit" << std::endl; + return EqualityError; + } + } + + { + QuantumCircuit dst(1, 0); + QuantumCircuit src(1, 0); + auto a0 = Parameter("a"); + auto a1 = Parameter("a"); + dst.rx(a0, 0); + src.ry(a1, 0); + + try { + dst.append(src[0]); + } catch (const std::invalid_argument &err) { + if (!expect_duplicate_error(err, "append")) { + return EqualityError; + } + } catch (...) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : append wrong exception type" << std::endl; + return EqualityError; + } + + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] a;\n" + "qubit[1] q;\n" + "rx(a) q[0];\n"; + if (dst.to_qasm3() != expected) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : append mutated circuit" << std::endl; + return EqualityError; + } + } + + { + QuantumCircuit dst(1, 0); + QuantumCircuit src(1, 0); + auto a0 = Parameter("a"); + auto a1 = Parameter("a"); + dst.rx(a0, 0); + src.ry(a1, 0); + + try { + dst.compose(src, reg_t({0}), reg_t({})); + } catch (const std::invalid_argument &err) { + if (!expect_duplicate_error(err, "compose")) { + return EqualityError; + } + } catch (...) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : compose wrong exception type" << std::endl; + return EqualityError; + } + + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "input float[64] a;\n" + "qubit[1] q;\n" + "rx(a) q[0];\n"; + if (dst.to_qasm3() != expected) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : compose mutated circuit" << std::endl; + return EqualityError; + } + } + + { + QuantumCircuit circ(1, 0); + auto a0 = Parameter("a"); + auto a1 = Parameter("a"); + + try { + circ.r(a0, a1, 0); + } catch (const std::invalid_argument &err) { + if (!expect_duplicate_error(err, "same gate")) { + return EqualityError; + } + } catch (...) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : same gate wrong exception type" << std::endl; + return EqualityError; + } + + const std::string expected = + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[1] q;\n"; + if (circ.num_parameters() != 0 || circ.to_qasm3() != expected) { + std::cerr << " parameter_symbols_duplicate_name_conflict test : same gate mutated circuit" << std::endl; + return EqualityError; + } + } + + return Ok; +} + +// parameter_symbols() reads the parameter expressions from the Rust circuit, so +// it recovers parameters introduced through compose() without any per-Parameter +// bookkeeping on the C++ side. +static int test_to_qasm3_compose_parameterized(void) { + auto qreg = QuantumRegister(2, std::string("q")); + QuantumCircuit circ(std::vector({qreg}), std::vector()); + QuantumCircuit sub(2, 0); + auto theta = Parameter("theta"); + auto phi = Parameter("phi"); + sub.rx(theta, 0); + sub.ry(phi, 1); + + circ.compose(sub, reg_t({0, 1}), reg_t({})); + + const auto symbols = circ.parameter_symbols(); + if (symbols != std::vector({"phi", "theta"})) { + std::cerr << " to_qasm3_compose_parameterized test : unexpected symbol list, got:"; + for (const auto &s : symbols) std::cerr << " " << s; + std::cerr << 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] phi;\n" + "input float[64] theta;\n" + "qubit[2] q;\n" + "rx(theta) q[0];\n" + "ry(phi) q[1];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_compose_parameterized test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + +// parameters introduced through append() of an existing instruction are likewise +// recovered, because the symbol list is derived from the Rust circuit. +static int test_to_qasm3_append_parameterized(void) { + QuantumCircuit src(1, 0); + auto theta = Parameter("theta"); + src.rx(theta, 0); + + QuantumCircuit dst(1, 0); + dst.append(src[0]); + + const auto symbols = dst.parameter_symbols(); + if (symbols != std::vector({"theta"})) { + std::cerr << " to_qasm3_append_parameterized test : unexpected symbol list, got:"; + for (const auto &s : symbols) std::cerr << " " << s; + std::cerr << std::endl; + return EqualityError; + } + + 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" + "rx(theta) q[0];\n"; + if (actual != expected) { + std::cerr << " to_qasm3_append_parameterized test : \n expected:\n" << expected + << "\n actual:\n" << actual << std::endl; + return EqualityError; + } + + return Ok; +} + #if defined(_WIN32) int test_circuit(int argc, char** const argv) { #else @@ -675,6 +1286,20 @@ int test_circuit(int argc, char** argv) { num_failed += RUN_TEST(test_append); num_failed += RUN_TEST(test_compose); num_failed += RUN_TEST(test_to_qasm3_multi_regs); + num_failed += RUN_TEST(test_parameter_symbols); + num_failed += RUN_TEST(test_parameter_symbols_unsupported_name); + num_failed += RUN_TEST(test_parameter_symbols_duplicate_name_conflict); + num_failed += RUN_TEST(test_to_qasm3_compose_parameterized); + num_failed += RUN_TEST(test_to_qasm3_append_parameterized); + num_failed += RUN_TEST(test_to_qasm3_parameterized); + num_failed += RUN_TEST(test_to_qasm3_parameter_name_conflicts); + num_failed += RUN_TEST(test_to_qasm3_stdgate_name_conflicts); + num_failed += RUN_TEST(test_to_qasm3_keyword_and_constant_name_conflicts); + num_failed += RUN_TEST(test_to_qasm3_classical_register_name_conflicts); + num_failed += RUN_TEST(test_to_qasm3_custom_gate_name_conflicts); + num_failed += RUN_TEST(test_to_qasm3_cu1_definition); + num_failed += RUN_TEST(test_to_qasm3_cu3_definition); + num_failed += RUN_TEST(test_to_qasm3_global_phase); num_failed += RUN_TEST(test_to_qasm3_physical_qubits); std::cerr << "=== Number of failed subtests: " << num_failed << std::endl; From bf419a54c0c0c0eb711b7dd6ab16ec09d80a7e95 Mon Sep 17 00:00:00 2001 From: spital <11034264+spital@users.noreply.github.com> Date: Mon, 15 Jun 2026 14:14:15 +0200 Subject: [PATCH 2/2] Drop duplicate parameter-symbol checking --- ...meterized-export-146-6f6f1d31c9e74d2b.yaml | 16 +- src/circuit/quantumcircuit_def.hpp | 89 +++++------ test/test_circuit.cpp | 140 +++++------------- 3 files changed, 76 insertions(+), 169 deletions(-) diff --git a/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml b/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml index 20ea48f..f145965 100644 --- a/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml +++ b/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml @@ -27,16 +27,14 @@ fixes: of the destination, and otherwise mis-classified instructions. This also ensures composed parameterized circuits export correctly with `to_qasm3()`. - - | - Parameterized gate insertion now raises `std::invalid_argument` when the - Qiskit C API reports `QkExitCode_ParameterNameConflict`, so distinct - `Parameter` objects with the same symbol name are rejected before the C++ - circuit is mutated. issues: - | `QuantumCircuit::parameter_symbols()` (and therefore the OpenQASM 3 exporter) identifies parameters by their original symbol name. Distinct - `Parameter` objects that share a name are not supported, because qiskit-cpp - cannot yet disambiguate parameters by UUID through the C-API. The original - symbol name is used for the exported input name and for the - `parameter_symbols()` list. + `Parameter` objects that share a name cannot be disambiguated, because + qiskit-cpp cannot yet identify parameters by UUID through the C-API. Such + duplicates collapse onto a single shared symbol of that name rather than + being rejected: detecting them would require copying the circuit on every + parameterized gate insertion, which is too expensive. The exported + OpenQASM 3 stays valid but binds the colliding gates to the same `input` + value, so use a unique name per distinct parameter. diff --git a/src/circuit/quantumcircuit_def.hpp b/src/circuit/quantumcircuit_def.hpp index 977b68f..931015d 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -25,7 +25,6 @@ #include #include #include -#include #include "utils/types.hpp" #include "circuit/parameter.hpp" @@ -83,29 +82,6 @@ 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 void check_parameterized_gate_result(const QkExitCode result) - { - if (result == QkExitCode_Success) { - return; - } - if (result == QkExitCode_ParameterNameConflict) { - 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("Failed to add parameterized gate to circuit."); - } - - void add_parameterized_gate(QkGate gate, const std::uint32_t *qubits, const QkParam *const *params) - { - std::shared_ptr preflight(qk_circuit_copy(rust_circuit_.get()), qk_circuit_free); - if (preflight == nullptr) { - throw std::runtime_error("Failed to copy circuit before adding parameterized gate."); - } - check_parameterized_gate_result(qk_circuit_parameterized_gate(preflight.get(), gate, qubits, params)); - check_parameterized_gate_result(qk_circuit_parameterized_gate(rust_circuit_.get(), gate, qubits, params)); - } - class ScopedCircuitInstruction { QkCircuitInstruction instruction_; @@ -453,7 +429,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {phase.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_Phase, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_Phase, qubits, params); } /// @brief Apply RGate @@ -477,7 +453,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get(), phi.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_R, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_R, qubits, params); } /// @brief Apply RXGate @@ -498,7 +474,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RX, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RX, qubits, params); } /// @brief Apply RYGate @@ -519,7 +495,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RY, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RY, qubits, params); } /// @brief Apply RZGate @@ -540,7 +516,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RZ, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZ, qubits, params); } /// @brief Apply SGate @@ -620,7 +596,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(); - add_parameterized_gate(QkGate_U, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U, qubits, params); } /// @brief Apply U1Gate @@ -641,7 +617,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_U1, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U1, qubits, params); } /// @brief Apply U2Gate @@ -665,7 +641,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit}; QkParam* params[] = {phi.qiskit_param_.get(), lam.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_U2, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U2, qubits, params); } /// @brief Apply U3Gate @@ -691,7 +667,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(); - add_parameterized_gate(QkGate_U3, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_U3, qubits, params); } /// @brief Apply unitary gate specified by unitary to qubits @@ -807,7 +783,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {phase.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_CPhase, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CPhase, qubits, params); } /// @brief Apply controlled RXGate @@ -830,7 +806,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_CRX, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRX, qubits, params); } /// @brief Apply controlled RYGate @@ -853,7 +829,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_CRY, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRY, qubits, params); } /// @brief Apply controlled RZGate @@ -876,7 +852,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_CRZ, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CRZ, qubits, params); } /// @brief Apply CSGate @@ -934,7 +910,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(); - add_parameterized_gate(QkGate_CU, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU, qubits, params); } /// @brief Apply CU1Gate @@ -958,7 +934,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)cqubit, (std::uint32_t)tqubit}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_CU1, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU1, qubits, params); } /// @brief Apply CU3Gate @@ -986,7 +962,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(); - add_parameterized_gate(QkGate_CU3, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_CU3, qubits, params); } /// @brief Apply RXXGate @@ -1009,7 +985,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RXX, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RXX, qubits, params); } /// @brief Apply RYYGate @@ -1032,7 +1008,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RYY, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RYY, qubits, params); } /// @brief Apply RZZGate @@ -1055,7 +1031,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RZZ, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZZ, qubits, params); } /// @brief Apply RZXGate @@ -1078,7 +1054,7 @@ class QuantumCircuit std::uint32_t qubits[] = {(std::uint32_t)qubit1, (std::uint32_t)qubit2}; QkParam* params[] = {theta.qiskit_param_.get()}; pre_add_gate(); - add_parameterized_gate(QkGate_RZX, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_RZX, qubits, params); } /// @brief Apply XXminusYY @@ -1104,7 +1080,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(); - add_parameterized_gate(QkGate_XXMinusYY, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_XXMinusYY, qubits, params); } /// @brief Apply XXplusYY @@ -1130,7 +1106,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(); - add_parameterized_gate(QkGate_XXPlusYY, qubits, params); + qk_circuit_parameterized_gate(rust_circuit_.get(), QkGate_XXPlusYY, qubits, params); } /// @brief Apply CCXGate @@ -1413,7 +1389,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) { - add_parameterized_gate(name_map[op.name].gate_map(), vqubits.data(), op.params); + qk_circuit_parameterized_gate(rust_circuit_.get(), 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 ? } @@ -1442,7 +1418,7 @@ class QuantumCircuit for (auto &p : op.params()) { params.push_back(p.qiskit_param_.get()); } - add_parameterized_gate(op.gate_map(), vqubits.data(), params.data()); + qk_circuit_parameterized_gate(rust_circuit_.get(), op.gate_map(), vqubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), op.gate_map(), vqubits.data(), nullptr); @@ -1471,7 +1447,7 @@ class QuantumCircuit for (auto &p : op.params()) { params.push_back(p.qiskit_param_.get()); } - add_parameterized_gate(op.gate_map(), qubits.data(), params.data()); + qk_circuit_parameterized_gate(rust_circuit_.get(), op.gate_map(), qubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), op.gate_map(), qubits.data(), nullptr); @@ -1505,7 +1481,7 @@ class QuantumCircuit for (auto &p : inst.instruction().params()) { params.push_back(p.qiskit_param_.get()); } - add_parameterized_gate(inst.instruction().gate_map(), vqubits.data(), params.data()); + qk_circuit_parameterized_gate(rust_circuit_.get(), inst.instruction().gate_map(), vqubits.data(), params.data()); } else qk_circuit_gate(rust_circuit_.get(), inst.instruction().gate_map(), vqubits.data(), nullptr); @@ -1605,12 +1581,13 @@ class QuantumCircuit /// implementation with a C-API once one is available. Until then, names /// must be recoverable as OpenQASM identifier tokens; this method throws /// if the harvested names do not match the Rust-side parameter count. - /// @note Limitation: distinct ``Parameter`` objects that share a name are - /// treated as a single symbol. qiskit-cpp cannot disambiguate Parameters - /// by UUID through the C-API, and the Qiskit C-API reports same-name - /// distinct symbols with ``QkExitCode_ParameterNameConflict``. The - /// original symbol name is used (the same name keyed by SamplerPub / - /// EstimatorPub parameter sets). + /// @note Limitation: distinct ``Parameter`` objects that share the same name + /// are treated as a single symbol. qiskit-cpp cannot disambiguate + /// Parameters by UUID through the C-API, so this case is neither detected + /// nor rejected; the colliding gates simply reference one shared symbol of + /// that name in the exported OpenQASM 3. Use a unique name per distinct + /// parameter. The original symbol name is preserved (the same name keyed + /// by SamplerPub / EstimatorPub parameter sets). std::vector parameter_symbols(void) const; /// @brief Serialize a QuantumCircuit object as an OpenQASM3 string. diff --git a/test/test_circuit.cpp b/test/test_circuit.cpp index 1ebe214..1280264 100644 --- a/test/test_circuit.cpp +++ b/test/test_circuit.cpp @@ -1070,62 +1070,33 @@ static int test_parameter_symbols_unsupported_name(void) { return EqualityError; } -static int test_parameter_symbols_duplicate_name_conflict(void) { - auto expect_duplicate_error = [](const std::invalid_argument &err, const std::string &label) -> bool { - const std::string message = err.what(); - if (message.find("Duplicate parameter symbols are not supported") == std::string::npos) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : " << label - << " unexpected error: " << message << std::endl; - return false; - } - return true; - }; - +// Documented limitation (per maintainer review on PR #159): qiskit-cpp cannot +// disambiguate distinct Parameter objects that share a name (no UUID through the +// C-API), so they collapse onto a single shared symbol of that name. This case +// is deliberately not detected or rejected -- preflight-copying the circuit on +// every parameterized gate just to detect it is too expensive. The exported +// OpenQASM 3 stays valid; the colliding gates simply bind the same input. Use a +// unique name per distinct parameter to avoid this. +static int test_parameter_symbols_duplicate_name_limitation(void) { + // Two distinct Parameter("a") objects across separate gates collapse to one + // symbol "a"; both gates reference it and the QASM remains valid. { QuantumCircuit circ(2, 0); auto a0 = Parameter("a"); auto a1 = Parameter("a"); circ.rx(a0, 0); + circ.ry(a1, 1); - try { - circ.ry(a1, 1); - } catch (const std::invalid_argument &err) { - if (!expect_duplicate_error(err, "direct gate")) { - return EqualityError; - } - } catch (...) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : direct gate wrong exception type" << std::endl; - return EqualityError; - } - - const std::string expected = - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "input float[64] a;\n" - "qubit[2] q;\n" - "rx(a) q[0];\n"; - if (circ.to_qasm3() != expected) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : direct gate mutated circuit" << std::endl; + if (circ.num_parameters() != 1) { + std::cerr << " parameter_symbols_duplicate_name_limitation test : expected 1 collapsed " + << "symbol, got " << circ.num_parameters() << std::endl; return EqualityError; } - } - - { - QuantumCircuit dst(1, 0); - QuantumCircuit src(1, 0); - auto a0 = Parameter("a"); - auto a1 = Parameter("a"); - dst.rx(a0, 0); - src.ry(a1, 0); - - try { - dst.append(src[0]); - } catch (const std::invalid_argument &err) { - if (!expect_duplicate_error(err, "append")) { - return EqualityError; - } - } catch (...) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : append wrong exception type" << std::endl; + const auto symbols = circ.parameter_symbols(); + if (symbols != std::vector({"a"})) { + std::cerr << " parameter_symbols_duplicate_name_limitation test : unexpected symbol list, got:"; + for (const auto &s : symbols) std::cerr << " " << s; + std::cerr << std::endl; return EqualityError; } @@ -1133,67 +1104,28 @@ static int test_parameter_symbols_duplicate_name_conflict(void) { "OPENQASM 3.0;\n" "include \"stdgates.inc\";\n" "input float[64] a;\n" - "qubit[1] q;\n" - "rx(a) q[0];\n"; - if (dst.to_qasm3() != expected) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : append mutated circuit" << std::endl; - return EqualityError; - } - } - - { - QuantumCircuit dst(1, 0); - QuantumCircuit src(1, 0); - auto a0 = Parameter("a"); - auto a1 = Parameter("a"); - dst.rx(a0, 0); - src.ry(a1, 0); - - try { - dst.compose(src, reg_t({0}), reg_t({})); - } catch (const std::invalid_argument &err) { - if (!expect_duplicate_error(err, "compose")) { - return EqualityError; - } - } catch (...) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : compose wrong exception type" << std::endl; - return EqualityError; - } - - const std::string expected = - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "input float[64] a;\n" - "qubit[1] q;\n" - "rx(a) q[0];\n"; - if (dst.to_qasm3() != expected) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : compose mutated circuit" << std::endl; + "qubit[2] q;\n" + "rx(a) q[0];\n" + "ry(a) q[1];\n"; + if (circ.to_qasm3() != expected) { + std::cerr << " parameter_symbols_duplicate_name_limitation test : \n expected:\n" << expected + << "\n actual:\n" << circ.to_qasm3() << std::endl; return EqualityError; } } + // The same collapse happens for duplicate names within a single + // multi-parameter gate. { QuantumCircuit circ(1, 0); - auto a0 = Parameter("a"); - auto a1 = Parameter("a"); - - try { - circ.r(a0, a1, 0); - } catch (const std::invalid_argument &err) { - if (!expect_duplicate_error(err, "same gate")) { - return EqualityError; - } - } catch (...) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : same gate wrong exception type" << std::endl; - return EqualityError; - } - - const std::string expected = - "OPENQASM 3.0;\n" - "include \"stdgates.inc\";\n" - "qubit[1] q;\n"; - if (circ.num_parameters() != 0 || circ.to_qasm3() != expected) { - std::cerr << " parameter_symbols_duplicate_name_conflict test : same gate mutated circuit" << std::endl; + auto b0 = Parameter("b"); + auto b1 = Parameter("b"); + circ.r(b0, b1, 0); + + if (circ.num_parameters() != 1 + || circ.parameter_symbols() != std::vector({"b"})) { + std::cerr << " parameter_symbols_duplicate_name_limitation test : multi-param gate did " + << "not collapse to a single symbol" << std::endl; return EqualityError; } } @@ -1288,7 +1220,7 @@ int test_circuit(int argc, char** argv) { num_failed += RUN_TEST(test_to_qasm3_multi_regs); num_failed += RUN_TEST(test_parameter_symbols); num_failed += RUN_TEST(test_parameter_symbols_unsupported_name); - num_failed += RUN_TEST(test_parameter_symbols_duplicate_name_conflict); + num_failed += RUN_TEST(test_parameter_symbols_duplicate_name_limitation); num_failed += RUN_TEST(test_to_qasm3_compose_parameterized); num_failed += RUN_TEST(test_to_qasm3_append_parameterized); num_failed += RUN_TEST(test_to_qasm3_parameterized);