fiqci.ems.primitives.fiqci_estimator#

A class that runs quantum circuits and calculates expectation values of observables with error mitigation techniques.

Classes

FiQCIEstimator(backend[, mitigation_level, ...])

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.

FiQCIEstimatorJob(job, compute_fn, observables)

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: object

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.

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: SparsePauliOp | list[SparsePauliOp], 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 – A single SparsePauliOp used for every circuit, or one per circuit.

  • 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 detailed is 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: SparsePauliOp | list[SparsePauliOp], 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 – A SparsePauliOp or list of SparsePauliOps representing the observables for which to calculate expectation values.

  • 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 containing the jobs and calculated expectation values.

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; seed makes that sampling reproducible. Extrapolation uses the achieved scale factors as the x-axis.

scale_factors may 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 to run().

extrapolation_method may be one of the built-in strings ("exponential", "richardson", "polynomial", "linear") or a user-defined callable. The callable is invoked once per circuit/observable pair as fn(expectation_values, scale_factors), where expectation_values is a list (one entry per scale factor) of per-observable expectation-value lists and scale_factors is the list of achieved scale factors; it must return a list of floats (the zero-noise estimate per observable). extrapolation_degree is ignored for callables.

A callable can also report the uncertainty of its estimate the way the built-in extrapolators do. If it accepts a sigmas keyword argument, it is additionally called with sigmas=<per-scale shot standard errors> (same shape as expectation_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 of FiQCIEstimatorJob.standard_errors(); callables that return only values leave both None.

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.

seed makes 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: object

Lazy 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 from FiQCIEstimator.run(); the expectation-value computation is deferred until expectation_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 – Observable(s) for which expectation values were calculated.

  • 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. None when 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).

observables(index: int | None = None) SparsePauliOp#

Get the observables for which expectation values were calculated.

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/observable pair, each a dict of per-Pauli-term standard errors with keys:

  • "shot_error": statistical SE of the raw measurement, sqrt((1 - ⟨P⟩²) / N) per term. When ZNE is enabled this is taken at the unfolded (scale 1) point. With Pauli twirling, N counts 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; None when ZNE is disabled, or when a user-defined extrapolation callable reports no standard errors.

  • "total": SE of the value expectation_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 index is None, or a single pair’s list when index is given. Empty when ZNE was disabled for the run. See achieved_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) when index is None, or a single pair’s list when index is 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. Unlike FiQCIEstimator.mitigator_options (which is live and mutable), this never changes after submission. Per-pair scale factors are available via requested_scale_factors() / achieved_scale_factors().