Skip to the content.

QESEM integration

qpubench models the Qedma QESEM quantum error suppression and mitigation service in src/qpubench/schemas/mirrors/qedma_qesem.py.

QESEM is available through two interfaces:

QESEM wraps any IBM gate-based backend with:

  1. Device characterization — noise-learning protocol builds a tailored noise model
  2. Noise-aware transpilation — maps circuit to physical qubits minimising QPU time
  3. Quasi-probabilistic Error Tuning (QET) — runs circuits at multiple noise scale factors
  4. Classical post-processing — extrapolates to a zero-noise unbiased estimate with error bar

Error mitigation strategy in qpubench: ErrorMitigationStrategy.QESEM


Quick start

from qpubench.schemas.backend import BackendSpec
from qpubench.schemas.execution import ExecutionOptions
from qpubench.schemas.primitives import ComputingModel, ErrorMitigationStrategy, QubitModality
from qpubench.schemas.mirrors.qedma_qesem import (
    QESEMJobSpec, QESEMObservableSpec, QESEMCircuitOptions,
    QESEMJobOptions, QESEMExecutionMode,
)

backend = BackendSpec.qesem("ibm_fez", api_token_ref="QEDMA_TOKEN")

options = ExecutionOptions(
    shots=10_000,
    error_mitigation=ErrorMitigationStrategy.QESEM,
    qesem_circuit_options=QESEMCircuitOptions(parallel_execution=True),
    qesem_job_options=QESEMJobOptions(execution_mode=QESEMExecutionMode.BATCH),
)

Observables

QESEM uses Qedma’s Pauli string dict format: comma-separated qubit-indexed Pauli operators mapped to real coefficients.

from qpubench.schemas.mirrors.qedma_qesem import QESEMObservableSpec

# Average magnetization over 5 qubits
avg_mag = QESEMObservableSpec(
    pauli_terms={"Z0": 0.2, "Z1": 0.2, "Z2": 0.2, "Z3": 0.2, "Z4": 0.2},
    description="average magnetization",
)

# Two-body correlation
zz_corr = QESEMObservableSpec(
    pauli_terms={"Z0,Z1": 1.0},
    description="ZZ correlation (0,1)",
)

# Hamiltonian fragment with non-commuting Paulis
ham_obs = QESEMObservableSpec(
    pauli_terms={"Z0,Z3": 1.0, "X1,Y2": 0.5, "Z2": -0.3},
    description="Hamiltonian observable",
)

Job specification

from qpubench.schemas.mirrors.qedma_qesem import (
    QESEMJobSpec, QESEMObservableSpec, QESEMCircuitOptions,
    QESEMTranspilationLevel, QESEMPrecisionMode,
)

spec = QESEMJobSpec(
    circuit_qasm='OPENQASM 2.0;\nqreg q[5];\nh q[0];\ncx q[0],q[1];\ncx q[1],q[2];',
    num_qubits=5,
    observables=[avg_mag, zz_corr],
    precision=0.05,                                 # target 1-σ = 0.05 for all observables
    precision_mode=QESEMPrecisionMode.CIRCUIT,       # applied per circuit instance
    backend_name="ibm_fez",
    circuit_options=QESEMCircuitOptions(
        transpilation_level=QESEMTranspilationLevel.STANDARD,
        parallel_execution=False,
        error_suppression_only=False,
    ),
    description="GHZ circuit magnetization",
)

Parameterized circuits

# Two circuit instances with different parameter bindings
spec_param = QESEMJobSpec(
    circuit_qasm='OPENQASM 2.0;\nqreg q[4];\n...',  # circuit with rx(param0), rx(param1)
    num_qubits=4,
    observables=[avg_mag],
    precision=0.1,
    backend_name="ibm_fez",
    parameterized_values={
        "param0": [0.5, 0.0],
        "param1": [0.1, 0.6],
    },
    precision_mode=QESEMPrecisionMode.CIRCUIT,
)

QPU time estimation

spec_estimate = QESEMJobSpec(
    circuit_qasm='...',
    observables=[avg_mag],
    precision=0.05,
    backend_name="ibm_fez",
    empirical_time_estimation=True,   # pilot run to estimate cost before full execution
)

Quasi-probabilistic Error Tuning (QET)

QET runs the circuit at multiple noise scale factors and extrapolates to the zero-noise limit.

from qpubench.schemas.mirrors.qedma_qesem import QESEMPrecisionPerFactor

spec_qet = QESEMJobSpec(
    circuit_qasm='...',
    observables=[avg_mag],
    backend_name="ibm_fez",
    # Different precision targets per noise scale
    precision_per_factor=QESEMPrecisionPerFactor(
        scale_precision_map={
            "0.0": 0.10,   # zero-noise extrapolated result
            "1.0": 0.15,   # physical device noise
            "2.0": 0.20,   # 2× noise amplification
        }
    ),
)

Results

Mitigated expectation values

from qpubench.schemas.mirrors.qedma_qesem import (
    QESEMJobRecord, QESEMCircuitResult, QESEMObservableResult,
    QESEMExpectationValue, QESEMScaleExpectationValue, QESEMNoiseScalingResult,
    QESEMHeuristicResult,
)

# Noise-scaling results from QET
ns = QESEMNoiseScalingResult(
    scaling_method="QESEM",
    results_per_scale=[
        QESEMScaleExpectationValue(value=-0.81, error_bar=0.05, scale=0.0),  # zero-noise
        QESEMScaleExpectationValue(value=-0.56, error_bar=0.04, scale=1.0),  # physical
        QESEMScaleExpectationValue(value=-0.32, error_bar=0.04, scale=2.0),  # amplified
    ]
)
print(f"Scale factors used: {ns.scale_factors}")         # [0.0, 1.0, 2.0]
print(f"Zero-noise result: {ns.zero_noise_result.value}")  # -0.81

# Heuristic extrapolation (QESEM's final best estimate)
heuristic = QESEMHeuristicResult(
    value=-0.80, error_bar=0.05,
    extrapolation="linear",
    scale_factors=[0.0, 1.0, 2.0],
)

# Full observable result
obs_result = QESEMObservableResult(
    unmitigated=QESEMExpectationValue(value=-0.42, error_bar=0.08),  # raw noisy
    noise_scaling=ns,
    qesem_heuristic=[heuristic],
)
print(f"Best mitigated: {obs_result.mitigated.value}")  # -0.80 (uses heuristic)

Circuit result — accessing mitigated values

from qpubench.schemas.mirrors.qedma_qesem import QESEMCircuitResult, QESEMCircuitObservableResult

circuit_result = QESEMCircuitResult(
    parameter_index=0,
    observable_results=[
        QESEMCircuitObservableResult(observable=avg_mag, result=obs_result),
        QESEMCircuitObservableResult(observable=zz_corr, result=obs_result_2),
    ],
)

print(circuit_result.mitigated_evs)   # [-0.80, -0.31]
print(circuit_result.mitigated_stds)  # [0.05, 0.04]
print(circuit_result.noisy_evs)       # [-0.42, -0.22]

Execution details

from qpubench.schemas.mirrors.qedma_qesem import (
    QESEMExecutionDetails, QESEMTranspiledCircuit,
)

tc = QESEMTranspiledCircuit(
    circuit_qasm="OPENQASM 2.0; ...",
    qubit_maps=[{"0": 12, "1": 13, "2": 14, "3": 15, "4": 16}],
    num_measurement_bases=6,        # distinct Pauli bases needed for observables
)

details = QESEMExecutionDetails(
    total_shots=120_000,            # calibration + characterization + mitigation
    mitigation_shots=30_000,        # purely mitigation allocation
    gate_fidelities={
        "CNOT":  0.9901,            # from QESEM's noise-learning characterization
        "ID1Q":  0.9989,
    },
    transpiled_circuits=[tc],
)

Device characterization

from qpubench.schemas.mirrors.qedma_qesem import (
    QESEMCharacterizationResult, QESEMGateInfidelity,
)

char = QESEMCharacterizationResult(
    qpu_name="ibm_fez",
    measurement_errors={
        12: 0.012,   # qubit 12: 1.2% readout error
        13: 0.009,
        14: 0.015,
        15: 0.011,
        16: 0.008,
    },
    gate_infidelities=[
        QESEMGateInfidelity(gate_name="CNOT", qubits=(12, 13), infidelity=0.0099),
        QESEMGateInfidelity(gate_name="CNOT", qubits=(13, 14), infidelity=0.0087),
        QESEMGateInfidelity(gate_name="CNOT", qubits=(14, 15), infidelity=0.0112),
    ],
    qubit_map={0: 12, 1: 13, 2: 14, 3: 15, 4: 16},
)

Full job record

from qpubench.schemas.mirrors.qedma_qesem import QESEMJobRecord, QESEMJobStatus, QESEMExecutionMode
from qpubench.schemas.result import QuantumResult
from qpubench.schemas.primitives import ComputingModel, QubitModality

record = QESEMJobRecord(
    job_id="qesem-fez-abc123",
    status=QESEMJobStatus.SUCCEEDED,
    qpu_name="ibm_fez",
    spec=spec,
    execution_mode=QESEMExecutionMode.BATCH,
    analytical_qpu_time_s=187.3,
    total_execution_time_s=241.8,
    circuit_results=[circuit_result],
    execution_details=details,
    characterization=char,
)

result = QuantumResult(
    computing_model=ComputingModel.GATE_BASED,
    qubit_modality=QubitModality.SUPERCONDUCTING,
    vendor_results={"qesem_result": record},
)

IBM Qiskit Function interface

When using the IBM Qiskit Functions catalog, set via_qiskit_function=True and use Qiskit’s PUB (Primitive Unified Bloc) format in your adapter:

# Backend spec for Qiskit Function path
backend = BackendSpec.qesem("ibm_fez", via_qiskit_function=True)

# Map QESEMJobSpec back to Qiskit Function run() call:
#   qesem_function.run(
#       pubs=[(circuit, [avg_mag_sparse_pauli_op, zz_sparse_pauli_op])],
#       backend_name="ibm_fez",
#   )
#
# Map results back to QESEMJobRecord:
#   results[0].data.evs          → mitigated_evs
#   results[0].data.stds         → mitigated_stds
#   results[0].metadata["noisy_results"].evs  → noisy_evs
#   results[0].metadata["total_qpu_time"]     → total_execution_time_s
#   results[0].metadata["gate_fidelities"]    → execution_details.gate_fidelities
#   results[0].metadata["total_shots"]        → execution_details.total_shots
#   results[0].metadata["mitigation_shots"]   → execution_details.mitigation_shots

Backend

from qpubench.schemas.backend import BackendSpec

# Native qedma-api client
BackendSpec.qesem("ibm_fez",    api_token_ref="QEDMA_TOKEN")
BackendSpec.qesem("ibm_torino", api_token_ref="QEDMA_TOKEN")

# Via IBM Qiskit Functions catalog
BackendSpec.qesem("ibm_fez", via_qiskit_function=True)