Skip to content

feat: add OpenQASM 3 export support to qiskit-cpp (#146)#157

Open
GoiBasia wants to merge 1 commit into
Qiskit:mainfrom
GoiBasia:QASM3-Export-branch
Open

feat: add OpenQASM 3 export support to qiskit-cpp (#146)#157
GoiBasia wants to merge 1 commit into
Qiskit:mainfrom
GoiBasia:QASM3-Export-branch

Conversation

@GoiBasia

@GoiBasia GoiBasia commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR version implements OpenQASM 3 export support entirely within qiskit-cpp. It adds a dedicated Qiskit::qasm3::dumps(...) exporter, keeps QuantumCircuit::to_qasm3() as a compatibility wrapper, and reuses the existing parameter serialization path to support parameter-aware QASM3 output.

No changes to the upstream Qiskit library or its C API are required for this implementation.

Details and comments

Fix

src/qasm3/qasm3_exporter.hpp

Adds the dedicated QASM 3 exporter and public Qiskit::qasm3::dumps(...) API. It adds RAII wrappers for C API resources such as QkOpCounts, QkCircuitInstruction, and qk_param_str() strings, and emits parameter declarations before registers and operations.

src/circuit/quantumcircuit_def.hpp

Removes inline QASM text generation from QuantumCircuit::to_qasm3() and delegates to the new exporter. It also records parameter-symbol metadata when parameterized gates are added, appended, composed, copied, or recovered through operator[].

src/circuit/quantumcircuit.hpp

Includes the new QASM 3 exporter header so the compatibility to_qasm3() path can delegate cleanly.

src/circuit/parameter.hpp

Tracks local parameter-symbol metadata on C++ Parameter objects. Symbol names are preserved across copies and parameter operations, including arithmetic and common unary functions, so expressions retain the names needed for QASM declarations.

test/test_circuit.cpp

Adds coverage for parameter-aware QASM export, including direct symbolic parameters, expressions, numeric-only parameters, uppercase U(...), explicit parameter names, invalid explicit names, parameterized compose(...), append(src[0]), and the fail-closed path when default names cannot be recovered.

Example Result

A parameterized circuit now exports self-contained OpenQASM 3:

OPENQASM 3.0;
include "stdgates.inc";
input float[64] theta;
qubit[1] q;
bit[1] c;
rx(theta) q[0];

Numeric-only parameters do not emit unnecessary input declarations:

OPENQASM 3.0;
include "stdgates.inc";
qubit[1] q;
bit[1] c;
rx(0.25) q[0];

Coverage

The qiskit-cpp tests cover:

  • existing non-parameterized QASM output
  • single parameter declarations
  • multiple parameter declarations in wrapper metadata order
  • parameter expressions
  • numeric-only parameterized gates
  • uppercase U(...) output
  • Qiskit::qasm3::dumps(circuit, names) happy path
  • wrong parameter count, empty name, and duplicate-name validation
  • compatibility through QuantumCircuit::to_qasm3()
  • parameter metadata through compose(...)
  • parameter metadata through append(src[0])
  • fail-closed default export when wrapper-side names are unavailable

Verification

The qiskit-cpp fix was verified with:

cmake --build test/build
ctest -V -C Debug --test-dir test/build
git diff --check

The qiskit-cpp test suite passed 3/3.

Checklist

  • I have added the tests to cover my changes.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • Unit tests under test/

@CLAassistant

CLAassistant commented Jun 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@doichanj

doichanj commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

I think having a list of Parameter in QuantumCircuit is not good, because the original list is on the rust side. I think the way #155 is the right way to get a list of parameter. However I think it will take some time to be merged into Qiskit

@GoiBasia

GoiBasia commented Jun 9, 2026

Copy link
Copy Markdown
Contributor Author

I think having a list of Parameter in QuantumCircuit is not good, because the original list is on the rust side. I think the way #155 is the right way to get a list of parameter. However I think it will take some time to be merged into Qiskit

Thank you very much for your kind review and suggestions. I agree. I would like to remove the QuantumCircuit-side parameter metadata from this PR. For the qiskit-cpp-only path #157, I' d like to keep the exporter but require explicit parameter names via Qiskit::qasm3::dumps(circuit, names) for parameterized circuits. The default dumps(circuit) / to_qasm3() path will fail closed for parameterized circuits until Qiskit exposes the canonical Rust-side parameter names through qk_circuit_param_symbol_name. After Qiskit/qiskit#16386 lands, I'd like to update this to use that C API directly.

@GoiBasia

Copy link
Copy Markdown
Contributor Author

Thank you for review. I have implemented the # 146 requested solution in this PR.

  1. Added the stub function: std::vector<std::string> QuantumCircuit::parameter_symbols(void) const

This currently returns the parameter symbol names tracked on the C++ wrapper side. The implementation can later be replaced by the C API once symbol-name enumeration is available from the Rust-side circuit data.

  1. Integrated the QASM3 exporter with this function. The default qasm3::dumps(circuit) path now calls circuit.parameter_symbols(), so circ.to_qasm3() automatically emits declarations such as:

    input float[64] theta;
  2. Added the temporary duplicate-symbol limitation: Qiskit C++ now rejects distinct Parameter objects with the same symbol name, for example:

    Parameter a0("a");
    Parameter a1("a");
    
    circ.ry(a0, 0);
    circ.ry(a1, 1);

Verification:

100% tests passed, 0 tests failed out of 4

This keeps the current implementation independent of the new Qiskit C API, while preserving a clean replacement point for the future C API-based implementation.

Comment thread src/circuit/quantumcircuit_def.hpp Outdated
std::map<std::string, std::shared_ptr<int>> parameter_symbol_refs;
std::map<uint_t, std::vector<parameter_symbol_refs_t>> instruction_parameter_symbols;
};
std::shared_ptr<ParameterSymbolData> parameter_symbol_data_ = std::make_shared<ParameterSymbolData>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I mentioned (#157 (comment)) a list of parameter symbols should not be stored on C++ side.
A list of parameter symbols can be parsed by traversing instructions in the circuit

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I updated the fix accordingly.

The implementation no longer stores a circuit-level parameter symbol list on the C++ side. I removed ParameterSymbolData, parameter_symbol_data_, instruction_parameter_symbols, and the symbol metadata from Parameter.

QuantumCircuit::parameter_symbols() now derives the symbol list by traversing the circuit instructions and parsing each instruction parameter expression from the actual QkParam values. This keeps the temporary implementation local to the stub and avoids maintaining a duplicate circuit parameter list in C++.

Verified with the local test suite: 100% tests passed, 0 tests failed out of 4

@GoiBasia GoiBasia changed the title Qiskit C++ Issue #146: Only Add OpenQASM 3 export support in qiskit-cpp feat: add OpenQASM 3 export support to qiskit-cpp (#146) Jun 15, 2026
@GoiBasia

GoiBasia commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@doichanj Thanks for the review. I pushed a new commit (cec0dcb) that keep the Qiskit::qasm3::dumps(...) API, but hardens the exporter behavior around the edge cases discussed here.

Main changes:

  • Added an ExportContext / identifier allocator so parameter names, the synthetic qreg name, and classical register names are emitted consistently and deconflicted against OpenQASM reserved words, stdgates names, and exporter helper names.
  • Explicit parameter renaming now rewrites both the input float[64] ...; declaration and all operation parameter expressions. This fixes cases like declaring theta_0 while still emitting rx(theta).
  • Parameter expression rewriting is token-based, so it avoids partial replacements such as theta inside theta2, and does not treat function names like sin(...) as parameter symbols.
  • Fixed invalid CU1/CU3 custom-gate definitions by adding the missing operand commas.
  • Added explicit global_phase handling so it exports as gphase(...) without a fake qubit operand.
  • Added regression coverage for explicit renaming, expression renaming, identifier conflicts, reserved names, standard-gate-name conflicts, CU definitions, global phase, and invalid explicit names.
  • Updated the release note to clarify that parameter symbols are currently recovered from instruction parameter expressions and cross-checked with qk_circuit_num_param_symbols until Qiskit C exposes canonical parameter names or UUIDs.
  • Removed the full-circuit preflight copy before parameterized gate insertion.

@GoiBasia GoiBasia force-pushed the QASM3-Export-branch branch from e66980a to cec0dcb Compare June 15, 2026 11:47
@GoiBasia GoiBasia requested a review from doichanj June 16, 2026 00:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants