Skip to content

Compiling Circuits

Clifft compiles a supported circuit input into a reusable Program. Compilation resolves Clifford coordinates, symbolic dependencies, active-state transitions, and executable actions before sampling starts.

circuit input -> parse -> trace to HIR -> optimize -> plan and prepare -> Program

Most users only need clifft.compile(). The lower-level functions expose the same pipeline for inspecting intermediate representations or supplying a custom optimization sequence.

Default path

Pass circuit text directly to clifft.compile():

import clifft

program = clifft.compile("""
    H 0
    CNOT 0 1
    T 2
    M 0 1 2
""")

The returned program can be reused by ordinary sampling, survivor sampling, fixed-fault importance sampling, or an exact-query API when the circuit meets that API's requirements:

result = clifft.sample(program, shots=1_000, seed=42)

Compilation options such as postselection_mask, normalize_syndromes, expected_detectors, and expected_observables define the result contract used during sampling.

For detector-based post-selection, set normalize_syndromes=True unless raw measurement parities are intentional. Clifft computes the noiseless detector and observable reference once during compilation and stores the normalization in the program. sample_survivors() then applies the post-selection mask to those normalized detector values; it does not recompute the reference per call.

Circuits containing LEAKAGE, LOSS, or LEVEL_TRANSITION annotations take a trajectory-specific path. Pass the circuit and model to clifft.noncomp.sample() rather than compiling one fixed program first; see Leakage and Loss.

Inspect or customize the pipeline

The following APIs are intended for power users working on compiler behavior, custom optimization, or intermediate-representation inspection.

Parse the circuit

clifft.parse() returns a Circuit without tracing or lowering it:

import clifft

circuit = clifft.parse("H 0\nCNOT 0 1\nM 0 1")

Use clifft.parse_file() for a file, or the format-specific parsing functions listed on Circuit Inputs for OpenQASM 2.

Trace to the Heisenberg IR

clifft.trace() absorbs Clifford operations into an offline tableau and emits explicit non-Clifford operations, measurements, noise, and classical outputs in the Heisenberg frame:

import clifft

hir = clifft.trace(clifft.parse("H 0\nCNOT 0 1\nT 0\nM 0 1"))
print(hir)

Optimize the HIR

The default pass manager applies the same HIR optimization sequence used by clifft.compile():

import clifft

hir = clifft.trace(clifft.parse("H 0\nCNOT 0 1\nT 0\nM 0 1"))
clifft.default_hir_pass_manager().run(hir)

Construct a focused pipeline when individual passes need to be controlled:

import clifft

hir = clifft.trace(clifft.parse("H 0\nT 0\nM 0"))
passes = clifft.HirPassManager()
passes.add(clifft.PeepholeFusionPass())
passes.run(hir)

Pass hir_passes=None to clifft.compile() to skip HIR optimization while keeping the one-step API.

Plan and prepare execution

clifft.lower() converts an optimized HirModule into the same public Program type returned by clifft.compile():

import clifft

hir = clifft.trace(clifft.parse("H 0\nT 0\nM 0"))
clifft.default_hir_pass_manager().run(hir)
program = clifft.lower(hir)

Internally, lowering first creates a semantic sampling plan that selects active coordinates and records expressions, rotations, measurements, width transitions, and outputs. Executable preparation then creates the immutable descriptors and dependency tables used by the executor and selects supported scalar or SIMD kernels.

These decisions happen before execution. Runtime kernels do not evolve a tableau, choose coordinates, localize Paulis, or discover dependencies. See Software Architecture for the contracts between HIR, semantic planning, executable preparation, and shot execution.