fiqci.ems.fiqci_backend#

FiQCI backend wrapper for seamless error mitigation.

FiQCIBackend wraps an IQM backend and applies error mitigation (e.g. M3 readout error correction) to every circuit execution. It handles calibration, caching, and result post-processing automatically, so users get mitigated results through the standard Qiskit backend interface without additional code.

Classes

BatchedJob(jobs[, batch_ranges, post_process])

Lazy handle over one or more backend jobs submitted as ordered batches.

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

FiQCI backend wrapper that applies error mitigation automatically.

MitigatedJob(handle)

Lazy view over a BatchedJob whose result has error mitigation applied.

PartialBatch(index, circuit_range, status, ...)

Snapshot of a single batch within a multi-batch job.

Exceptions

BatchFailedError

Raised when one or more batches of a submitted job fail.

exception BatchFailedError#

Bases: RuntimeError

Raised when one or more batches of a submitted job fail.

The message identifies the failing batch by its index and the range of original circuit indices it covered, so callers can map the failure back to their input.

class PartialBatch(index: int, circuit_range: tuple[int, int], status: JobStatus, job_id: str, result: Result | None)#

Bases: NamedTuple

Snapshot of a single batch within a multi-batch job.

index#

Position of the batch in submission order.

Type:

int

circuit_range#

(start, end_exclusive) original circuit indices covered by the batch.

Type:

tuple[int, int]

status#

Current JobStatus of the batch job.

Type:

qiskit.providers.jobstatus.JobStatus

job_id#

Backend job id of the batch.

Type:

str

result#

The batch’s Result if it has completed, else None.

Type:

qiskit.result.result.Result | None

index: int#

Alias for field number 0

circuit_range: tuple[int, int]#

Alias for field number 1

status: JobStatus#

Alias for field number 2

job_id: str#

Alias for field number 3

result: Result | None#

Alias for field number 4

class FiQCIBackend(backend: IQMBackendBase, mitigation_level: int = 1, calibration_shots: int = 1000, calibration_file: str | None = None)#

Bases: object

FiQCI backend wrapper that applies error mitigation automatically.

Mitigation levels:
  • 0: No error mitigation (raw results)

  • 1: Readout error mitigation using M3 (default)

  • 2: Level 1 + dynamical decoupling (DD)

  • 3: Level 2 + Pauli twirling

Parameters:
  • backend – An IQMBackendBase instance to wrap.

  • mitigation_level – Error mitigation level (0-3). Default is 1.

  • calibration_shots – Number of shots for calibration circuits. Default is 1000.

  • calibration_file – Optional path to save/load M3 calibration data.

__init__(backend: IQMBackendBase, mitigation_level: int = 1, calibration_shots: int = 1000, calibration_file: str | None = None) None#

Initialize the FiQCI backend wrapper.

Parameters:
  • backend – An IQMBackendBase instance to wrap.

  • mitigation_level – Error mitigation level (0-3). Default is 1.

  • calibration_shots – Number of shots for calibration circuits. Default is 1000.

  • calibration_file – Optional path to save/load M3 calibration data.

Raises:

ValueError – If mitigation_level is not in range 0-3.

property backend: IQMBackendBase#

Get the underlying backend.

property mitigation_level: int#

Get the current mitigation level.

property raw_counts: list[dict[str, int]] | None#

Get the raw (unmitigated) counts from the most recent run.

The list is flat with one entry per circuit submitted to the backend, in submission order. With Pauli twirling enabled this is the per-twirl counts before any averaging, so the length is num_input_circuits * (num_twirls + 1) and entries for the same input circuit are contiguous.

Note

Because post-processing is now lazy, this is populated only after the run’s mitigated result() has been retrieved at least once. It returns None until then.

Returns:

List of raw count dictionaries, or None if no run’s result has been computed yet.

property mitigator_options: dict[str, Any]#

Get current mitigator settings.

Returns:

A dictionary of current mitigator settings and their values.

total_circuits_generated(num_base_circuits: int, detailed: bool = False) int | dict[str, int]#

Calculate total circuits generated for a given number of base circuits and observables.

init_pauli_twirl(enabled: bool, num_twirls: int = 10, gates_to_twirl: Iterable[Gate] | None = None) None#

Initialize Pauli twirling settings.

Parameters:
  • enabled – Whether Pauli twirling is enabled.

  • num_twirls – Number of twirled circuits to generate per input circuit.

  • gates_to_twirl – Optional list of gates to twirl, if None, all two-qubit basis gates will be twirled.

dd(enabled: bool = True, gate_sequences: list[tuple[int, str | list[tuple[float, float]], str]] | None = None) None#

Set dynamical decoupling settings for the backend.

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.

rem(enabled: bool = True, calibration_shots: int = 1000, calibration_file: str | None = None) None#

Set readout error mitigation settings for the backend.

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.

pauli_twirl(enabled: bool, num_twirls: int = 10, gates_to_twirl: list | None = None) None#

Set Pauli twirling settings for the backend.

Parameters:
  • enabled – Whether to enable Pauli twirling.

  • num_twirls – Number of twirled circuits to generate per input circuit (default: 10).

  • gates_to_twirl – Optional list of gates to twirl, if None, all two-qubit basis gates will be twirled.

run(circuits: QuantumCircuit | list[QuantumCircuit], shots: int = 1024, max_batch_size: int = 100, **kwargs: Any) MitigatedJob | BatchedJob#

Submit quantum circuits and return a lazy job handle immediately.

Circuits are submitted to the backend (in batches), and a handle is returned right away without waiting for results: the per-batch job_id()``s and ``status() are available immediately, and any configured error mitigation is deferred until the handle’s result() is first called.

Parameters:
  • circuits – Single quantum circuit or list of circuits to execute.

  • shots – Number of shots. Default is 1024.

  • max_batch_size – Maximum number of circuits per backend job. The (post-twirl) circuit list is flattened and split into batches of this size; the resulting jobs are wrapped so that the returned handle’s result() exposes a single combined Result indexed in submission order (default: 100).

  • **kwargs – Additional keyword arguments passed to backend.run().

Returns:

A BatchedJob handle (level 0) or a MitigatedJob view (level 1+). In both cases mitigation/combination is computed lazily on the first result() call.

Raises:

ValueError – If circuits is empty or invalid.

class BatchedJob(jobs: list[JobV1], batch_ranges: list[tuple[int, int]] | None = None, post_process: Callable[[Result], Result] | None = None)#

Bases: object

Lazy handle over one or more backend jobs submitted as ordered batches.

A larger circuit list is split into batches that are each submitted to the backend; this wrapper holds the resulting per-batch jobs. It is returned immediately from FiQCIBackend.run() (the submission loop does not wait for results), so callers can inspect job_ids() and poll status()/done() right away.

Calling result() blocks until every batch reaches a terminal state, then concatenates the batches’ results lists in submission order (so get_counts(idx) on the combined Result corresponds to the original circuit index) and runs the optional post_process callback that applies error mitigation. The combined/post-processed result is computed once and cached. If any batch failed, result() raises BatchFailedError identifying the batch and the original circuit indices it covered.

__init__(jobs: list[JobV1], batch_ranges: list[tuple[int, int]] | None = None, post_process: Callable[[Result], Result] | None = None) None#

Initialize the handle.

Parameters:
  • jobs – Per-batch jobs in submission order. Must be non-empty.

  • batch_ranges(start, end_exclusive) original circuit-index range for each batch, used for partial-result reporting and failure messages. If omitted, ranges are reported as best-effort placeholders.

  • post_process – Optional callback mapping the combined raw Result to the final (mitigated) Result. Runs once on the first result() call.

job_id() str#

Backend job id of the first batch (for single-job back-compat).

job_ids() list[str]#

Backend job ids of every batch, in submission order.

statuses() list[JobStatus]#

Current JobStatus of each batch, in submission order.

status() JobStatus#

Single aggregated status across all batches (see _aggregate_status()).

done() bool#

True once every batch has reached a terminal state (DONE/ERROR/CANCELLED).

all_succeeded() bool#

True once every batch has completed successfully (DONE).

partial_results() list[PartialBatch]#

Per-batch snapshot, exposing results for batches that have already completed.

Each entry carries the batch’s status and, for completed (DONE) batches, its Result. Batches that are still running or have failed report result=None. Results are exposed at batch granularity only; the globally-indexed combined Result is available from result() once all batches are terminal.

result(timeout: float | None = None) Result#

Return the combined, post-processed result, computed once and cached.

Blocks until every batch is terminal. Raises BatchFailedError if any batch failed, otherwise concatenates batch results in submission order and applies the post_process callback (if any).

Parameters:

timeout – Best-effort total budget (seconds) shared across all batches.

class MitigatedJob(handle: BatchedJob)#

Bases: object

Lazy view over a BatchedJob whose result has error mitigation applied.

The mitigation work is deferred to the wrapped handle’s post_process callback, so this wrapper simply exposes the handle’s polling API and a result() that returns the mitigated, combined Result. It exists as a distinct type so callers and tests can detect that mitigation was configured for the run.

__init__(handle: BatchedJob) None#

Initialize the wrapper.

Parameters:

handle – The submission handle carrying the batch jobs and the mitigation post_process callback.

result(timeout: float | None = None) Result#

Return the mitigated, combined result (computed once by the underlying handle).