Compilation

MicroQuantum’s compiler turns a program (a QuantumCircuit or an IRCircuit) into an optimized, target-aware intermediate representation that can be rebuilt into an executable circuit.

There is a single public entry point: the Compiler class.

from microquantum import Compiler, QuantumCircuit

qc = QuantumCircuit(2).h(0).cx(0, 1)
result = Compiler(optimization_level=1).compile(qc)
compiled = result.circuit()      # executable QuantumCircuit

The pipeline is fixed and deterministic:

  1. Validate — the IR is structurally validated; malformed IR fails loudly.

  2. Optimize — safe, semantics-preserving transformations only.

  3. Decompose — only when a Target is supplied, gates are expanded into the target’s native basis (exact, standard identities).

  4. Diagnose — target-compatibility problems are reported as a list; the compiler never silently drops an operation it cannot handle.

A CompilationResult carries the source IR, the compiled IR, the applied passes, the diagnostics and rich metadata; its circuit() method rebuilds a QuantumCircuit that runs on all existing backends.

Optimization levels

optimization_level selects how aggressive the standard pipeline is. Levels outside 0..2 are rejected with ValueError.

Level

Passes

Used for

0

none (validation only)

probing “what does my program look like as IR?”

1

remove-identity-gates, cancel-adjacent-inverse

general safe simplification

2

level 1 + combine-rotations

reducing rotation count (never for symbolic angles)

from microquantum import Compiler, QuantumCircuit

qc = QuantumCircuit(2)
qc.h(0)
qc.h(0)              # identity pair -> removed
qc.cx(0, 1)

result = Compiler(optimization_level=1).compile(qc)
print(result.passes_applied)
# ['remove-identity-gates', 'cancel-adjacent-inverse']
print(result.result.num_gates)          # 1  (the cnot)

The individual passes are also available directly (RemoveIdentityGates, CancelAdjacentInverse, CombineRotations) and can be composed with IRPassManager or passed to Compiler.compile(..., passes=[...]).

Safe transformations

Every transformation preserves the state vector exactly (up to floating point). The implemented rules are structural and numeric-only:

  • identity gates and zero-angle rotations R(0) are removed;

  • adjacent inverse pairs cancel: self-inverse gates (H X Y Z CNOT CZ SWAP), the S/Sdg and T/Tdg pairs, and opposite rotations R(a) R(-a) (or a full 2*pi turn);

  • adjacent same-axis rotations on one qubit fuse: R(a) R(b) -> R(a+b).

Symbolic Parameter angles are never fused, cancelled or folded, because an unbound RX(theta) RX(theta) is textually RX(2*theta) only after binding — optimizing it away would change the program.

from microquantum import Compiler, Gate, IRCircuit, Parameter

theta = Parameter("theta")
symbolic = IRCircuit(num_qubits=1, operations=[
    Gate(name="rx", qubits=(0,), params=(theta,)),
    Gate(name="rx", qubits=(0,), params=(theta,)),
])
out = Compiler(optimization_level=2).compile(symbolic)
assert out.result.gate_names() == {"rx": 2}   # never simplified

Measurements

Terminal measurements are preserved through compilation and rebuilding: compile a measured circuit and result.circuit() keeps the same measured qubits in the same order. An unmeasured circuit is treated as a full sampling program, so the compiler appends terminal measurements on every qubit. Measurement nodes act as barriers: gates on different sides never cancel across a measurement.

from microquantum import Compiler, QuantumCircuit

qc = QuantumCircuit(3).x(1).measure(2).measure(0)
compiled = Compiler(optimization_level=1).compile(qc).circuit()
assert compiled.measurements == [2, 0]

QuantumCircuit -> IR -> QuantumCircuit is lossless: rotation signs are recovered exactly, so from_ir(to_ir(qc)) reproduces the same behavior (with the sdk’s exact-amplitude simulators the resulting state vectors agree to machine precision).

Targets & capabilities

Compiling toward a Target adds basis decomposition and compatibility diagnostics. This is a two-tier contract:

  • the compiler reports problems as a diagnostics list and flips is_compatible to False (soft, inspectable);

  • the runtime raises ValueError when an execution plan or backend refuses the program (hard).

Unsupported gates are never silently dropped — they remain in the compiled IR and are listed in diagnostics. The target’s native_gates may spell the controlled-NOT as "cx"; the IR calls the same gate "cnot" and the two are treated as identical.

from microquantum import Compiler, QuantumCircuit, Target

target = Target(
    name="limited",
    num_qubits=2,
    native_gates=("h", "cx"),   # note the cx spelling
    supports_measurement=True,
)
result = Compiler().compile(QuantumCircuit(2).cz(0, 1), target=target)
print(result.circuit().to_ir().gate_names())   # {'h': 2, 'cnot': 1} (cz lowered)
assert result.is_compatible

Standard exact decompositions are used when they improve compatibility:

*: cz(c, t) -> h(t), cnot(c, t), h(t) (basis has h and cnot) *: swap(a, b) -> cnot(a,b), cnot(b,a), cnot(a,b) (basis has cnot)

Metrics

CompilationResult.metadata (JSON-safe) reports the before/after shape:

Key

Meaning

source_qubits / compiled_qubits

qubit counts

source_gates / compiled_gates

gate counts

source_depth / compiled_depth

circuit depth (via the depth() method)

source_gates_by_type / compiled_gates_by_type

gate-name -> count dictionaries

optimization_level

the level used

Backends

Compiled circuits are ordinary QuantumCircuit objects and run on every local simulator. The probability vectors are identical across the state vector, density-matrix, MPS and TTN backends.

from microquantum import Compiler, MPSBackend, QuantumCircuit, StatevectorBackend

qc = QuantumCircuit(3).h(0).h(0).cx(0, 1).cx(1, 2).measure_all()
compiled = Compiler(optimization_level=1).compile(qc).circuit()
sv = StatevectorBackend().run(compiled, shots=4000, seed=10).counts
mps = MPSBackend().run(compiled, shots=4000, seed=10).counts
print(sv, mps)   # statistically consistent

QASM

A compiled circuit exports standard OpenQASM 2.0 and re-imports losslessly:

from microquantum import Compiler, QuantumCircuit

qc = QuantumCircuit(2).h(0).cx(0, 1)
compiled = Compiler(optimization_level=1).compile(qc).circuit()
restored = QuantumCircuit.from_qasm(compiled.qasm())

Determinism

Compilation is deterministic: compiling the same input twice yields an identical CompilationResult (including its JSON serialization).

Limitations

The compiler deliberately stops before hardware. Out of scope today:

  • qubit routing / layout synthesis and coupling-map-aware scheduling;

  • vendor device calibration, pulse or timing control;

  • GPU/distributed/HPC orchestration, LLVM or external frameworks;

  • optimal-circuit synthesis search (Qiskit/Cirq/pytket/SAT-SMT/ML are not used);

  • noisy/error-mitigated compilation and QEC.

See the IR API reference (api/microquantum/ir, including IRCircuit, IRPass and IRPassManager) for the internal representation and the pass classes. Run the whole flow end to end in the examples: examples/22_ir_roundtrip.py through examples/29_execute_compiled_circuit.py.