OpenQASM Support

Yao provides built-in support for OpenQASM 2.0, a widely-used quantum assembly language. This allows you to:

  • Export Yao circuits to OpenQASM format for use with other quantum computing frameworks
  • Import OpenQASM circuits from other tools into Yao for simulation and analysis
  • Save circuits to files for later use
  • Share circuits across different quantum computing platforms

Basic Usage

Converting Circuits to QASM

Use the qasm function to convert a Yao circuit to an OpenQASM string:

julia> using Yao
julia> # Simple single-qubit gate qasm(put(2, 1=>X))ERROR: MethodError: no method matching qasm(::PutBlock{2, 1, XGate}) The function `qasm` exists, but no method is defined for this combination of argument types.
julia> # Control gate (outputs QASM 2.0 compatible cx) qasm(control(2, 1, 2=>X))ERROR: MethodError: no method matching qasm(::ControlBlock{XGate, 1, 1}) The function `qasm` exists, but no method is defined for this combination of argument types.
julia> # A complete circuit circuit = chain(2, put(1=>H), control(1, 2=>X))nqubits: 2 chain ├─ put on (1) │ └─ H └─ control(1) └─ (2,) X
julia> qasm(circuit)ERROR: MethodError: no method matching qasm(::ChainBlock{2}) The function `qasm` exists, but no method is defined for this combination of argument types.

To include the full QASM header (version, includes, and register declarations), use include_header=true:

julia> qasm(circuit; include_header=true)ERROR: MethodError: no method matching qasm(::ChainBlock{2}; include_header::Bool)
The function `qasm` exists, but no method is defined for this combination of argument types.

Automatic Circuit Simplification

Before converting to QASM, circuits are automatically simplified using Optimise.canonicalize. This:

  • Flattens nested chain blocks
  • Converts complex gates to basic types
  • Eliminates redundant structure

This means you can export complex circuits built with high-level constructs:

julia> using Yao.EasyBuild
julia> # QFT circuit uses nested chains and controlled phase gates circuit = qft_circuit(3)nqubits: 3 chain ├─ chain │ ├─ put on (1) │ │ └─ H │ ├─ control(2) │ │ └─ (1,) shift(1.5707963267948966) │ └─ control(3) │ └─ (1,) shift(0.7853981633974483) ├─ chain │ ├─ put on (2) │ │ └─ H │ └─ control(3) │ └─ (2,) shift(1.5707963267948966) └─ chain └─ put on (3) └─ H
julia> println(qasm(circuit; include_header=true))ERROR: MethodError: no method matching qasm(::ChainBlock{2}; include_header::Bool) The function `qasm` exists, but no method is defined for this combination of argument types.

Parsing QASM to Circuits

Use the parseblock function to convert an OpenQASM string to a Yao circuit:

julia> qasm_str = """
       OPENQASM 2.0;
       include "qelib1.inc";
       qreg q[2];
       creg c[2];
       h q[0];
       cx q[0],q[1];
       measure q -> c;
       """;
julia> task = parseblock(qasm_str)ERROR: MethodError: no method matching parseblock(::String) The function `parseblock` exists, but no method is defined for this combination of argument types.
julia> task.circuitERROR: UndefVarError: `task` not defined in `Main` Suggestion: add an appropriate import or assignment. This global was declared but not assigned.

The returned SimulationTask contains:

  • task.circuit: The quantum circuit as a ChainBlock
  • task.outcomes: References to measurement outcomes

Roundtrip Verification

Circuits can be converted to QASM and back with functional equivalence. This is verified by comparing fidelity:

using Yao, Yao.EasyBuild

# Create a circuit
circuit = variational_circuit(4, 2)

# Convert to QASM and back
qasm_str = qasm(circuit; include_header=true)
parsed = parseblock(qasm_str).circuit

# Verify equivalence
reg1 = rand_state(4)
reg2 = copy(reg1)
apply!(reg1, circuit)
apply!(reg2, parsed)
fidelity(reg1, reg2) ≈ 1.0  # true

This works for EasyBuild circuits including qft_circuit, variational_circuit, and rand_google53.

Saving and Loading Circuits

Save to File

using Yao

# Create a circuit
circuit = chain(3,
    put(1=>H),
    control(1, 2=>X),
    control(2, 3=>X),
    put(1=>Rz(0.5)),
    Measure(3)
)

# Convert to QASM and save
qasm_string = qasm(circuit; include_header=true)
write("my_circuit.qasm", qasm_string)

Load from File

using Yao

# Read QASM file
qasm_string = read("my_circuit.qasm", String)

# Parse to Yao circuit
task = parseblock(qasm_string)
circuit = task.circuit

# Use the circuit
reg = zero_state(nqubits(circuit))
apply!(reg, circuit)

Supported Gates

Export (Yao → QASM)

Single-Qubit Gates:

Yao BlockQASM Output
I2id
X, Y, Zx, y, z
Hh
S, Sdags, inv @ s
T, Tdagt, inv @ t
Rx(θ), Ry(θ), Rz(θ)rx(θ), ry(θ), rz(θ)
shift(λ)p(λ)

Two-Qubit Gates:

Yao BlockQASM Output
control(n, c, t=>X)cx
control(n, c, t=>Y)cy
control(n, c, t=>Z)cz
control(n, c, t=>H)ch
control(n, c, t=>shift(λ))cu1(λ)
control(n, c, t=>Rz(θ))crz(θ)
control(n, (c1,c2), t=>X)ccx (Toffoli)
swap(n, i, j)swap
rot(kron(Z,Z), θ)rzz(θ)

Modifiers:

Yao BlockQASM Output
Daggered(G)inv @ g
control(n, -c, t=>G)negctrl @ g (QASM 3.0)
Note

The compiler automatically uses QASM 2.0 gates when possible (e.g., cx instead of ctrl @ x) for maximum compatibility. QASM 3.0 modifiers (ctrl @, negctrl @, inv @) are used as fallback for complex cases.

Import (QASM → Yao)

Single-Qubit Gates:

QASM GateYao Block
idI2
x, y, zX, Y, Z
hH
s, sdgS, Sdag
t, tdgT, Tdag
sxmatblock(SqrtX)
rx(θ), ry(θ), rz(θ)Rx(θ), Ry(θ), Rz(θ)
u1(λ), p(λ)shift(λ)
u2(ϕ,λ), u3(θ,ϕ,λ)matblock(...)
r(θ,ϕ)rot(cos(ϕ)X + sin(ϕ)Y, θ)

Two-Qubit Gates:

QASM GateYao Block
cx, cy, czcontrol(...=>X/Y/Z)
chcontrol(...=>H)
crz(θ)control(...=>Rz(θ))
cu1(λ), cp(λ)control(...=>shift(λ))
cu3(θ,ϕ,λ)control(...=>matblock(...))
swapswap(n, i, j)
rxx(θ)rot(kron(X,X), θ)
rzz(θ)rot(kron(Z,Z), θ)

Three-Qubit Gates:

QASM GateYao Block
ccxcontrol((c1,c2), t=>X)

Measurement:

QASM GateYao Block
measure q -> cMeasure(n)
measure q[i] -> c[j]Measure(n; locs=...)

Working with Other Frameworks

Export to Qiskit

using Yao

# Create circuit in Yao
circuit = chain(2, put(1=>H), control(1, 2=>X))

# Save as QASM
write("bell_state.qasm", qasm(circuit; include_header=true))

Then in Python with Qiskit:

from qiskit import QuantumCircuit
circuit = QuantumCircuit.from_qasm_file("bell_state.qasm")

Import from Qiskit

Save your Qiskit circuit:

from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()
qc.qasm(filename="qiskit_circuit.qasm")

Load in Yao:

using Yao

qasm_str = read("qiskit_circuit.qasm", String)
task = parseblock(qasm_str)
circuit = task.circuit

Export to Cirq, PennyLane, etc.

Since OpenQASM is a standard format, circuits exported from Yao can be loaded into any framework that supports QASM:

# Yao
write("circuit.qasm", qasm(circuit; include_header=true))
# Cirq
import cirq
from cirq.contrib.qasm_import import circuit_from_qasm
circuit = circuit_from_qasm(open("circuit.qasm").read())

# PennyLane
import pennylane as qml
circuit = qml.from_qasm(open("circuit.qasm").read())

Advanced: Noisy Simulation

For advanced users, Yao supports parsing circuits with noise models:

using Yao
using YaoBlocks: ErrorPattern, parse_noise_model

# Define noise model
noise_data = [
    Dict(
        "type" => "depolarizing",
        "operations" => ["x", "y", "z", "h"],
        "qubits" => [[0], [1]],
        "probability" => 0.01
    ),
    Dict(
        "type" => "depolarizing2",
        "operations" => ["cx"],
        "qubits" => [[0, 1]],
        "probability" => 0.02
    )
]

gate_errors, ro_errors = parse_noise_model(noise_data)

# Parse QASM with noise
qasm_str = """
OPENQASM 2.0;
include "qelib1.inc";
qreg q[2];
creg c[2];
h q[0];
cx q[0],q[1];
"""

task = parseblock(qasm_str, gate_errors)
# The circuit now includes noise channels after each gate

Supported noise types:

  • depolarizing / depolarizing2: Single/two-qubit depolarizing noise
  • thermal_relaxation: T1/T2 thermal relaxation
  • pauli: Pauli X/Y/Z errors with individual probabilities
  • amplitude_damping: Amplitude damping channel
  • phase_damping: Phase damping channel
  • phase_amplitude_damping: Combined amplitude and phase damping
  • coherent_unitary: Coherent unitary error
  • kraus: Custom Kraus operators
  • roerror: Readout error

Limitations

  • Only include "qelib1.inc" is supported for include statements
  • Custom gate definitions (gate ... { }) are not yet supported for parsing
  • Only one qreg and one creg are supported per program
  • Barriers are ignored (with a warning)
  • Conditional operations (if (c==n) gate) are not supported

API Reference

See qasm and parseblock in the Blocks manual for full API documentation.