FiQCISampler#
FiQCISampler is a sampling interface that wraps an IQM backend and applies error mitigation to measurement results. It executes quantum circuits and returns mitigated counts.
Basic Configuration#
Initialize the sampler with an IQM backend, mitigation level, and optional parameters:
from fiqci.ems import FiQCISampler
# Initialize sampler with mitigation level 1
sampler = FiQCISampler(backend, mitigation_level=1, calibration_shots=2000, calibration_file="cals.json")
For more details see the API reference documentation for FiQCISampler.
Mitigation Levels#
Mitigation levels apply predefined sets of error mitigation techniques.
Level |
Mitigation Applied |
Technique |
|---|---|---|
0 |
None |
Raw results |
1 |
Readout Error Mitigation |
M3 (matrix-free measurement mitigation) |
2 |
Level 1 + Dynamical Decoupling |
Dynamical Decoupling standard sequence (see below) |
3 |
Level 2 + Pauli Twirling |
Pauli Twirling with 10 twirls on all two-qubit gates |
Mitigation Options#
Mitigators can also be configured manually using the provided methods.
REM (Readout Error Mitigation)#
Readout error mitigation uses M3 (matrix-free measurement mitigation) to correct measurement errors. It is enabled by default at mitigation level 1.
Configure REM using the rem() method:
sampler.rem(enabled=True, calibration_shots=2000, calibration_file="cals.json")
Parameter |
Default |
Description |
|---|---|---|
|
|
Enable or disable readout error mitigation |
|
|
Number of shots used for M3 calibration circuits |
|
|
Path to save/load calibration data (JSON). Reuses cached calibrations when available. |
Dynamical Decoupling (DD)#
Dynamical decoupling inserts sequences of gates to mitigate decoherence. It is enabled at mitigation level 2.
Configure DD using the dd() method:
sampler.dd(enabled=True, gate_sequences=None) # None uses a standard set of sequences
The standard sequence is:
[
(9, 'XYXYYXYX', 'asap'),
(5, 'YXYX', 'asap'),
(2, 'XX', 'center'),
]
Parameter |
Default |
Description |
|---|---|---|
|
|
Enable or disable DD |
|
|
|
Pauli Twirling#
Pauli twirling sandwiches two-qubit gates with random single-qubit Pauli gates to mitigate coherent errors. It is enabled at mitigation level 3.
Configure Pauli Twirling using the pauli_twirl() method:
sampler.pauli_twirl(enabled=True, num_twirls=10, gates_to_twirl=None) # None twirls all two-qubit gates
Parameter |
Default |
Description |
|---|---|---|
|
|
Enable or disable Pauli twirling |
|
|
Number of twirled variant circuits to generate for each original circuit |
|
|
List of two-qubit gates to twirl. If |
Inspecting Options#
Use the mitigator_options property to view currently applied mitigation settings:
sampler.mitigator_options
Counting Circuits#
Use total_circuits_generated() to see how many circuits will actually be executed under the current mitigation settings (accounts for Pauli twirls):
sampler.total_circuits_generated(num_base_circuits=len(circuits), detailed=True)
Set detailed=True to print the breakdown and return a dictionary with each multiplier; otherwise the method returns the total as an int. The count does not include REM calibration circuits.
Running Circuits#
run() accepts a max_batch_size parameter (default 100) that controls how many circuits are sent to the backend in a single job. Larger circuit lists are split into batches automatically; results are re-combined so indexing matches the order of the input circuits.
job = sampler.run(circuits, shots=1024, max_batch_size=100)
run returns immediately with a lazy job handle without waiting for results: a BatchedJob at level 0, or a MitigatedJob at level 1+. The per-batch job_id() values and an aggregated status() are available right away; error mitigation and result combination are computed the first time you call job.result() (which then exposes a single combined Result indexed in input order, and caches it).
The handle also lets you inspect a multi-batch run before it finishes:
job.job_ids() # backend job id of every batch (None for any unsubmitted batch)
job.statuses() # per-batch JobStatus, in submission order
job.status() # single aggregated status across all batches
job.done() # True once every batch has reached a terminal state
job.partial_results() # per-batch results for batches that have already completed
If a batch fails, job.result() raises BatchFailedError identifying the failing batch and the input circuit indices it covered, instead of an opaque error during combination.
Note
Submission is not atomic. If the backend rejects a circuit partway through (e.g. an un-transpiled circuit), run does not raise: it logs a warning, stops submitting further batches, and still returns a handle covering every intended batch. Submitted batches keep their job ids and status, the rejected batch reports ERROR, and the batches skipped afterwards report CANCELLED (with job_id() None). Inspect the outcome with statuses()/status()/partial_results(); result() then raises BatchFailedError because a complete combined result cannot be formed.