Skip to content

Commit d02c9bf

Browse files
tanujkhattarrht
authored andcommitted
Add cirq.TensoredConfusionMatrices for readout error mitigation. (quantumlib#4854)
Fixes quantumlib#4800. cc @mrwojtek @mpharrigan
1 parent 011f937 commit d02c9bf

8 files changed

+599
-6
lines changed

cirq-core/cirq/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
)
103103

104104
from cirq.experiments import (
105+
TensoredConfusionMatrices,
105106
estimate_parallel_single_qubit_readout_errors,
106107
estimate_single_qubit_readout_errors,
107108
hog_score_xeb_fidelity_from_probabilities,
@@ -114,6 +115,7 @@
114115
generate_boixo_2018_supremacy_circuits_v2,
115116
generate_boixo_2018_supremacy_circuits_v2_bristlecone,
116117
generate_boixo_2018_supremacy_circuits_v2_grid,
118+
measure_confusion_matrix,
117119
xeb_fidelity,
118120
)
119121

cirq-core/cirq/experiments/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@
6565
random_rotations_between_grid_interaction_layers_circuit,
6666
)
6767

68+
from cirq.experiments.readout_confusion_matrix import (
69+
TensoredConfusionMatrices,
70+
measure_confusion_matrix,
71+
)
72+
6873
from cirq.experiments.n_qubit_tomography import (
6974
get_state_tomography_data,
7075
state_tomography,

cirq-core/cirq/experiments/readout_confusion_matrix.py

+367
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copyright 2022 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import numpy as np
16+
import cirq
17+
import pytest
18+
19+
from cirq.experiments.single_qubit_readout_calibration_test import NoisySingleQubitReadoutSampler
20+
21+
22+
def get_expected_cm(num_qubits: int, p0: float, p1: float):
23+
expected_cm = np.zeros((2 ** num_qubits,) * 2)
24+
for i in range(2 ** num_qubits):
25+
for j in range(2 ** num_qubits):
26+
p = 1.0
27+
for k in range(num_qubits):
28+
b0 = (i >> k) & 1
29+
b1 = (j >> k) & 1
30+
if b0 == 0:
31+
p *= p0 * b1 + (1 - p0) * (1 - b1)
32+
else:
33+
p *= p1 * (1 - b1) + (1 - p1) * b1
34+
expected_cm[i][j] = p
35+
return expected_cm
36+
37+
38+
@pytest.mark.parametrize('p0, p1', [(0, 0), (0.2, 0.4), (0.5, 0.5), (0.6, 0.3), (1.0, 1.0)])
39+
def test_measure_confusion_matrix_with_noise(p0, p1):
40+
sampler = NoisySingleQubitReadoutSampler(p0, p1, seed=1234)
41+
num_qubits = 4
42+
qubits = cirq.LineQubit.range(num_qubits)
43+
expected_cm = get_expected_cm(num_qubits, p0, p1)
44+
qubits_small = qubits[:2]
45+
expected_cm_small = get_expected_cm(2, p0, p1)
46+
repetitions = 12_000
47+
# Build entire confusion matrix by running 2 ** 4 = 16 circuits.
48+
readout_cm = cirq.measure_confusion_matrix(sampler, qubits, repetitions=repetitions)
49+
assert readout_cm.repetitions == repetitions
50+
for q, expected in zip([None, qubits_small], [expected_cm, expected_cm_small]):
51+
np.testing.assert_allclose(readout_cm.confusion_matrix(q), expected, atol=1e-2)
52+
np.testing.assert_allclose(
53+
readout_cm.confusion_matrix(q) @ readout_cm.correction_matrix(q),
54+
np.eye(expected.shape[0]),
55+
atol=1e-2,
56+
)
57+
58+
# Build a tensored confusion matrix using smaller single qubit confusion matrices.
59+
# This works because the error is uncorrelated and requires only 4 * 2 = 8 circuits.
60+
readout_cm = cirq.measure_confusion_matrix(
61+
sampler, [[q] for q in qubits], repetitions=repetitions
62+
)
63+
assert readout_cm.repetitions == repetitions
64+
for q, expected in zip([None, qubits_small], [expected_cm, expected_cm_small]):
65+
np.testing.assert_allclose(readout_cm.confusion_matrix(q), expected, atol=1e-2)
66+
np.testing.assert_allclose(
67+
readout_cm.confusion_matrix(q) @ readout_cm.correction_matrix(q),
68+
np.eye(expected.shape[0]),
69+
atol=1e-2,
70+
)
71+
72+
# Apply corrections to sampled probabilities using readout_cm.
73+
qs = qubits_small
74+
circuit = cirq.Circuit(cirq.H.on_each(*qs), cirq.measure(*qs))
75+
reps = 100_000
76+
sampled_result = cirq.get_state_histogram(sampler.run(circuit, repetitions=reps)) / reps
77+
expected_result = [1 / 4] * 4
78+
79+
def l2norm(result: np.ndarray):
80+
return np.sum((expected_result - result) ** 2)
81+
82+
corrected_result = readout_cm.apply(sampled_result, qs)
83+
assert l2norm(corrected_result) <= l2norm(sampled_result)
84+
85+
86+
def test_readout_confusion_matrix_raises():
87+
num_qubits = 2
88+
confusion_matrix = get_expected_cm(num_qubits, 0.1, 0.2)
89+
qubits = cirq.LineQubit.range(4)
90+
with pytest.raises(ValueError, match=r"measure_qubits cannot be empty"):
91+
_ = cirq.TensoredConfusionMatrices([], [], repetitions=0, timestamp=0)
92+
93+
with pytest.raises(ValueError, match=r"len\(confusion_matrices\)"):
94+
_ = cirq.TensoredConfusionMatrices(
95+
[confusion_matrix], [qubits[:2], qubits[2:]], repetitions=0, timestamp=0
96+
)
97+
98+
with pytest.raises(ValueError, match="Shape mismatch for confusion matrix"):
99+
_ = cirq.TensoredConfusionMatrices(confusion_matrix, qubits, repetitions=0, timestamp=0)
100+
101+
with pytest.raises(ValueError, match="Repeated qubits not allowed"):
102+
_ = cirq.TensoredConfusionMatrices(
103+
[confusion_matrix, confusion_matrix],
104+
[qubits[:2], qubits[1:3]],
105+
repetitions=0,
106+
timestamp=0,
107+
)
108+
109+
readout_cm = cirq.TensoredConfusionMatrices(
110+
[confusion_matrix, confusion_matrix], [qubits[:2], qubits[2:]], repetitions=0, timestamp=0
111+
)
112+
113+
with pytest.raises(ValueError, match="should be a subset of"):
114+
_ = readout_cm.confusion_matrix([cirq.NamedQubit("a")])
115+
116+
with pytest.raises(ValueError, match="should be a subset of"):
117+
_ = readout_cm.correction_matrix([cirq.NamedQubit("a")])
118+
119+
with pytest.raises(ValueError, match="result.shape .* should be"):
120+
_ = readout_cm.apply(np.asarray([100]), qubits[:2])
121+
122+
with pytest.raises(ValueError, match="method.* should be"):
123+
_ = readout_cm.apply(np.asarray([1 / 16] * 16), method='l1norm')
124+
125+
126+
def test_readout_confusion_matrix_repr_and_equality():
127+
mat1 = cirq.testing.random_orthogonal(4, random_state=1234)
128+
mat2 = cirq.testing.random_orthogonal(2, random_state=1234)
129+
q = cirq.LineQubit.range(3)
130+
a = cirq.TensoredConfusionMatrices([mat1, mat2], [q[:2], q[2:]], repetitions=0, timestamp=0)
131+
b = cirq.TensoredConfusionMatrices(mat1, q[:2], repetitions=0, timestamp=0)
132+
c = cirq.TensoredConfusionMatrices(mat2, q[2:], repetitions=0, timestamp=0)
133+
for x in [a, b, c]:
134+
cirq.testing.assert_equivalent_repr(x)
135+
assert cirq.approx_eq(x, x)
136+
assert x._approx_eq_(mat1, 1e-6) is NotImplemented
137+
eq = cirq.testing.EqualsTester()
138+
eq.add_equality_group(a, a)
139+
eq.add_equality_group(b, b)
140+
eq.add_equality_group(c, c)

cirq-core/cirq/experiments/single_qubit_readout_calibration_test.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def run_sweep(
5252
results = self.simulator.run_sweep(program, params, repetitions)
5353
for result in results:
5454
for bits in result.measurements.values():
55-
with np.nditer(bits, op_flags=['readwrite']) as it:
56-
for x in it:
57-
if x == 0 and self.prng.uniform() < self.p0:
58-
x[...] = 1
59-
elif self.prng.uniform() < self.p1:
60-
x[...] = 0
55+
rand_num = self.prng.uniform(size=bits.shape)
56+
should_flip = np.logical_or(
57+
np.logical_and(bits == 0, rand_num < self.p0),
58+
np.logical_and(bits == 1, rand_num < self.p1),
59+
)
60+
bits[should_flip] ^= 1
6161
return results
6262

6363

cirq-core/cirq/json_resolver_cache.py

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ def _parallel_gate_op(gate, qubits):
140140
'_QubitAsQid': raw_types._QubitAsQid,
141141
'QuantumFourierTransformGate': cirq.QuantumFourierTransformGate,
142142
'RandomGateChannel': cirq.RandomGateChannel,
143+
'TensoredConfusionMatrices': cirq.TensoredConfusionMatrices,
143144
'RepetitionsStoppingCriteria': cirq.work.RepetitionsStoppingCriteria,
144145
'ResetChannel': cirq.ResetChannel,
145146
'Result': cirq.Result,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"cirq_type": "TensoredConfusionMatrices",
3+
"confusion_matrices": [
4+
[
5+
[
6+
0.6395,
7+
0.1594,
8+
0.1592,
9+
0.0419
10+
],
11+
[
12+
0.5617,
13+
0.2368,
14+
0.1401,
15+
0.0614
16+
],
17+
[
18+
0.5655,
19+
0.1367,
20+
0.2403,
21+
0.0575
22+
],
23+
[
24+
0.4835,
25+
0.2185,
26+
0.2048,
27+
0.0932
28+
]
29+
],
30+
[
31+
[
32+
0.7999,
33+
0.2001
34+
],
35+
[
36+
0.7046,
37+
0.2954
38+
]
39+
]
40+
],
41+
"measure_qubits": [
42+
[
43+
{
44+
"cirq_type": "LineQubit",
45+
"x": 0
46+
},
47+
{
48+
"cirq_type": "LineQubit",
49+
"x": 1
50+
}
51+
],
52+
[
53+
{
54+
"cirq_type": "NamedQubit",
55+
"name": "a"
56+
}
57+
]
58+
],
59+
"repetitions": 10000,
60+
"timestamp": 1642630636.966274
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
cirq.TensoredConfusionMatrices(
2+
[
3+
np.array(
4+
[
5+
[0.6395, 0.1594, 0.1592, 0.0419],
6+
[0.5617, 0.2368, 0.1401, 0.0614],
7+
[0.5655, 0.1367, 0.2403, 0.0575],
8+
[0.4835, 0.2185, 0.2048, 0.0932],
9+
],
10+
dtype=np.float64,
11+
),
12+
np.array([[0.7999, 0.2001], [0.7046, 0.2954]], dtype=np.float64),
13+
],
14+
[[cirq.LineQubit(0), cirq.LineQubit(1)], [cirq.NamedQubit('a')]],
15+
repetitions=10000,
16+
timestamp=1642630636.966274,
17+
)

0 commit comments

Comments
 (0)