-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update calibration-to-noise experiment
- Loading branch information
1 parent
96c8cf9
commit 550a39a
Showing
3 changed files
with
255 additions
and
314 deletions.
There are no files selected for viewing
186 changes: 91 additions & 95 deletions
186
cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,117 +1,113 @@ | ||
# pylint: disable=wrong-or-nonexistent-copyright-notice | ||
import cirq_google | ||
# Copyright 2021 The Cirq Developers | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# https://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from typing import Dict, Optional | ||
import cirq, cirq_google | ||
import numpy as np | ||
from cirq.devices.noise_properties import NoiseProperties | ||
from cirq.devices.noise_properties import ( | ||
NoiseProperties, | ||
SINGLE_QUBIT_GATES, | ||
) | ||
from cirq.devices.noise_utils import ( | ||
OpIdentifier, | ||
) | ||
|
||
|
||
def _xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits=2): | ||
# Converts from XEB Fidelity to depolarization decay constant | ||
if xeb_fidelity is not None: | ||
N = 2 ** num_qubits # Dimension of Hilbert space | ||
return 1 - (1 - xeb_fidelity) / (1 - 1 / N) | ||
return None | ||
|
||
|
||
def _rb_average_error_to_decay_constant(rb_average_error, num_qubits: int = 1): | ||
# Converts from randomized benchmarking average error to depolarization decay constant | ||
if rb_average_error is not None: | ||
N = 2 ** num_qubits # Dimension of Hilbert space | ||
return 1 - rb_average_error / (1 - 1 / N) | ||
else: | ||
return None | ||
|
||
|
||
def _rb_pauli_error_to_decay_constant(rb_pauli_error, num_qubits: int = 1): | ||
# Converts from randomized benchmarking pauli error to depolarization decay constant | ||
if rb_pauli_error is not None: | ||
N = 2 ** num_qubits # Dimension of Hilbert space | ||
return 1 - rb_pauli_error / (1 - 1 / N ** 2) | ||
else: | ||
return None | ||
|
||
|
||
def _within_tolerance(val_1, val_2, tolerance): | ||
# Helper function to check if two values are within tolerance | ||
def _within_tolerance(val_1: Optional[float], val_2: Optional[float], tolerance: float) -> bool: | ||
"""Helper function to check if two values are within a given tolerance.""" | ||
if val_1 is None or val_2 is None: | ||
return True | ||
return abs(val_1 - val_2) <= tolerance | ||
|
||
|
||
def _unpack_from_calibration(metric_name, calibration): | ||
# Gets the average (over all qubits) of each metric | ||
# TODO: Add support for per-qubit noise | ||
if metric_name in calibration.keys(): | ||
return np.mean([value for qubit, value in calibration[metric_name].items()]) | ||
else: | ||
return None | ||
def _unpack_from_calibration( | ||
metric_name: str, calibration: cirq_google.Calibration | ||
) -> Dict[cirq.Qid, float]: | ||
"""Converts a single-qubit metric from Calibration to dict format.""" | ||
if metric_name not in calibration: | ||
return {} | ||
return { | ||
cirq_google.Calibration.key_to_qubit(key): cirq_google.Calibration.value_to_float(val) | ||
for key, val in calibration[metric_name].items() | ||
} | ||
|
||
|
||
def noise_properties_from_calibration( | ||
calibration: cirq_google.Calibration, validate: bool = True, tolerance: float = 0.01 | ||
): | ||
def noise_properties_from_calibration(calibration: cirq_google.Calibration) -> NoiseProperties: | ||
"""Translates between a Calibration object and a NoiseProperties object. | ||
The NoiseProperties object can then be used as input to the NoiseModelFromNoiseProperties | ||
class (cirq.devices.noise_properties) to create a NoiseModel that can be used with a simulator. | ||
If the validate argument is set to false, the depolarization decay constant will be calculated | ||
from the RB Pauli error if defined, the XEB Fidelity if RB Pauli error is not defined, or the | ||
RB Average error if the others are not defined. | ||
Args: | ||
calibration: a Calibration object with hardware metrics | ||
validate: whether or not to check that the depolarization decay constants calculated from | ||
RB Pauli error, RB average error, & XEB Fidelity agree to within a given tolerance | ||
tolerance: threshold for validating decay constants frmo RB Pauli error, RB Average error, | ||
and XEB fidelity. | ||
Raises: | ||
ValueError: decay constants from RB Average Error and RB Pauli Error aren't within tolerance | ||
ValueError: decay constants from RB Pauli Error and XEB Fidelity aren't within tolerance | ||
ValueError: decay constant from RB Pauli Error and XEB Fidelity aren't within tolerance | ||
""" | ||
|
||
# TODO: acquire this based on the target device. | ||
# Default map of gates to their durations. | ||
DEFAULT_GATE_NS: Dict[type, float] = { | ||
cirq.ZPowGate: 25.0, | ||
cirq.MeasurementGate: 4000.0, | ||
cirq.ResetChannel: 250.0, | ||
cirq.PhasedXZGate: 25.0, | ||
cirq.FSimGate: 32.0, | ||
cirq.ISwapPowGate: 32.0, | ||
cirq.CZPowGate: 32.0, | ||
# cirq.WaitGate is a special case. | ||
} | ||
|
||
# Unpack all values from Calibration object | ||
t1_micros = _unpack_from_calibration('single_qubit_idle_t1_micros', calibration) | ||
t1_nanos = t1_micros * 1000 if t1_micros is not None else None | ||
xeb_error = _unpack_from_calibration('xeb', calibration) | ||
xeb_fidelity = 1 - xeb_error if xeb_error is not None else None | ||
rb_pauli_error = _unpack_from_calibration('single_qubit_rb_pauli_error_per_gate', calibration) | ||
rb_average_error = _unpack_from_calibration( | ||
'single_qubit_rb_average_error_per_gate', calibration | ||
# 1. Extract T1 for all qubits | ||
T1_micros = _unpack_from_calibration('single_qubit_idle_t1_micros', calibration) | ||
T1_ns = {q: T1_micro * 1000 for q, T1_micro in T1_micros.items()} | ||
|
||
# 2. Extract Tphi for all qubits | ||
rb_incoherent_errors = _unpack_from_calibration( | ||
'single_qubit_rb_incoherent_error_per_gate', calibration | ||
) | ||
Tphi_ns = {} | ||
if rb_incoherent_errors: | ||
microwave_time_ns = DEFAULT_GATE_NS[cirq.PhasedXZGate] | ||
for qubit, t1_ns in T1_ns.items(): | ||
tphi_err = rb_incoherent_errors[qubit] - microwave_time_ns / (3 * t1_ns) | ||
if tphi_err > 0: | ||
tphi_ns = microwave_time_ns / (3 * tphi_err) | ||
else: | ||
tphi_ns = 1e10 | ||
Tphi_ns[qubit] = tphi_ns | ||
|
||
# 3a. Extract Pauli error for single-qubit gates. | ||
rb_pauli_errors = _unpack_from_calibration('single_qubit_rb_pauli_error_per_gate', calibration) | ||
gate_pauli_errors = { | ||
OpIdentifier(gate, q): pauli_err | ||
for q, pauli_err in rb_pauli_errors.items() | ||
for gate in SINGLE_QUBIT_GATES | ||
} | ||
|
||
# TODO: 3a. Extract Pauli error for two-qubit gates. | ||
|
||
# 4. Extract readout fidelity for all qubits. | ||
p00 = _unpack_from_calibration('single_qubit_p00_error', calibration) | ||
p11 = _unpack_from_calibration('single_qubit_p11_error', calibration) | ||
decay_constant_pauli = _rb_pauli_error_to_decay_constant(rb_pauli_error) | ||
|
||
decay_constant_average = _rb_average_error_to_decay_constant(rb_average_error) | ||
|
||
if validate: # Will throw error if metrics aren't compatible | ||
if not _within_tolerance(decay_constant_pauli, decay_constant_average, tolerance): | ||
raise ValueError( | ||
f'Decay constant from RB Pauli error: {decay_constant_pauli}, ' | ||
f'decay constant from RB Average error: {decay_constant_average}. ' | ||
'If validation is disabled, RB Pauli error will be used.' | ||
) | ||
decay_constant_from_xeb = _xeb_fidelity_to_decay_constant(xeb_fidelity) | ||
if not _within_tolerance(decay_constant_from_xeb, decay_constant_pauli, tolerance): | ||
raise ValueError( | ||
f'Decay constant from RB Pauli error: {decay_constant_pauli}, ' | ||
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. ' | ||
'If validation is disabled, RB Pauli error will be used.' | ||
) | ||
if not _within_tolerance(decay_constant_from_xeb, decay_constant_average, tolerance): | ||
raise ValueError( | ||
f'Decay constant from RB Average error: {decay_constant_average}, ' | ||
f'decay constant from XEB Fidelity: {decay_constant_from_xeb}. ' | ||
'If validation is disabled, XEB Fidelity will be used.' | ||
) | ||
|
||
if decay_constant_pauli is not None: # can't define both decay constant and xeb | ||
return NoiseProperties( | ||
t1_ns=t1_nanos, decay_constant=decay_constant_pauli, p00=p00, p11=p11 | ||
) | ||
if xeb_fidelity is not None: | ||
return NoiseProperties(t1_ns=t1_nanos, xeb_fidelity=xeb_fidelity, p00=p00, p11=p11) | ||
return NoiseProperties(t1_ns=t1_nanos, decay_constant=decay_constant_average, p00=p00, p11=p11) | ||
ro_fidelities = { | ||
q: np.array([p00.get(q, 0), p11.get(q, 0)]) for q in set(p00.keys()) | set(p11.keys()) | ||
} | ||
|
||
# TODO: include entangling errors. | ||
|
||
return NoiseProperties( | ||
gate_times_ns=DEFAULT_GATE_NS, | ||
T1_ns=T1_ns, | ||
Tphi_ns=Tphi_ns, | ||
ro_fidelities=ro_fidelities, | ||
gate_pauli_errors=gate_pauli_errors, | ||
) |
Oops, something went wrong.