OpenQASM and Python Import/Export
Circuits can be converted between in-memory strings, files, and Python reconstruction code. OpenQASM is suitable for exchanging circuits with other tools; Python export generates readable UnitaryLab construction code.
from unitarylab import Circuit
qc = Circuit(2)
qc.h(0)
qc.cx(0, 1)
qasm3 = qc.to_qasm()
qasm2 = qc.to_qasm2()
restored = Circuit.from_qasm(qasm3)
qc.to_qasm_file("circuit.qasm")
restored_from_file = Circuit.from_qasm_file("circuit.qasm")to_qasm() exports OpenQASM 3.0 by default. from_qasm() parses OpenQASM 2.0 or 3.0 according to the version declaration. decompose controls block expansion; transpile=True transpiles to a basic gate basis before export.
Public interfaces
| Interface | Purpose |
|---|---|
to_qasm(...) | Return an OpenQASM 3.0 string |
to_qasm2(qreg_name="q", creg_name="c", decompose=False, transpile=False, gates_to_unroll=None, transpile_basis="default") | Return an OpenQASM 2.0 string |
from_qasm(code, gate_storage="python") | Parse a version declaration and create a circuit |
from_qasm_file(path, gate_storage="python") | Read a circuit from a file |
to_qasm_file(path, ...) | Write OpenQASM 3.0 to a file |
to_python(...) / to_python_file(...) | Generate UnitaryLab or CUDA-Q Python code |
to_cudaq() | Return a CUDA-Q kernel (requires CUDA-Q) |
Both to_qasm() and to_qasm2() support decomposition and transpilation options. decompose=True is equivalent to expanding one level; a positive integer specifies the number of levels. transpile=True fully expands block gates, regardless of the decompose depth. transpile_basis and gates_to_unroll control gate-basis transpilation before export.
The lower-level unitarylab.backend.qasm module provides gate_sequence_to_qasm*, qasm*_to_gate_sequence, and circuit_from_gate_sequence; use these when directly handling GateSequence objects.
Supported formats and constraints
- Common single-qubit, parameterized, controlled, SWAP, measurement, and custom
gateoperations are supported. - Parameters must be numeric; classical control flow such as
ifandwhileis not supported. - OpenQASM 3.0 uses a UnitaryLab extension for
unitarygates with stored matrices. UnitaryLab can restore them, but this is not a standard interchange format. OpenQASM 2.0 has no arbitrary-matrix syntax; useto_qasm2(transpile=True)to decompose them into a gate basis first. - Non-contiguous block gates may be inlined; not every custom gate supports a complete round trip.
- Read OpenQASM only from trusted sources and limit input file size. Parsing does not execute embedded Python, but very large circuits can still consume substantial memory.
Python source
to_python(*, framework="unitarylab", circuit_name="Unitarylab Circuit", variable_name="circuit") generates UnitaryLab reconstruction code by default. It supports common gates, block gates, measurement, and gates with stored matrices; other gates are reconstructed using QuantumGate information. circuit_name=None reuses the original circuit name. to_python_file(filepath, ...) writes the code and returns the resolved pathlib.Path.
import numpy as np
codegen_qc = Circuit(2)
codegen_qc.p(np.pi, 0)
codegen_qc.ry(np.pi / 2, 0)
codegen_qc.cx(0, 1)
print(codegen_qc.to_python())After exporting, perform a round-trip check by comparing gate counts and measurement mappings, and compare matrices for small circuits without measurements.
CUDA-Q export
from unitarylab import Circuit
bell = Circuit(2)
bell.h(0)
bell.cx(0, 1)
cudaq_source = bell.to_python(framework="cudaq", variable_name="kernel")
print(cudaq_source)
# With CUDA-Q installed, construct an executable kernel directly:
# kernel = bell.to_cudaq()Generating source with to_python(framework="cudaq") does not require CUDA-Q to be installed. Executing the generated code or calling to_cudaq() requires this optional dependency. to_cudaq() returns a non-parameterized CUDA-Q kernel whose gate parameters are fixed to the numeric values stored in the current circuit.
Export expands block gates. Arbitrary unitary matrices are limited to one or two qubits. Circuits without measurement are allowed; if measurement is present, it must be terminal and map every qubit in order as 0→0, 1→1, …. Mid-circuit measurement, partial measurement, and reordered classical mappings are unsupported. Unsupported gates raise an exception.