FlagQuantum API Reference

September 22, 2026 · View on GitHub

FlagQuantum exposes one curated Python interface: import flagquantum as fq. Build a circuit, inspect its runtime plan, execute it through a stable result contract, and train parameterized programs with PyTorch.

Exact stable names are defined by public_api_v1.json, verified by executable contract tests, and rendered in the stable API inventory.

API map

TaskPrimary interfaceResult
Build a programfq.CircuitCircuit backed by FlagQuantum IR
Optimize a programflagquantum.compiler.optimizefq.CircuitIR
Compile for a selected tool and targetfq.compilefq.CircuitIR
Inspect executionfq.plan, Circuit.runtime_planExplainable runtime plan
Execute locally or remotelyfq.runfq.ExecutionResult
Define a trainable quantum layerfq.ModulePyTorch module
Trainfq.trainfq.TrainingResult
Package for a targetflagquantum.deployment.create_deployment_packageSealed deployment package

Build and execute

Run the complete CPU example with python -m examples.cpu_statevector, or use the same stable interfaces directly:

import flagquantum as fq

circuit = fq.Circuit(n_qubits=2).h(0).cx(0, 1)
options = fq.ExecutionOptions(mode="auto", precision="complex64")
plan = fq.plan(circuit, options=options)
result = fq.run(plan)

print(plan.identity)
print(plan.summary()["recommended_mode"])
print(result.plan.identity)
print(result.state)

n_qubits is the preferred public name for circuit size. Positional Circuit(2), n_wires=2, and the legacy nqubits=2 remain compatible; conflicting aliases fail during construction. Runtime, compiler, and IR internals continue to use wire for logical mappings.

Generated gate methods keep their concise positional form and also accept semantic qubit keywords. For example, h(0) and h(qubit=0) are equivalent; cx(0, 1) and cx(control=0, target=1) are equivalent. Symmetric two-qubit gates use qubit1= and qubit2=, while the generic Circuit.gate(...) and FlagQuantum IR continue to use wires=.

fq.run(...) -> fq.ExecutionResult is the single recommended execution entry point. ExecutionOptions owns backend-neutral execution configuration; measurement requests and an optional flagquantum.noise.NoiseModel are semantic program inputs. Unknown keywords fail before planning. Circuit.run(...) and fq.run(circuit, ...) are equivalent.

For an inspectable and reproducible execution, pass the result of fq.plan directly to fq.run. The supplied plan is validated and executed without replanning or recompiling, and result.plan is plan holds in the same process. Plan identity covers the canonical IR, resolved execution semantics, compiler pipeline, required environment, and selected decision. JSON round trips verify all fingerprints and the final SHA-256 identity before execution:

text = plan.to_json()
restored = fq.ExecutionPlan.from_json(text)
result = fq.run(restored)
assert result.plan.identity == plan.identity

An existing plan is closed to semantic overrides: passing options, measurements, or noise_model alongside it raises TypeError. Environment or world-size incompatibility fails before kernel launch rather than silently replanning or falling back. Provider submission and signed portability remain the responsibility of DeploymentPackage. flagquantum.runtime.run_native, flagquantum.simulation.mps.run_mps, and flagquantum.simulation.tensor_network.run_tensor_network are advanced interfaces for callers that explicitly need native backend result objects or backend-specific controls.

For remote execution, name the compiler and provider target explicitly:

result = fq.run(
    circuit,
    compiler="qsteed",
    target="quafu:Baihua",
    # Optional ordered logical-to-physical mapping:
    # target_qubits=(17, 18),
    shots=1024,
    name="bell calibration",
)
counts = result.measurement("counts").value[0]

This path compiles, packages, submits, and waits for the remote result without changing the stable fq.ExecutionResult return type. It never selects or substitutes a compiler or provider implicitly. Use fq.compile to inspect the compiled IR, and create_deployment_package when the sealed artifact must be persisted, signed, or submitted later.

name is optional. Omitting it uses the deployment default; an explicitly provided name is trimmed and must not be empty. The provider-assigned task ID remains independent of this display name.

target_qubits is also optional. When omitted, the selected compiler chooses a physical subgraph. When provided, its order maps logical wires to physical qubits and compilation fails unless the current target snapshot proves that the selection is valid and connected. An explicit mapping is never silently replaced.

One Pauli expectation can use the same remote entry point:

energy = fq.run(
    circuit,
    outputs=fq.expectation(0.5 * (fq.X(0) @ fq.X(1)) + fq.Z(0)),
    compiler="qsteed",
    target="quafu:Baihua",
    shots=4096,
).expectation()

The circuit is compiled once, then qubit-wise-commuting terms are measured in separate sealed jobs without changing the selected physical-qubit mapping. shots applies to each measurement group. The measurement statistics report the estimator standard error, group count, per-group shots, and total shots; provenance records every provider task and deployment identity. Mixed outputs and unsupported remote outputs fail before compilation or submission.

Optimize a program

Compiler optimization is an expert-facing, target-independent transformation:

import flagquantum.compiler as compiler

optimized_ir = compiler.optimize(circuit)
result = fq.run(optimized_ir, options=options)

optimize returns a new CircuitIR, leaves the input unchanged, and applies canonical rewrites to a fixed point. Use compiler.compile when a concrete target topology or target-aware lowering is required. The complete executable example is python -m examples.compiler_optimize.

For target-aware compilation, provide an explicit coupling map:

coupling = compiler.CouplingMap.line(circuit.n_qubits)
compiled_ir = compiler.compile(
    circuit,
    coupling_map=coupling,
    routing_strategy="auto",
)

The compiler emits only topology-valid two-qubit operations and records its routing decision in compiled_ir.metadata["routing"]. It does not select or invoke an execution backend. Run the complete example with python -m examples.target_aware_compilation.

Train with PyTorch

Execution and training are intentionally separate. A complete trainable program looks like ordinary PyTorch code:

import flagquantum as fq
import torch

def build_circuit(parameters, inputs=None):
    return (
        fq.Circuit(n_qubits=2)
        .ry(0, theta=parameters[0])
        .cx(0, 1)
        .ry(1, theta=parameters[1])
    )

module = fq.Module(
    build_circuit,
    n_parameters=2,
    policy=fq.RuntimePolicy(observable_wires=(1,)),
)
optimizer = torch.optim.Adam(module.parameters(), lr=0.01)

training = fq.train(
    module,
    optimizer=optimizer,
    objective=lambda value: value.mean(),
    steps=100,
)

print(training.losses[-1])

module(inputs) and module.forward(inputs) always return an autograd-compatible Tensor. Use module.execute(inputs) when the caller needs an ExecutionResult, provenance, runtime diagnostics, or explicit backend compatibility information. fq.run accepts Circuit, IR, or ExecutionPlan—not Module—and never updates parameters.

fq.train is intentionally a minimal, caller-owned PyTorch optimizer loop. It performs zero_grad, backward, and step, then returns fq.TrainingResult. Without logging or a callback, loss history is transferred from the execution device only once, after the optimizer loop. Observing a loss through logging or a callback requires host synchronization for that step. Checkpoint and resume belong to Module.save_checkpoint() and Module.load_checkpoint() or to an application-owned training loop; they are not hidden options of fq.train. Training lifecycle types such as PrecisionPolicy, SeedContract, and checkpoint errors live in the stable flagquantum.training namespace. Distributed training remains explicitly experimental under flagquantum.experimental.distributed.

ExecutionResult.require_value() returns the Module value or fails clearly. ExecutionResult.diagnostics() returns a versioned envelope containing metrics, provenance, runtime, and compatibility; keys inside those four sections may grow compatibly. TrainingResult.final_loss and its versioned summary() provide stable training output access.

The examples index provides runnable statevector, MPS, JAX, distributed, and deployment workflows.

Errors

Catch stable lifecycle categories from flagquantum.errors:

import flagquantum.errors as fqe

try:
    result = fq.run(fq.plan(circuit, options=options))
except fqe.ValidationError:
    ...  # invalid semantic input
except fqe.PlanningError:
    ...  # stale, tampered, or incompatible plan
except fqe.CapabilityError:
    ...  # requested capability is unavailable
except fqe.ExecutionError:
    ...  # execution or training failure

All categories inherit FlagQuantumError and their compatible Python built-in exception (ValueError, RuntimeError, or NotImplementedError). Wrong Python types and unknown keyword arguments continue to raise TypeError. Existing specific errors such as IRValidationError, IRSerializationError, and flagquantum.training.TrainingStateError remain available and now belong to the corresponding stable category.

Measurements

Describe mathematical observables with fq.X, fq.Y, and fq.Z, then request named outputs from fq.plan or fq.run. Pauli products use @; Hamiltonian sums and real coefficients use ordinary arithmetic.

outputs = (
    fq.expectation(fq.Z(0) + fq.Z(1), name="magnetization"),
    fq.expectation(fq.X(0) @ fq.Z(1), name="correlation"),
    fq.samples(wires=(0, 1)),
)
plan = fq.plan(
    circuit,
    outputs=outputs,
    options=fq.ExecutionOptions(shots=1024, seed=7),
)
result = fq.run(plan)

z_sum = result.expectation("magnetization")
xz_value = result.expectation("correlation")
bit_samples = result.require_samples()

The public output factories are expectation, probabilities, samples, and counts. Sampling and counts accept computational-basis wires or one unweighted Pauli product, such as fq.samples(fq.X(0) @ fq.Y(1)), and require a positive shot count. Use result.expectation(), result.expectations, result.probabilities, result.samples, and result.counts for the ordinary typed result path. Unsupported output kinds and missing shot counts fail before execution.

Use result.measurement(index_or_name) for a specific request, result.statevector() for a required statevector, and result.native() only when intentionally depending on an unstable backend-native object. Backend attributes are not implicitly forwarded through ExecutionResult.

The same output requests work on a resident Jiuding compute target. Sampling and count reduction execute without returning the full statevector:

result = fq.run(
    circuit,
    target="jiuding:gpu",
    outputs=(fq.samples(wires=(0, 1)), fq.counts(wires=(0, 1))),
    shots=1024,
)

bit_samples = result.require_samples()
outcome_counts = result.counts

Set JIUDING_WORKSPACE when calling from outside the workspace. Inspect result.runtime and result.provenance for the selected device, result transfer, counts aggregation, and CPU-fallback evidence.

Noise

Stable noisy execution accepts a flagquantum.noise.NoiseModel during planning:

import flagquantum.noise as fqn

noise = fqn.NoiseModel().add("x", fqn.bit_flip_channel(0.01))
plan = fq.plan(circuit, noise_model=noise)
restored = fq.ExecutionPlan.from_json(plan.to_json())
result = fq.run(restored)

The versioned model payload and its SHA-256 identity are verified as part of the plan. The first public alpha candidate supports stable noisy planning for mode="auto" and mode="density_matrix"; unsupported mode combinations fail during planning.

probabilities computes an exact joint marginal over the requested wires. A statevector or density-matrix result reduces the distribution it already carries — the squared amplitudes or the diagonal — over the complement of those wires, which is one sum over 2**n_wires values, and the surviving axes are returned in the order the request named them. A marginal whose total is not one is normalised, so a dense result that was handed in unnormalised still returns a distribution. An MPS or tensor-network result keeps the parity route, because reading a distribution out of one means materialising a dense state it exists to avoid; so does a target that exposes only expectation_ps, which recovers the marginal from 2**len(wires) Pauli-Z contractions. The default limit of eight wires bounds the marginal width itself; callers must set max_marginal_wires explicitly to request a wider one.

Core IR measurement nodes remain available to Runtime implementers for advanced capabilities such as bounded postselection, but are intentionally absent from the root user API.

Hardware Pauli measurements

Use create_pauli_measurement_plan to measure a Hamiltonian containing X, Y, and Z terms on shot-based hardware. It greedily groups qubit-wise-commuting terms, appends the required basis rotations, and creates one sealed deployment package per group:

import flagquantum.deployment as fqd

plan = fqd.create_pauli_measurement_plan(
    circuit,
    hamiltonian,
    backend=backend,
    shots=4096,
)

results = tuple(provider.run(package) for package in plan.packages)
energy = plan.expectation(tuple(result.counts for result in results))

X measurements append H; Y measurements append RZ(-pi/2) followed by H. Aggregation validates the number of groups, shot totals, bitstring widths, and real Hamiltonian coefficients before producing an expectation value. Each package records its group index, term indices, basis, routing evidence, and sealed deployment identity.

Dynamic backend assessment

Backend assessment is public as an experimental, read-only preflight. Provider deployment packaging and submission remain internal qualification workflows.

report = fq.experimental.dynamic.assess_dynamic_backend(
    circuit,
    backend,
)
assert report.compatible, report.blockers

The Amazon Braket IQM integration has been validated locally through the real SDK serializer and mocked task contract only; no real IQM QPU execution is claimed.

Provider-neutral dynamic conformance

Backend conformance vectors are repository verification assets rather than SDK API. Maintainers can run the local suite with pytest tests/test_dynamic_conformance.py; the Qiskit lane additionally requires pip install -e '.[qiskit]'. User code should call fq.experimental.dynamic.assess_dynamic_backend(...) for preflight checks. DynamicExecutionResult exposes final_samples, classical_register, mid_circuit_measurements, availability metadata, and to_execution_result() for projection into the canonical result contract. Local trajectory results also expose a statistics mapping with trajectory, measurement, reset, conditional-branch, observed-branch and elapsed-time counters. The same mapping is retained as runtime["dynamic_statistics"] by the canonical projection. run_dynamic(..., strategy="auto") uses batched statevector trajectories for eligible workloads of at least 32 shots and falls back to the reference trajectory path when batching would exceed max_batched_bytes (256 MiB by default) or the input is already batched. Callers may explicitly request strategy="trajectory" or "batched". statistics["gate_execution_strategy"] records the selected path for benchmark attribution. The Qiskit path also validates a full DynamicCircuit → OpenQASM 3 → Qiskit → Aer round trip and statistical agreement for random measurement branches.

Use the development microbenchmark to measure shot and mid-circuit-measurement scaling locally. Add --backend qiskit_aer after installing the optional Qiskit dependencies for a same-workload comparison:

python benchmarks/dynamic_trajectory.py \
  --shots 100 1000 --mid-circuit-measurements 1 2 4 \
  --json-output benchmarks/results/smoke/dynamic-trajectory.json

Use --flagquantum-strategy trajectory and batched in separate runs for a direct reference-versus-vectorized comparison.

This payload is development evidence only and does not support scalability or provider-performance claims.

Optional integration suites can be selected independently:

pytest -m qiskit
pytest -m pennylane
pytest -m braket

Dynamic circuit construction

The candidate-stable builder is isolated from experimental execution and provider integrations:

from flagquantum.dynamic import DynamicCircuit

circuit = DynamicCircuit(2)
circuit.h(0)
circuit.measure(0, classical_bit=0)
circuit.conditional("x", 1, classical_bit=0)

result = fq.experimental.dynamic.run_dynamic(circuit, shots=128, seed=7)
stable_result = result.to_execution_result()

DynamicCircuit and its CircuitIR encoding are candidate-stable pending API owner approval. run_dynamic and backend assessment remain experimental; routing, dialect export, deployment and provider adapters are internal. Stable dynamic execution will return the canonical fq.ExecutionResult; provider-native state and diagnostic fields will not be frozen into that contract.

Interoperability adapter contract

External framework adapters implement one candidate-stable, framework-neutral protocol under flagquantum.ecosystem. The default registry stores import-safe descriptors and loads an adapter implementation only when requested:

from flagquantum.ecosystem import available_adapters, get_adapter

assert available_adapters() == ("braket", "cirq", "cudaq", "pennylane", "qiskit")
adapter = get_adapter("qiskit")
result = adapter.import_program(external_circuit)
flagquantum_ir = result.ir

InteropConversionIssue, InteropConversionReport, InteropImportResult, and InteropExportResult define the common diagnostics boundary. Registries are immutable: adding a descriptor returns a new registry and cannot alter the process-wide default. Resolving the Qiskit descriptor imports no Qiskit module; the external dependency is loaded only when conversion is requested. Adapter API mismatches and registered/loaded identity mismatches fail before use. InteropRegistry.to_dict() provides a machine-readable inventory for tooling and review without probing or importing dependencies. The default registry instance and each Braket/Cirq/CUDA-Q/Qiskit/PennyLane adapter remain experimental implementation details; they are not part of the candidate-stable export list.

PennyLane QuantumScript interoperability

PennyLane is an optional control-plane adapter and is never a FlagQuantum runtime dependency. On Python 3.11 or newer, install it with pip install 'flagquantum[pennylane]' and convert only at the immutable QuantumScript boundary:

from flagquantum.ecosystem.pennylane import from_pennylane, to_pennylane

ir = from_pennylane(quantum_script)
round_trip = to_pennylane(ir)

The v1 adapter is intentionally static and complex128-first. It supports the gate map recorded in contracts/pennylane-interop-contract.toml, bound real scalar parameters, and contiguous integer wires. QNodes, devices, execution, shots, measurement processes, autograd bridges, and symbolic parameters remain out of scope and fail closed. Nonstandard wire labels can only be flattened with an explicit allow_lossy=True report. PennyLane objects do not cross into the compiler, PyTorch runtime, Torch-FL, CUDA, vendor accelerator, or QPU layers.

Adapter authors use InteropRoundTripCase, InteropRejectionCase, and run_adapter_conformance() to apply the same framework-neutral identity, lossless round-trip, fail-closed, and explicit-loss checks to every adapter. The returned InteropConformanceResult.to_dict() payload uses the versioned flagquantum_interop_conformance_v1 schema. See Interoperability adapter development for the required adapter layout and evidence boundary.

Cirq circuit interoperability

Cirq is an optional static conversion boundary. Install it with pip install 'flagquantum[cirq]' and convert only cirq.Circuit artifacts:

from flagquantum.ecosystem.cirq import from_cirq, to_cirq

ir = from_cirq(cirq_circuit)
round_trip = to_cirq(ir)

The v1 adapter supports the gate subset in contracts/cirq-interop-contract.toml, contiguous LineQubit indices, bound real parameters, and an explicit statevector qubit order. Moment packing, measurements, global phase operations, symbolic parameters, tags, classical controls, noise, and unsupported gates fail closed with machine-readable diagnostics. Cirq objects remain inside flagquantum.ecosystem.cirq and never enter the compiler or runtime.

CUDA-Q kernel export

CUDA-Q is an optional, Linux-only heterogeneous toolchain. Install it with pip install 'flagquantum[cudaq]', then export a bound static-unitary program:

from flagquantum.ecosystem.cudaq import export_cudaq, to_cudaq

kernel = to_cudaq(circuit)
result = export_cudaq(circuit)
assert result.report.lossless

The v1 boundary is deliberately one-way and does not execute the kernel. Reverse conversion, symbolic or non-finite parameters, measurements, noise, control flow, kernel arguments, unsupported gates, and lossy export fail closed with machine-readable diagnostics. CUDA-Q objects remain inside flagquantum.ecosystem.cudaq. Statevector conformance explicitly reverses bit axes because CUDA-Q wire zero is least-significant while FlagQuantum wire zero is most-significant.

Qiskit IR interoperability

Qiskit is an optional control-plane adapter, not a FlagQuantum runtime dependency. Install it with pip install 'flagquantum[qiskit]', then convert at the versioned IR boundary:

from flagquantum.ecosystem.qiskit import from_qiskit, to_qiskit

ir = from_qiskit(qiskit_circuit)
round_trip = to_qiskit(ir)

These Qiskit-specific functions and result types implement the common adapter protocol rather than defining a parallel framework architecture. They remain experimental and have their own tested dependency-version window.

Both directions fail closed when an operation, control-flow construct, or parameter expression cannot be represented losslessly. Qiskit arithmetic parameter expressions are imported after symbolic simplification when they use only addition, multiplication, numeric constants, and negation; subtraction and division by a numeric constant are represented through that subset. Functions, powers, division by a parameter, and other symbolic operations are rejected. Use import_qiskit() or export_qiskit() to receive the converted object together with a machine-readable QiskitConversionReport. allow_lossy=True must be explicit and records every skipped operation; it is intended for inspection, not silent execution fallback. Importing flagquantum or flagquantum.ecosystem.qiskit does not import Qiskit.

Custom unitary matrices on one to three qubits are supported in both directions. The adapter reverses the local input and output bit axes because Qiskit treats the first qarg as the least-significant local bit while FlagQuantum treats the first instruction wire as the most-significant local bit. Shape, finite-value, unitarity, and width checks fail closed with machine-readable issue codes before a matrix crosses the adapter boundary.

The supported bidirectional gate set, parameter names, bit-index mapping, statevector endianness, loss policy, unsupported boundary, and certified Qiskit/Aer version lanes are pinned in contracts/qiskit-interop-contract.toml. Run the same deterministic semantic certification used by CI when qualifying a new environment:

from flagquantum.ecosystem.qiskit import run_qiskit_conformance

result = run_qiskit_conformance()
assert result.passed

The implementation is partitioned under flagquantum.runtime.dynamic into circuit, result, execution, routing, deployment, and dialects boundaries. Existing fq.experimental names and the flagquantum.runtime.dynamic import path remain compatible.

The stable surface also includes circuit and IR construction, backend compilation, runtime planning, and deployment helpers. The generated table is authoritative for exact names.

Stability boundaries

  • fq.experimental has no compatibility guarantee.
  • Compatibility imports are migration aids and are not implied stable.
  • A planner result describes intent and estimates; it is never runtime or benchmark evidence.
  • Operator/backend support comes from the executable lowering registry in the generated capability table.
  • Runtime evidence must satisfy the typed contracts.

Examples in stable documentation are executed by documentation contract tests. New public names must first be importable, snapshot-tested, and added to the stable API manifest.