Source code for uniqc.algorithms.workflows.readout_em_workflow
"""High-level readout EM workflow.
Chip-agnostic: runs readout calibration and returns a ready-to-use ReadoutEM instance.
For WK180-specific usage, see ``examples/wk180/readout_em.py``.
"""
from __future__ import annotations
from typing import Any
from uniqc._error_hints import format_enriched_message
__all__ = [
"run_readout_em_workflow",
"apply_readout_em",
]
def _get_adapter(backend: str, **kwargs) -> Any:
"""Get a QuantumAdapter for the given backend name.
Args:
backend: Backend identifier, e.g. "dummy", "originq:PQPUMESH8".
For OriginQ backends the chip name (after "originq:") is extracted
and passed as ``backend_name`` to ``OriginQAdapter``.
"""
from uniqc.backend_adapter.task.adapters import (
DummyAdapter,
OriginQAdapter,
QuafuAdapter,
)
if backend == "dummy" or backend.startswith("dummy:"):
from uniqc.backend_adapter.dummy_backend import dummy_adapter_kwargs
return DummyAdapter(**dummy_adapter_kwargs(backend, **kwargs))
elif backend.startswith("origin"):
# Extract chip name: "originq:PQPUMESH8" → "PQPUMESH8"
chip = backend.split(":", 1)[1] if ":" in backend else backend
return OriginQAdapter(backend_name=chip, **kwargs)
elif backend.startswith("quafu"):
return QuafuAdapter(**kwargs)
else:
return DummyAdapter(**kwargs)
[docs]
def run_readout_em_workflow(
backend: str = "dummy:local:simulator",
qubits: list[int] | None = None,
pairs: list[tuple[int, int]] | None = None,
shots: int = 1000,
max_age_hours: float = 24.0,
chip_characterization: Any = None,
) -> Any:
"""Run readout calibration and return a ready-to-use ReadoutEM instance.
This function:
1. Creates an adapter for the given backend
2. Runs 1q and/or 2q readout calibration
3. Returns a ``ReadoutEM`` instance ready for applying mitigation
Args:
backend: Backend name (e.g. "dummy", "originq:wuyuan:wk180").
qubits: List of qubit indices for 1q calibration.
pairs: List of (u, v) qubit pairs for 2q calibration.
shots: Number of shots per calibration circuit.
max_age_hours: Maximum acceptable age of existing calibration data.
chip_characterization: Optional ChipCharacterization.
Returns:
A ``ReadoutEM`` instance. Calibration results are saved to
``~/.uniqc/calibration_cache/`` and loaded automatically.
"""
from uniqc.calibration.readout import ReadoutCalibrator
from uniqc.qem import ReadoutEM
adapter_kwargs: dict[str, Any] = {}
if chip_characterization is not None:
adapter_kwargs["chip_characterization"] = chip_characterization
adapter = _get_adapter(backend, **adapter_kwargs)
# Run calibration
calibrator = ReadoutCalibrator(adapter=adapter, shots=shots)
if qubits:
calibrator.calibrate_qubits(qubits)
if pairs:
calibrator.calibrate_pairs(pairs)
# Return a ReadoutEM instance for applying mitigation
return ReadoutEM(
adapter=adapter,
max_age_hours=max_age_hours,
shots=shots,
)
[docs]
def apply_readout_em(
result: Any,
readout_em: Any,
measured_qubits: list[int],
) -> dict[int, float]:
"""Apply readout EM to a UnifiedResult's counts.
Args:
result: A ``UnifiedResult`` or result dict with a ``counts`` field.
readout_em: A ``ReadoutEM`` instance.
measured_qubits: List of qubit indices that were measured.
Returns:
Dict mapping outcome → corrected probability.
"""
# Extract counts from result
if hasattr(result, "counts"):
counts = result.counts
elif isinstance(result, dict):
counts = result.get("counts", result.get("result", {}).get("counts", {}))
else:
raise TypeError(format_enriched_message(f"Unsupported result type: {type(result)}", "circuit_validation"))
if isinstance(counts, dict) and counts and isinstance(next(iter(counts)), str):
counts = {int(k): v for k, v in counts.items()}
corrected_counts = readout_em.mitigate_counts(counts, measured_qubits)
total = sum(corrected_counts.values())
if total > 0:
return {k: v / total for k, v in corrected_counts.items()}
return corrected_counts