|
| 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