fiqci.ems.primitives.fiqci_estimator#
A class that runs quantum circuits and calculates expectation values of observables with error mitigation techniques.
Classes
|
FiQCIEstimator wraps a backend with built-in error mitigation (readout error mitigation via M3, zero-noise extrapolation) and computes expectation values of observables directly from circuits, eliminating the need for manual post-processing of measurement counts. |
|
Lazy wrapper around the backend job that produced an estimator's results. |
- class FiQCIEstimator(backend, mitigation_level=1, calibration_shots=1000, calibration_file=None)#
Bases:
objectFiQCIEstimator wraps a backend with built-in error mitigation (readout error mitigation via M3, zero-noise extrapolation) and computes expectation values of observables directly from circuits, eliminating the need for manual post-processing of measurement counts.
- Mitigation levels:
0: No error mitigation (raw results)
1: Readout error mitigation using M3 (default)
2: Level 1 + dynamical decoupling (DD)
3: Level 2 + zero-noise extrapolation (ZNE) with local folding and exponential extrapolation
- Parameters:
backend – An IQMBackendBase instance to wrap.
mitigation_level – Level of error mitigation to apply (default: 1).
calibration_shots – Number of shots to use for calibration circuits (default: 1000).
calibration_file – Optional calibration file to use for readout error mitigation.
- __init__(backend, mitigation_level=1, calibration_shots=1000, calibration_file=None)#
- property mitigator_options: dict[str, Any]#
Get current mitigator settings.
The returned dict is a copy, so mutating it does not change the estimator’s configuration; use
zne()/rem()/dd()/pauli_twirl(), which validate their input.
- total_circuits_generated(num_base_circuits: int, observables, detailed: bool = False) int | dict[str, Any]#
Calculate total circuits generated for a given number of base circuits and observables.
The number of measurement-basis circuits depends on the observable, and the number of ZNE circuits can depend on the circuit, so the total is summed per circuit/observable pair rather than taken from a single multiplier:
pauli_twirl_multiplier * sum(measurement_groups_i * scale_factors_i).- Parameters:
num_base_circuits – Number of circuits to be submitted.
observables – As for
run(). A single observable used for every circuit, or one entry per circuit, where each entry is an observable or a list of observables measured on that circuit. With one base circuit, a list holds that circuit’s observables.detailed – Print the breakdown and return it as a dict instead of just the total.
- Returns:
The total circuit count, or a dict with the breakdown when
detailedis set. Entries that differ between circuits (measurement groups, ZNE multiplier) are reported as a list.- Raises:
ValueError – If a list of observables or per-circuit scale factors does not have one entry per base circuit.
- run(circuits: QuantumCircuit | list[QuantumCircuit], observables, shots: int = 2048, max_batch_size: int = 100, **options) FiQCIEstimatorJob#
Execute the given circuits on the backend and calculate expectation values for the provided observables.
- Parameters:
circuits – A QuantumCircuit or list of QuantumCircuits to execute.
observables – The observables to estimate, as in Qiskit’s Estimator. Each observable is one Hermitian operator (
SparsePauliOp,Pauli,SparseObservable, label string or{label: coeff}dict) whose expectation value issum(c * <P>)over its terms, and it must act on as many qubits as its circuit. A single observable is used for every circuit. With a list of circuits, give one entry per circuit, each an observable or a list of them. With a single circuit, a list holds that circuit’s observables.shots – Number of shots to execute each circuit (default: 2048).
max_batch_size – Maximum number of circuits to send in a single backend job. All measurement-basis subcircuits (across all circuit/observable pairs and ZNE scale factors) are flattened and split into batches of this size (default: 100).
**options – Additional options to pass to the backend’s run method.
- Returns:
A FiQCIEstimatorJob whose expectation values are one list per circuit, with one value per observable.
- Raises:
ValueError – If an observable’s qubit count does not match its circuit, an observable has complex coefficients, or no observable has a non-identity term.
- rem(enabled: bool, calibration_shots: int = 1000, calibration_file: str | None = None) None#
Set readout error mitigation settings for the estimator. This will configure the underlying backend’s readout error mitigation accordingly.
- Parameters:
enabled – Whether to enable readout error mitigation.
calibration_shots – Number of shots to use for calibration circuits (default: 1000).
calibration_file – Optional calibration file to use for readout error mitigation.
- dd(enabled: bool, gate_sequences: list[tuple[int, str | list[tuple[float, float]], str]] | None = None) None#
Set dynamical decoupling settings for the estimator. This will configure the underlying backend’s dynamical decoupling accordingly.
- Parameters:
enabled – Whether to enable dynamical decoupling.
gate_sequences – List of (threshold_length, sequence, strategy) tuples defining DD behavior. See build_dd_options for details on each field.
- zne(enabled: bool, fold_gates: list | None = None, scale_factors: list[float] | list[list[float]] = [1, 3, 5], folding_method: str = 'local', extrapolation_method: str | Callable[[...], list[float] | tuple[list[float], list[float]]] = 'exponential', extrapolation_degree: int | None = None, seed: int | None = None)#
Configure zero-noise extrapolation settings.
Scale factors may be any real numbers >= 1. Non-odd-integer values (even integers, fractions) are approximated by partially folding a randomly-sampled subset of gates;
seedmakes that sampling reproducible. Extrapolation uses the achieved scale factors as the x-axis.scale_factorsmay be either a single flat list applied to every submitted circuit, or a list of lists (one per submitted circuit) so each circuit uses its own scale factors. The number of lists must then match the number of circuit/observable pairs passed torun().extrapolation_methodmay be one of the built-in strings ("exponential","richardson","polynomial","linear") or a user-defined callable. The callable is invoked once per circuit asfn(expectation_values, scale_factors), whereexpectation_valuesis a list (one entry per scale factor) of per-observable expectation-value lists andscale_factorsis the list of achieved scale factors; it must return a list of floats (the zero-noise estimate per observable).extrapolation_degreeis ignored for callables. Every extrapolator sees the observables without their identity terms, whose coefficients are added to the result afterwards.A callable can also report the uncertainty of its estimate the way the built-in extrapolators do. If it accepts a
sigmaskeyword argument, it is additionally called withsigmas=<per-scale shot standard errors>(same shape asexpectation_values), and it may then return a(values, standard_errors)pair instead of just the values. Those standard errors are surfaced as the"zne_extrapolation_error"/"total"entries ofFiQCIEstimatorJob.standard_errors(); callables that return only values leave bothNone.On devices with computational resonators, local folding leaves MOVE gates alone unless
fold_gatesnames"move"explicitly: folding MOVE adds state transfers without scaling how long the state sits in the resonator, so its noise would not scale by the requested factor. Global folding inverts the whole circuit and therefore always folds MOVE too.
- pauli_twirl(enabled: bool, num_twirls: int = 10, gates_to_twirl: list | None = None, seed: int | None = None) None#
Configure Pauli twirling settings for the estimator.
seedmakes the random twirl selection reproducible for a run.
- class FiQCIEstimatorJob(job, compute_fn: Callable[[], tuple[list, list, list]], observables, requested_scale_factors: list[list[float]] | None = None, achieved_scale_factors: list[list[float]] | None = None, zne_options: dict[str, Any] | None = None)#
Bases:
objectLazy wrapper around the backend job that produced an estimator’s results.
The estimator flattens all per-pair measurement-basis circuits into one backend call, so there is exactly one underlying job (which may itself batch internally. See
BatchedJob). This class is returned immediately fromFiQCIEstimator.run(); the expectation-value computation is deferred untilexpectation_values()/raw_expectation_values()is first called (it fetches the underlying results and computes once, then caches). Polling the underlying job (status/done/job_ids) works before the values are computed.- __init__(job, compute_fn: Callable[[], tuple[list, list, list]], observables, requested_scale_factors: list[list[float]] | None = None, achieved_scale_factors: list[list[float]] | None = None, zne_options: dict[str, Any] | None = None) None#
Initialize the estimator job.
- Parameters:
job – The underlying job that produced the results (BatchedJob or MitigatedJob).
compute_fn – Deferred callable returning
(expectation_values, raw_expectation_values, standard_errors).observables – The coerced observables of each circuit.
requested_scale_factors – ZNE scale factors requested for each circuit/observable pair (empty when ZNE is disabled).
achieved_scale_factors – ZNE scale factors actually realised by folding for each pair. The x-axis used for extrapolation (empty when ZNE is disabled).
zne_options – Frozen snapshot of the ZNE configuration used at submission (folding/extrapolation settings), surfaced via
mitigator_options.Nonewhen unknown.
- result()#
Get the underlying combined result for this estimator run (blocks until ready).
- job()#
Get the underlying job for this estimator run.
- raw_expectation_values(index: int | None = None) list[float]#
Get the raw (unmitigated) expectation values before extrapolation (computes lazily).
- expectation_values(index: int | None = None) list[float]#
Get the calculated expectation values (computes lazily on first access).
With readout error mitigation enabled these come from M3’s quasi-probabilities, which are an unbiased but not necessarily physical estimate: a value may fall slightly outside
[-1, 1]when the calibration over-subtracts. Clamping it would reintroduce the boundary bias, so the estimate is reported as is; a value far outside the range means the readout calibration is too noisy (raisecalibration_shots).
- observables(index: int | None = None) list[list[SparsePauliOp]] | list[SparsePauliOp]#
Observables the expectation values were calculated for, one list per circuit, as
SparsePauliOp.
- standard_errors(index: int | None = None) list[dict] | dict#
Standard errors of the expectation values (computes lazily on first access).
Mirrors the shape of
expectation_values(): one entry per circuit, each a dict of per-observable standard errors with keys:"shot_error": statistical SE of the raw measurement,sum(|c| * sqrt((1 - ⟨P⟩²) / N))over the observable’s terms, as in Qiskit’sBackendEstimatorV2, taken from the pre-mitigation counts. When readout error mitigation is on, each term’s error is inflated bysqrt(M3 mitigation overhead), so a mitigated value carries a larger error bar than the unmitigated one it was computed from — mitigation trades bias for variance. When ZNE is enabled the value is taken at the unfolded (scale 1) point. With Pauli twirling,Ncounts every twirled variant’s shots, not the averaged total."zne_extrapolation_error": SE of the extrapolated value, the per-scale shot errors propagated through the (linear) extrapolator;Nonewhen ZNE is disabled, or when a user-defined extrapolation callable reports no standard errors."total": SE of the valueexpectation_values()actually returns —"shot_error"when ZNE is off,"zne_extrapolation_error"when ZNE is on. Not a quadrature sum, since the extrapolation error already incorporates the shot noise.
- Parameters:
index – If given, return the dict for that pair; otherwise the list of all pairs.
- requested_scale_factors(index: int | None = None) list[list[float]] | list[float]#
ZNE scale factors requested for this run, as one list per circuit/observable pair.
Returns all pairs’ lists (a list of lists) when
indexis None, or a single pair’s list whenindexis given. Empty when ZNE was disabled for the run. Seeachieved_scale_factors()for the values folding could actually realise.
- achieved_scale_factors(index: int | None = None) list[list[float]] | list[float]#
ZNE scale factors actually realised by folding, as one list per circuit/observable pair.
Folding can only approximate the requested scale factors, so these (the x-axis used for extrapolation) may differ from
requested_scale_factors(). Returns all pairs’ lists (a list of lists) whenindexis None, or a single pair’s list whenindexis given. Empty when ZNE was disabled for the run.
- property mitigator_options: dict[str, Any]#
Mitigation settings frozen at submission time for this estimator run.
Merges the ZNE configuration with the underlying backend job’s snapshot (
mitigation_level,rem,dd,pauli_twirl), so the returned dict describes the full mitigation stack the run actually used. UnlikeFiQCIEstimator.mitigator_options(which is live and mutable), this never changes after submission. Per-pair scale factors are available viarequested_scale_factors()/achieved_scale_factors().