Skip to content

Commit 8eb928d

Browse files
Add calibration-to-noise pipeline (#5187)
This PR reinstates the cirq_google utility for converting `Calibration` objects to noise properties. To create a simulator with behavior that mimics some `Calibration` "cal", the expected usage is: ```python noise_props = cg.noise_properties_from_calibration(cal) noise_model = cg.NoiseModelFromGoogleNoiseProperties(noise_props) simulator = cirq.Simulator(noise=noise_model) # will mimic noise from `calibration` ``` This is part 4 of #4666; part 3 was #5082. Remaining steps include: - Convenience methods for modifying noise properties - Reinstatement of compare_generated_noise_to_metrics using new types
1 parent 8b108eb commit 8eb928d

File tree

2 files changed

+524
-0
lines changed

2 files changed

+524
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
16+
"""Tools for converting Calibrations to NoiseProperties.
17+
18+
Given a Calibration "cal", a user can simulate noise approximating that
19+
calibration using the following pipeline:
20+
21+
>>> noise_props = cg.noise_properties_from_calibration(cal)
22+
>>> noise_model = cg.NoiseModelFromGoogleNoiseProperties(noise_props)
23+
>>> simulator = cirq.Simulator(noise=noise_model)
24+
>>> result = simulator.simulate(circuit)
25+
# 'result' contains the simulation results
26+
"""
27+
28+
from typing import Dict, Tuple, Type, TYPE_CHECKING
29+
import numpy as np
30+
31+
from cirq import ops
32+
from cirq.devices import noise_utils
33+
from cirq_google import engine
34+
from cirq_google import ops as cg_ops
35+
from cirq_google.devices import google_noise_properties
36+
37+
if TYPE_CHECKING:
38+
import cirq
39+
40+
41+
def _unpack_1q_from_calibration(
42+
metric_name: str, calibration: engine.Calibration
43+
) -> Dict['cirq.Qid', float]:
44+
"""Converts a single-qubit metric from Calibration to dict format."""
45+
if metric_name not in calibration:
46+
return {}
47+
return {
48+
engine.Calibration.key_to_qubit(key): engine.Calibration.value_to_float(val)
49+
for key, val in calibration[metric_name].items()
50+
}
51+
52+
53+
def _unpack_2q_from_calibration(
54+
metric_name: str, calibration: engine.Calibration
55+
) -> Dict[Tuple['cirq.Qid', ...], float]:
56+
"""Converts a two-qubit metric from Calibration to dict format."""
57+
if metric_name not in calibration:
58+
return {}
59+
return {
60+
engine.Calibration.key_to_qubits(key): engine.Calibration.value_to_float(val)
61+
for key, val in calibration[metric_name].items()
62+
}
63+
64+
65+
def noise_properties_from_calibration(
66+
calibration: engine.Calibration,
67+
) -> google_noise_properties.GoogleNoiseProperties:
68+
"""Translates between `cirq_google.Calibration` and NoiseProperties.
69+
70+
The NoiseProperties object can then be used as input to the
71+
`cirq.devices.noise_propertiesNoiseModelFromNoiseProperties` class to
72+
create a `cirq.NoiseModel` that can be used with a simulator.
73+
74+
To manually override noise properties, call `override` on the output:
75+
76+
# Set all gate durations to 37ns.
77+
>>> noise_properties_from_calibration(cal).override(gate_times_ns=37)
78+
79+
See `cirq_google.GoogleNoiseProperties` for details.
80+
81+
Args:
82+
calibration: a Calibration object with hardware metrics.
83+
84+
Returns:
85+
A `cirq_google.GoogleNoiseProperties` which represents the error
86+
present in the given Calibration object.
87+
"""
88+
89+
# TODO: acquire this based on the target device.
90+
# Default map of gates to their durations.
91+
default_gate_ns: Dict[Type['cirq.Gate'], float] = {
92+
ops.ZPowGate: 25.0,
93+
ops.MeasurementGate: 4000.0,
94+
ops.ResetChannel: 250.0,
95+
ops.PhasedXZGate: 25.0,
96+
ops.FSimGate: 32.0,
97+
ops.ISwapPowGate: 32.0,
98+
ops.CZPowGate: 32.0,
99+
# ops.WaitGate is a special case.
100+
}
101+
102+
# Unpack all values from Calibration object
103+
# 1. Extract T1 for all qubits
104+
T1_micros = _unpack_1q_from_calibration('single_qubit_idle_t1_micros', calibration)
105+
t1_ns = {q: T1_micro * 1000 for q, T1_micro in T1_micros.items()}
106+
107+
# 2. Extract Tphi for all qubits
108+
rb_incoherent_errors = _unpack_1q_from_calibration(
109+
'single_qubit_rb_incoherent_error_per_gate', calibration
110+
)
111+
tphi_ns = {}
112+
if rb_incoherent_errors:
113+
microwave_time_ns = default_gate_ns[ops.PhasedXZGate]
114+
for qubit, q_t1_ns in t1_ns.items():
115+
tphi_err = rb_incoherent_errors[qubit] - microwave_time_ns / (3 * q_t1_ns)
116+
q_tphi_ns = 1e10 if tphi_err <= 0 else microwave_time_ns / (3 * tphi_err)
117+
tphi_ns[qubit] = q_tphi_ns
118+
119+
# 3a. Extract Pauli error for single-qubit gates.
120+
rb_pauli_errors = _unpack_1q_from_calibration(
121+
'single_qubit_rb_pauli_error_per_gate', calibration
122+
)
123+
gate_pauli_errors = {
124+
noise_utils.OpIdentifier(gate, q): pauli_err
125+
for q, pauli_err in rb_pauli_errors.items()
126+
for gate in google_noise_properties.SINGLE_QUBIT_GATES
127+
}
128+
129+
# 3b. Extract Pauli error for two-qubit gates.
130+
gate_prefix_pairs: Dict[Type['cirq.Gate'], str] = {
131+
cg_ops.SycamoreGate: 'two_qubit_parallel_sycamore_gate',
132+
ops.ISwapPowGate: 'two_qubit_parallel_sqrt_iswap_gate',
133+
}
134+
for gate, prefix in gate_prefix_pairs.items():
135+
pauli_error = _unpack_2q_from_calibration(
136+
prefix + '_xeb_pauli_error_per_cycle', calibration
137+
)
138+
gate_pauli_errors.update(
139+
{
140+
k: v
141+
for qs, pauli_err in pauli_error.items()
142+
for k, v in {
143+
noise_utils.OpIdentifier(gate, *qs): pauli_err,
144+
noise_utils.OpIdentifier(gate, *qs[::-1]): pauli_err,
145+
}.items()
146+
}
147+
)
148+
149+
# 4. Extract readout fidelity for all qubits.
150+
p00 = _unpack_1q_from_calibration('single_qubit_p00_error', calibration)
151+
p11 = _unpack_1q_from_calibration('single_qubit_p11_error', calibration)
152+
readout_errors = {
153+
q: np.array([p00.get(q, 0), p11.get(q, 0)]) for q in set(p00.keys()) | set(p11.keys())
154+
}
155+
156+
# 5. Extract entangling angle errors.
157+
fsim_errors = {}
158+
for gate, prefix in gate_prefix_pairs.items():
159+
theta_errors = _unpack_2q_from_calibration(
160+
prefix + '_xeb_entangler_theta_error_per_cycle',
161+
calibration,
162+
)
163+
phi_errors = _unpack_2q_from_calibration(
164+
prefix + '_xeb_entangler_phi_error_per_cycle',
165+
calibration,
166+
)
167+
angle_keys = set(theta_errors.keys()) | set(phi_errors.keys())
168+
for qubits in angle_keys:
169+
theta = theta_errors.get(qubits, 0)
170+
phi = phi_errors.get(qubits, 0)
171+
op_id = noise_utils.OpIdentifier(gate, *qubits)
172+
fsim_errors[op_id] = ops.PhasedFSimGate(theta=theta, phi=phi)
173+
op_id_reverse = noise_utils.OpIdentifier(gate, *qubits[::-1])
174+
fsim_errors[op_id_reverse] = ops.PhasedFSimGate(theta=theta, phi=phi)
175+
176+
# Known false positive: https://github.com/PyCQA/pylint/issues/5857
177+
return google_noise_properties.GoogleNoiseProperties( # pylint: disable=unexpected-keyword-arg
178+
gate_times_ns=default_gate_ns,
179+
t1_ns=t1_ns,
180+
tphi_ns=tphi_ns,
181+
readout_errors=readout_errors,
182+
gate_pauli_errors=gate_pauli_errors,
183+
fsim_errors=fsim_errors,
184+
)

0 commit comments

Comments
 (0)