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..f145965 --- /dev/null +++ b/releasenotes/notes/qasm3-parameterized-export-146-6f6f1d31c9e74d2b.yaml @@ -0,0 +1,40 @@ +--- +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()`. +issues: + - | + `QuantumCircuit::parameter_symbols()` (and therefore the OpenQASM 3 + exporter) identifies parameters by their original symbol name. Distinct + `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/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..931015d 100644 --- a/src/circuit/quantumcircuit_def.hpp +++ b/src/circuit/quantumcircuit_def.hpp @@ -58,6 +58,7 @@ namespace circuit class ControlFlowOp; class IfElseOp; +class Qasm3Exporter; static Parameter null_param; @@ -65,6 +66,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 +81,26 @@ 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 + + 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() {} @@ -1345,21 +1367,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 +1389,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); + 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 ? } - qk_circuit_instruction_clear(op); } for (auto m : circ.measure_map_) { @@ -1551,382 +1572,27 @@ 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 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. /// @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..1280264 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,204 @@ 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; +} + +// 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); + + 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; + } + 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; + } + + 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" + "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 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; + } + } + + 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 +1218,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_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); + 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;