-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathreadout_mitigation.py
More file actions
133 lines (110 loc) · 4.67 KB
/
readout_mitigation.py
File metadata and controls
133 lines (110 loc) · 4.67 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
"""
Readout error mitigation demo (refactored to new API).
This example builds a small Bell-like circuit, constructs synthetic measured counts
with readout errors, and applies readout mitigation to recover the ideal counts.
"""
from __future__ import annotations
import tyxonq as tq
from tyxonq.postprocessing.readout import ReadoutMit
from typing import Any, Dict
import numpy as np
def build_bell_circuit(nqubit: int = 2) -> tq.Circuit:
c = tq.Circuit(nqubit)
c.h(0).cx(0, 1)
c.measure_z(0).measure_z(1)
return c
def ideal_counts_for_bell(shots: int) -> dict[str, int]:
# |Φ+> = (|00>+|11>)/sqrt(2) measured in Z basis → 00 and 11 with 50%-50%
half = shots // 2
return {"00": half, "11": shots - half}
# Removed old local channel; now provided by postprocessing.readout.apply_readout_noise_to_counts
pass
def apply_readout_noise_to_counts(raw_counts: Dict[str, int], single_qubit_cals: Dict[int, Any]) -> Dict[str, int]:
"""Apply per-qubit readout calibration matrices to ideal counts to simulate readout noise.
Parameters:
raw_counts: counts dict mapping bitstring→frequency (ideal ground truth)
single_qubit_cals: mapping qubit index→2x2 matrix A so that measured_prob = A @ true_prob
Returns:
counts dict after applying readout channel.
"""
if not raw_counts:
return {}
import tyxonq as tq
nb = tq.get_backend() # use current numeric backend
n = len(next(iter(raw_counts.keys())))
size = 2**n
prob_true = nb.zeros((size,), dtype=nb.float64)
shots = 0.0
for bstr, cnt in raw_counts.items():
idx = int(bstr, 2)
arr = nb.to_numpy(prob_true)
arr[idx] = float(cnt)
prob_true = nb.asarray(arr)
shots += float(cnt)
if shots <= 0:
return dict(raw_counts)
prob_true = nb.asarray(nb.to_numpy(prob_true) / shots)
# kron A on ascending wires (0..n-1)
A = None
for q in range(n):
m = single_qubit_cals.get(q)
if m is None:
m = nb.eye(2)
A = m if A is None else nb.kron(A, m)
# Convert to numpy for final rounding and indexing ops
prob_meas_np = np.asarray(nb.to_numpy(A) @ nb.to_numpy(prob_true), dtype=float)
prob_meas_np = np.clip(prob_meas_np, 0.0, 1.0)
s = float(np.sum(prob_meas_np))
if s <= 1e-12:
s = 1.0
prob_meas_np = prob_meas_np / s
vecm = np.rint(prob_meas_np * shots).astype(int)
out: Dict[str, int] = {}
nz_idx = np.nonzero(vecm)[0]
for idx in nz_idx:
out[format(int(idx), f"0{n}b")] = int(vecm[int(idx)])
return out
def demo_readout_mitigation():
c = build_bell_circuit(2)
shots = 10000
# Ideal counts (synthetic)
ideal = ideal_counts_for_bell(shots)
# Define per-qubit calibration matrices (measured_prob = A @ true_prob), row-stochastic
# Example: mild flip biases
nb = tq.set_backend("numpy") # choose numeric backend
A0 = nb.array([[0.97, 0.03], [0.05, 0.95]], dtype=nb.float64)
A1 = nb.array([[0.98, 0.02], [0.04, 0.96]], dtype=nb.float64)
# Synthesize raw counts as if readout errors were applied
raw = apply_readout_noise_to_counts(ideal, {0: A0, 1: A1})
# Configure mitigation with known calibrations (numeric path)
mit = ReadoutMit()
# nb.to_numpy exists via API: convert backend arrays to numpy for scipy interop inside mitigation
mit.set_single_qubit_cals({0: nb.to_numpy(A0), 1: nb.to_numpy(A1)})
corrected = mit.apply_readout_mitigation(raw, method="inverse", qubits=[0, 1], shots=shots)
# Chainable circuit execution path (proper noise injection via device layer)
run_results = (
c
.device(
provider="simulator",
device="statevector",
shots=shots,
use_noise=True,
noise={"type": "readout", "cals": {0: A0, 1: A1}},
)
.postprocessing(method="readout_mitigation", cals={0: nb.to_numpy(A0), 1: nb.to_numpy(A1)}, mitigation="inverse")
.run()
)
counts_from_run = run_results.get("result", {}) if isinstance(run_results, dict) else run_results[0].get("result", {})
# Postprocessing is performed by Circuit.run when method="readout_mitigation" and cals provided
print("Ideal:", ideal)
print("Raw (with readout error):", raw)
print("Corrected (mitigated, numeric):", corrected)
print("Run(raw with device-injected readout noise):", counts_from_run)
postproc_payload = (
run_results.get("postprocessing", {})
if isinstance(run_results, dict)
else (run_results[0].get("postprocessing", {}) if run_results else {})
)
print("Postprocessing payload:", postproc_payload)
if __name__ == "__main__":
demo_readout_mitigation()