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
| Task | Primary interface | Result |
|---|---|---|
| Build a program | fq.Circuit | Circuit backed by FlagQuantum IR |
| Optimize a program | flagquantum.compiler.optimize | fq.CircuitIR |
| Compile for a selected tool and target | fq.compile | fq.CircuitIR |
| Inspect execution | fq.plan, Circuit.runtime_plan | Explainable runtime plan |
| Execute locally or remotely | fq.run | fq.ExecutionResult |
| Define a trainable quantum layer | fq.Module | PyTorch module |
| Train | fq.train | fq.TrainingResult |
| Package for a target | flagquantum.deployment.create_deployment_package | Sealed 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.experimentalhas 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.