Measurement and noise#

Observables and outputs#

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.

import flagquantum as fq

circuit = fq.Circuit(2).h(0).cx(0, 1)
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)

print(result.expectation("magnetization"))
print(result.expectation("correlation"))
print(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 and require a positive shot count. Use result.measurement(index_or_name) for a specific request and result.statevector() when a statevector is required. Backend-native attributes are not implicitly forwarded: result.native() is the explicit escape hatch.

Probabilities and expectations are exact by default; counts and samples require an explicit shot count. Local execution preserves its batch dimension, so counts returns one dictionary per batch item.

Noise models#

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

import flagquantum as fq
import flagquantum.noise as fqn

circuit = fq.Circuit(2).h(0).cx(0, 1)
noise = (
    fqn.NoiseModel()
    .add("h", fqn.thermal_relaxation_channel(t1=50_000, t2=70_000, duration=35))
    .add("cx", fqn.depolarizing_channel(0.01))
    .add_readout(0, fqn.ReadoutError(((0.98, 0.02), (0.07, 0.93))))
)

result = fq.run(
    circuit,
    noise_model=noise,
    options=fq.ExecutionOptions(mode="density_matrix"),
    outputs=fq.expectation(fq.Z(0) + fq.Z(1)),
)
print(result.expectation())

The versioned model payload and its SHA-256 identity are verified as part of the plan, and the identity is available as noise.identity. Exact density-matrix evolution is the small-system correctness oracle; batched statevector trajectories and MPS quantum trajectories report sampling statistics, and the MPS path additionally reports truncation data. When the exact density matrix exceeds an explicit memory budget, the caller must opt in to approximation — otherwise planning fails rather than silently changing semantics.

Hardware Pauli measurement planning#

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))

Each package records its group index, term indices, basis, routing evidence and sealed deployment identity.

Dynamic circuits#

The candidate-stable builder is isolated from experimental execution:

import flagquantum as fq
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()

Local dynamic noise is limited to one-wire bit-flip channels after matching executed gates plus independent readout confusion; other channels fail closed. Backend compatibility can be checked before running with the read-only preflight fq.experimental.dynamic.assess_dynamic_backend(circuit, backend).