Skip to content

Commit e6c584b

Browse files
authored
Calibration to fidelity (#4431)
Ignore the fidelity.py and fidelity_test.py files- those are already under review. All feedback is appreciated!
1 parent aa8184c commit e6c584b

File tree

2 files changed

+398
-0
lines changed

2 files changed

+398
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import cirq_google
2+
import numpy as np
3+
from cirq.devices.noise_properties import NoiseProperties
4+
5+
6+
def _xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits=2):
7+
# Converts from XEB Fidelity to depolarization decay constant
8+
if xeb_fidelity is not None:
9+
N = 2 ** num_qubits # Dimension of Hilbert space
10+
return 1 - (1 - xeb_fidelity) / (1 - 1 / N)
11+
return None
12+
13+
14+
def _rb_average_error_to_decay_constant(rb_average_error, num_qubits: int = 1):
15+
# Converts from randomized benchmarking average error to depolarization decay constant
16+
if rb_average_error is not None:
17+
N = 2 ** num_qubits # Dimension of Hilbert space
18+
return 1 - rb_average_error / (1 - 1 / N)
19+
else:
20+
return None
21+
22+
23+
def _rb_pauli_error_to_decay_constant(rb_pauli_error, num_qubits: int = 1):
24+
# Converts from randomized benchmarking pauli error to depolarization decay constant
25+
if rb_pauli_error is not None:
26+
N = 2 ** num_qubits # Dimension of Hilbert space
27+
return 1 - rb_pauli_error / (1 - 1 / N ** 2)
28+
else:
29+
return None
30+
31+
32+
def _within_tolerance(val_1, val_2, tolerance):
33+
# Helper function to check if two values are within tolerance
34+
if val_1 is None or val_2 is None:
35+
return True
36+
return abs(val_1 - val_2) <= tolerance
37+
38+
39+
def _unpack_from_calibration(metric_name, calibration):
40+
# Gets the average (over all qubits) of each metric
41+
# TODO: Add support for per-qubit noise
42+
if metric_name in calibration.keys():
43+
return np.mean([value for qubit, value in calibration[metric_name].items()])
44+
else:
45+
return None
46+
47+
48+
def noise_properties_from_calibration(
49+
calibration: cirq_google.Calibration, validate: bool = True, tolerance: float = 0.01
50+
):
51+
"""Translates between a Calibration object and a NoiseProperties object.
52+
The NoiseProperties object can then be used as input to the NoiseModelFromNoiseProperties
53+
class (cirq.devices.noise_properties) to create a NoiseModel that can be used with a simulator.
54+
55+
If the validate argument is set to false, the depolarization decay constant will be calculated
56+
from the RB Pauli error if defined, the XEB Fidelity if RB Pauli error is not defined, or the
57+
RB Average error if the others are not defined.
58+
59+
Args:
60+
calibration: a Calibration object with hardware metrics
61+
validate: whether or not to check that the depolarization decay constants calculated from
62+
RB Pauli error, RB average error, & XEB Fidelity agree to within a given tolerance
63+
tolerance: threshold for validating decay constants frmo RB Pauli error, RB Average error,
64+
and XEB fidelity.
65+
66+
Raises:
67+
ValueError: decay constants from RB Average Error and RB Pauli Error aren't within tolerance
68+
69+
ValueError: decay constants from RB Pauli Error and XEB Fidelity aren't within tolerance
70+
71+
ValueError: decay constant from RB Pauli Error and XEB Fidelity aren't within tolerance
72+
"""
73+
74+
# Unpack all values from Calibration object
75+
t1_micros = _unpack_from_calibration('single_qubit_idle_t1_micros', calibration)
76+
t1_nanos = t1_micros * 1000 if t1_micros is not None else None
77+
xeb_fidelity = _unpack_from_calibration('xeb', calibration)
78+
rb_pauli_error = _unpack_from_calibration('single_qubit_rb_pauli_error_per_gate', calibration)
79+
rb_average_error = _unpack_from_calibration(
80+
'single_qubit_rb_average_error_per_gate', calibration
81+
)
82+
p00 = _unpack_from_calibration('single_qubit_p00_error', calibration)
83+
p11 = _unpack_from_calibration('single_qubit_p11_error', calibration)
84+
decay_constant_pauli = _rb_pauli_error_to_decay_constant(rb_pauli_error)
85+
86+
decay_constant_average = _rb_average_error_to_decay_constant(rb_average_error)
87+
88+
if validate: # Will throw error if metrics aren't compatible
89+
if not _within_tolerance(decay_constant_pauli, decay_constant_average, tolerance):
90+
raise ValueError(
91+
f'Decay constant from RB Pauli error: {decay_constant_pauli}, '
92+
f'decay constant from RB Average error: {decay_constant_average}. '
93+
'If validation is disabled, RB Pauli error will be used.'
94+
)
95+
decay_constant_from_xeb = _xeb_fidelity_to_decay_constant(xeb_fidelity)
96+
if not _within_tolerance(decay_constant_from_xeb, decay_constant_pauli, tolerance):
97+
raise ValueError(
98+
f'Decay constant from RB Pauli error: {decay_constant_pauli}, '
99+
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. '
100+
'If validation is disabled, RB Pauli error will be used.'
101+
)
102+
if not _within_tolerance(decay_constant_from_xeb, decay_constant_average, tolerance):
103+
raise ValueError(
104+
f'Decay constant from RB Average error: {decay_constant_average}, '
105+
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. '
106+
'If validation is disabled, XEB Fidelity will be used.'
107+
)
108+
109+
if decay_constant_pauli is not None: # can't define both decay constant and xeb
110+
return NoiseProperties(
111+
t1_ns=t1_nanos, decay_constant=decay_constant_pauli, p00=p00, p11=p11
112+
)
113+
if xeb_fidelity is not None:
114+
return NoiseProperties(t1_ns=t1_nanos, xeb_fidelity=xeb_fidelity, p00=p00, p11=p11)
115+
return NoiseProperties(t1_ns=t1_nanos, decay_constant=decay_constant_average, p00=p00, p11=p11)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
import pytest
2+
import cirq_google
3+
from cirq_google.api import v2
4+
from cirq_google.experimental.noise_models.calibration_to_noise_properties import (
5+
noise_properties_from_calibration,
6+
)
7+
from google.protobuf.text_format import Merge
8+
import numpy as np
9+
10+
11+
def test_noise_properties_from_calibration():
12+
xeb_1 = 0.999
13+
xeb_2 = 0.996
14+
15+
p00_1 = 0.001
16+
p00_2 = 0.002
17+
p00_3 = 0.003
18+
19+
t1_1 = 0.005
20+
t1_2 = 0.007
21+
t1_3 = 0.003
22+
23+
_CALIBRATION_DATA = Merge(
24+
f"""
25+
timestamp_ms: 1579214873,
26+
metrics: [{{
27+
name: 'xeb',
28+
targets: ['0_0', '0_1'],
29+
values: [{{
30+
double_val: {xeb_1}
31+
}}]
32+
}}, {{
33+
name: 'xeb',
34+
targets: ['0_0', '1_0'],
35+
values: [{{
36+
double_val:{xeb_2}
37+
}}]
38+
}}, {{
39+
name: 'single_qubit_p00_error',
40+
targets: ['0_0'],
41+
values: [{{
42+
double_val: {p00_1}
43+
}}]
44+
}}, {{
45+
name: 'single_qubit_p00_error',
46+
targets: ['0_1'],
47+
values: [{{
48+
double_val: {p00_2}
49+
}}]
50+
}}, {{
51+
name: 'single_qubit_p00_error',
52+
targets: ['1_0'],
53+
values: [{{
54+
double_val: {p00_3}
55+
}}]
56+
}}, {{
57+
name: 'single_qubit_readout_separation_error',
58+
targets: ['0_0'],
59+
values: [{{
60+
double_val: .004
61+
}}]
62+
}}, {{
63+
name: 'single_qubit_readout_separation_error',
64+
targets: ['0_1'],
65+
values: [{{
66+
double_val: .005
67+
}}]
68+
}},{{
69+
name: 'single_qubit_readout_separation_error',
70+
targets: ['1_0'],
71+
values: [{{
72+
double_val: .006
73+
}}]
74+
}}, {{
75+
name: 'single_qubit_idle_t1_micros',
76+
targets: ['0_0'],
77+
values: [{{
78+
double_val: {t1_1}
79+
}}]
80+
}}, {{
81+
name: 'single_qubit_idle_t1_micros',
82+
targets: ['0_1'],
83+
values: [{{
84+
double_val: {t1_2}
85+
}}]
86+
}}, {{
87+
name: 'single_qubit_idle_t1_micros',
88+
targets: ['1_0'],
89+
values: [{{
90+
double_val: {t1_3}
91+
}}]
92+
}}]
93+
""",
94+
v2.metrics_pb2.MetricsSnapshot(),
95+
)
96+
97+
# Create NoiseProperties object from Calibration
98+
calibration = cirq_google.Calibration(_CALIBRATION_DATA)
99+
prop = noise_properties_from_calibration(calibration)
100+
101+
expected_t1_nanos = np.mean([t1_1, t1_2, t1_3]) * 1000
102+
expected_xeb_fidelity = np.mean([xeb_1, xeb_2])
103+
expected_p00 = np.mean([p00_1, p00_2, p00_3])
104+
105+
assert np.isclose(prop.t1_ns, expected_t1_nanos)
106+
assert np.isclose(prop.xeb, expected_xeb_fidelity)
107+
assert np.isclose(prop.p00, expected_p00)
108+
109+
110+
def test_from_calibration_rb():
111+
rb_pauli_1 = 0.001
112+
rb_pauli_2 = 0.002
113+
rb_pauli_3 = 0.003
114+
115+
_CALIBRATION_DATA_RB = Merge(
116+
f"""
117+
timestamp_ms: 1579214873,
118+
metrics: [{{
119+
120+
name: 'single_qubit_rb_pauli_error_per_gate',
121+
targets: ['0_0'],
122+
values: [{{
123+
double_val: {rb_pauli_1}
124+
}}]
125+
}}, {{
126+
name: 'single_qubit_rb_pauli_error_per_gate',
127+
targets: ['0_1'],
128+
values: [{{
129+
double_val: {rb_pauli_2}
130+
}}]
131+
}}, {{
132+
name: 'single_qubit_rb_pauli_error_per_gate',
133+
targets: ['1_0'],
134+
values: [{{
135+
double_val: {rb_pauli_3}
136+
}}]
137+
}}]
138+
""",
139+
v2.metrics_pb2.MetricsSnapshot(),
140+
)
141+
142+
# Create NoiseProperties object from Calibration
143+
rb_calibration = cirq_google.Calibration(_CALIBRATION_DATA_RB)
144+
rb_noise_prop = noise_properties_from_calibration(rb_calibration)
145+
146+
average_pauli_rb = np.mean([rb_pauli_1, rb_pauli_2, rb_pauli_3])
147+
assert np.isclose(average_pauli_rb, rb_noise_prop.pauli_error)
148+
149+
150+
def test_validate_calibration():
151+
# RB Pauli error and RB Average Error disagree
152+
rb_pauli_error = 0.05
153+
rb_average_error = 0.1
154+
155+
decay_constant_pauli = 1 - rb_pauli_error / (1 - 1 / 4)
156+
decay_constant_average = 1 - rb_average_error / (1 - 1 / 2)
157+
_CALIBRATION_DATA_PAULI_AVERAGE = Merge(
158+
f"""
159+
timestamp_ms: 1579214873,
160+
metrics: [{{
161+
162+
name: 'single_qubit_rb_pauli_error_per_gate',
163+
targets: ['0_0'],
164+
values: [{{
165+
double_val: {rb_pauli_error}
166+
}}]
167+
}}, {{
168+
name: 'single_qubit_rb_average_error_per_gate',
169+
targets: ['0_1'],
170+
values: [{{
171+
double_val: {rb_average_error}
172+
}}]
173+
}}]
174+
""",
175+
v2.metrics_pb2.MetricsSnapshot(),
176+
)
177+
bad_calibration_pauli_average = cirq_google.Calibration(_CALIBRATION_DATA_PAULI_AVERAGE)
178+
with pytest.raises(
179+
ValueError,
180+
match=f'Decay constant from RB Pauli error: {decay_constant_pauli}, '
181+
f'decay constant from RB Average error: {decay_constant_average}. '
182+
'If validation is disabled, RB Pauli error will be used.',
183+
):
184+
noise_properties_from_calibration(bad_calibration_pauli_average)
185+
186+
assert np.isclose(
187+
noise_properties_from_calibration(
188+
bad_calibration_pauli_average, validate=False
189+
).pauli_error,
190+
rb_pauli_error,
191+
)
192+
193+
# RB Pauli Error and XEB Fidelity disagree
194+
xeb_fidelity = 0.99
195+
196+
decay_constant_from_xeb = 1 - (1 - xeb_fidelity) / (1 - 1 / 4)
197+
198+
_CALIBRATION_DATA_PAULI_XEB = Merge(
199+
f"""
200+
timestamp_ms: 1579214873,
201+
metrics: [{{
202+
203+
name: 'single_qubit_rb_pauli_error_per_gate',
204+
targets: ['0_0'],
205+
values: [{{
206+
double_val: {rb_pauli_error}
207+
}}]
208+
}}, {{
209+
name: 'xeb',
210+
targets: ['0_0', '1_0'],
211+
values: [{{
212+
double_val:{xeb_fidelity}
213+
}}]
214+
}}]
215+
""",
216+
v2.metrics_pb2.MetricsSnapshot(),
217+
)
218+
219+
bad_calibration_pauli_xeb = cirq_google.Calibration(_CALIBRATION_DATA_PAULI_XEB)
220+
with pytest.raises(
221+
ValueError,
222+
match=f'Decay constant from RB Pauli error: {decay_constant_pauli}, '
223+
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. '
224+
'If validation is disabled, RB Pauli error will be used.',
225+
):
226+
noise_properties_from_calibration(bad_calibration_pauli_xeb)
227+
228+
# RB Average Error and XEB Fidelity disagree
229+
_CALIBRATION_DATA_AVERAGE_XEB = Merge(
230+
f"""
231+
timestamp_ms: 1579214873,
232+
metrics: [{{
233+
234+
name: 'single_qubit_rb_average_error_per_gate',
235+
targets: ['0_0'],
236+
values: [{{
237+
double_val: {rb_average_error}
238+
}}]
239+
}}, {{
240+
name: 'xeb',
241+
targets: ['0_0', '1_0'],
242+
values: [{{
243+
double_val:{xeb_fidelity}
244+
}}]
245+
}}]
246+
""",
247+
v2.metrics_pb2.MetricsSnapshot(),
248+
)
249+
250+
bad_calibration_average_xeb = cirq_google.Calibration(_CALIBRATION_DATA_AVERAGE_XEB)
251+
with pytest.raises(
252+
ValueError,
253+
match=f'Decay constant from RB Average error: {decay_constant_average}, '
254+
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. '
255+
'If validation is disabled, XEB Fidelity will be used.',
256+
):
257+
noise_properties_from_calibration(bad_calibration_average_xeb)
258+
259+
assert np.isclose(
260+
noise_properties_from_calibration(bad_calibration_average_xeb, validate=False).xeb,
261+
xeb_fidelity,
262+
)
263+
264+
# Calibration data with no RB error or XEB fidelity
265+
t1 = 2.0 # microseconds
266+
267+
_CALIBRATION_DATA_T1 = Merge(
268+
f"""
269+
timestamp_ms: 1579214873,
270+
metrics: [{{
271+
name: 'single_qubit_idle_t1_micros',
272+
targets: ['0_0'],
273+
values: [{{
274+
double_val: {t1}
275+
}}]
276+
}}]
277+
""",
278+
v2.metrics_pb2.MetricsSnapshot(),
279+
)
280+
281+
calibration_t1 = cirq_google.Calibration(_CALIBRATION_DATA_T1)
282+
283+
assert np.isclose(noise_properties_from_calibration(calibration_t1).t1_ns, t1 * 1000)

0 commit comments

Comments
 (0)