From afbd6f1255e4751996bbb3a326b671f8504a4386 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 1 Nov 2021 09:09:34 -0700 Subject: [PATCH 01/13] NoiseProperties refactor --- cirq-core/cirq/devices/__init__.py | 7 + cirq-core/cirq/devices/noise_properties.py | 738 +++++++++++++----- .../cirq/devices/noise_properties_test.py | 601 ++++++++------ cirq-core/cirq/devices/noise_utils.py | 90 +++ 4 files changed, 970 insertions(+), 466 deletions(-) create mode 100644 cirq-core/cirq/devices/noise_utils.py diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 1060f7aea0e..b0d02e0d2f5 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -39,6 +39,13 @@ ConstantQubitNoiseModel, ) +from cirq.devices.noise_properties import ( + FSimEntanglingAngles, + FSimPhaseAngles, + NoiseProperties, + NoiseModelFromNoiseProperties, +) + from cirq.devices.named_topologies import ( NamedTopology, draw_gridlike, diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index d2d3a39944c..3f3a1736ee0 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,8 +1,9 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice import warnings -from typing import Sequence, TYPE_CHECKING, List -from itertools import product -from cirq import ops, protocols, devices +from dataclasses import dataclass, field +from scipy.linalg import expm +from typing import Any, Dict, Iterable, Optional, Sequence, TYPE_CHECKING, List, Tuple, Union +from cirq import ops, protocols, devices, qis import numpy as np if TYPE_CHECKING: @@ -10,218 +11,519 @@ import cirq +OpIdentifierType = Union[ + Tuple[type], + Tuple[type, 'cirq.Qid'], + Tuple[type, 'cirq.Qid', 'cirq.Qid'], +] + + +VIRTUAL_Z_GATE = ops.ZPowGate +MW_GATE = ops.PhasedXZGate +MEASURE_GATE = ops.MeasurementGate +RESET_GATE = ops.ResetChannel +WAIT_GATE = ops.WaitGate +SINGLE_QUBIT_GATES = {VIRTUAL_Z_GATE, MW_GATE, MEASURE_GATE, RESET_GATE} + +FSIM_GATE = ops.FSimGate +ISWAP_GATE = ops.ISwapPowGate +CZ_GATE = ops.CZPowGate +SYMMETRIC_TWO_QUBIT_GATES = {ISWAP_GATE, FSIM_GATE, CZ_GATE} +ASYMMETRIC_TWO_QUBIT_GATES = set() +TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES + +_EXPECTED_GATES = SINGLE_QUBIT_GATES | TWO_QUBIT_GATES + + +_REAL_GATE_TAG = 'real_gate' + + +# TODO: merge into single "FSimAngles" dataclass +@dataclass(frozen=True) +class FSimEntanglingAngles: + """Angles of an fsim gate that entangle. + + Parameters match cirq.PhasedFSimGate + """ + + theta: float + phi: float + + +@dataclass(frozen=True) +class FSimPhaseAngles: + """Angles of an fsim gate corresponding to z phases. + + Parameters match cirq.PhasedFSimGate + """ + + zeta: float + chi: float + gamma: float + + +@dataclass(frozen=True) class NoiseProperties: - def __init__( - self, - *, - t1_ns: float = None, - decay_constant: float = None, - xeb_fidelity: float = None, - pauli_error: float = None, - p00: float = None, - p11: float = None, - ) -> None: - """Creates a NoiseProperties object using the provided metrics. - - Only one of decay_constant, xeb_fidelity, and pauli_error should be specified. + gate_times_ns: Dict[str, float] + T1_ns: Dict['cirq.GridQubit', float] + Tphi_ns: Dict['cirq.GridQubit', float] + ro_fidelities: Dict['cirq.GridQubit', np.ndarray] + entangler_errors: Dict[OpIdentifierType, 'FSimEntanglingAngles'] + gate_pauli_errors: Dict[OpIdentifierType, float] + z_phase_errors: Optional[Dict[OpIdentifierType, 'FSimPhaseAngles']] = None + + _qubits: Optional[List['cirq.GridQubit']] = field(init=False, default=None) + + def __attrs_post_init__(self): + t1_qubits = set(self.T1_ns) + tphi_qubits = set(self.Tphi_ns) + + if t1_qubits != tphi_qubits: + raise ValueError(f'Keys specified for T1 and Tphi are not' f' identical.') + + # validate two qubit gate errors. + self._validate_symmetric_errors('entangler_errors') + self._validate_symmetric_errors('gate_pauli_errors') + self._validate_symmetric_errors('z_phase_errors') + + def _validate_symmetric_errors(self, field_name: str) -> None: + gate_error_dict = getattr(self, field_name) + if gate_error_dict is None: + return # some fields are optional + for op_id, val in gate_error_dict.items(): + gate_type, qpair = op_id[0], op_id[1:] + if len(qpair) != 2: + # single qubit op_ids also present, or generic values are + # specified. Skip these cases + if len(qpair) > 2: + raise ValueError( + f'Found gate {gate_type} with {len(qpair)} qubits. ' + 'Symmetric errors can only apply to 2-qubit gates.' + ) + continue + + if gate_type not in SYMMETRIC_TWO_QUBIT_GATES: + if gate_type not in ASYMMETRIC_TWO_QUBIT_GATES: + raise ValueError( + f'Found gate {gate_type} which does not appear in the' + 'symmetric or asymmetric gate sets.' + ) + continue + op_id_swapped = (gate_type, *qpair[::-1]) + if op_id_swapped not in gate_error_dict: + raise ValueError( + f'Operation {op_id} of field {field_name} has ' + f'errors but its symmetric id ' + f'{op_id_swapped} does not.' + ) - Args: - t1_ns: t1 decay constant in ns - decay_constant: depolarization decay constant - xeb_fidelity: 2-qubit XEB Fidelity - pauli_error: total Pauli error - p00: probability of qubit initialized as zero being measured as zero - p11: probability of qubit initialized as one being measured as one + @property + def qubits(self) -> List['cirq.GridQubit']: + """Qubits for which we have data""" + if self._qubits == None: + self._qubits = sorted(self.T1_ns) + return self._qubits + + +def _left_mul(mat: np.ndarray) -> np.ndarray: + """Superoperator associated with left multiplication by a square matrix.""" + mat = np.asarray(mat) + if mat.shape[-1] != mat.shape[-2]: + raise ValueError( + f'_left_mul only accepts square matrices, but input matrix has shape {mat.shape}.' + ) + dim = mat.shape[-1] - Raises: - ValueError: if no metrics are specified - ValueError: if xeb fidelity, pauli error, p00, or p00 are less than 0 or greater than 1 - ValueError: if more than one of pauli error, xeb fidelity, or decay constant is specified - """ - if not any([t1_ns, decay_constant, xeb_fidelity, pauli_error, p00, p11]): - raise ValueError('At least one metric must be specified') + return np.kron(mat, np.eye(dim)) - for metric in [xeb_fidelity, pauli_error, p00, p11]: - if metric is not None and not 0.0 <= metric <= 1.0: - raise ValueError('xeb, pauli error, p00, and p11 must be between 0 and 1') - if ( - np.count_nonzero( - [metric is not None for metric in [xeb_fidelity, pauli_error, decay_constant]] - ) - > 1 - ): +def _right_mul(mat: np.ndarray) -> np.ndarray: + """Superoperator associated with right multiplication by a square matrix.""" + mat = np.asarray(mat) + if mat.shape[-1] != mat.shape[-2]: + raise ValueError( + f'_right_mul only accepts square matrices, but input matrix has shape {mat.shape}.' + ) + dim = mat.shape[-1] + + return np.kron(np.eye(dim), np.swapaxes(mat, -2, -1)) + + +def lindbladian(left_op: np.ndarray) -> np.ndarray: + """Superoperator representing a Lindbladian. + + The Lindbladian generated by a single operator A is the superoperator + L(\rho) = A \rho A^\dagger - 0.5 (A^\dagger A \rho + \rho A^\dagger A) + + Args: + left_op: The operator acting on the left in the Lindbladian (A above). + + Returns: + Superoperator corresponding to the Lindbladian. + """ + left_op = np.asarray(left_op) + right_op = left_op.conj().T + square = right_op @ left_op + out = _left_mul(left_op) @ _right_mul(right_op) + out -= 0.5 * (_left_mul(square) + _right_mul(square)) + return out + + +def decoherence_matrix( + cool_rate: float, dephase_rate: float, heat_rate: float = 0.0, dim: int = 2 +) -> np.ndarray: + """Construct a rate matrix associated with decay and dephasing. + + The units of the matrix match the units of the rates specified. + This matrix can be used to construct a ThermalChannel after rescaling + by an idling time (to make it dimensionless). + + Args: + cool_rate: Decay rate of the system, usually 1 / T_1 + dephase_rate: Static dephasing rate of the system, usually 1 / T_phi + heat_rate: Heating rate of the system (default 0). + dim: Number of energy levels to include (default 2). + + """ + # heating (related to a^dag) + rate_matrix = np.diag(np.arange(1, dim) * heat_rate, 1).T.astype(float) + # cooling (related to a) + rate_matrix += np.diag(np.arange(1, dim) * cool_rate, 1) + # dephasing (related to n=a^dag * a) + # we specify i^2 since we take the sqrt to get + # Lindblad op later. + rate_matrix += np.diag(dephase_rate * np.arange(dim) ** 2) + return rate_matrix + + +def _validate_rates(qubit_dims: Dict['cirq.Qid', int], rates: Dict['cirq.Qid', np.ndarray]) -> None: + """ + Check all rate matrices are square, and of appropriate dim. + We check rates are positive in the class validator. + """ + if set(qubit_dims) != set(rates): + raise ValueError('qubits for rates inconsistent with those through qubit_dims') + for q in rates: + if rates[q].shape != (qubit_dims[q], qubit_dims[q]): raise ValueError( - 'Only one of xeb fidelity, pauli error, or decay constant should be defined' + f'rate matrix invalid shape. ' + f'should be ({qubit_dims[q]}, {qubit_dims[q]}), ' + f'but got {rates[q].shape}' ) - self._t1_ns = t1_ns - self._p = decay_constant - self._p00 = p00 - self._p11 = p11 - if pauli_error is not None: - self._p = self.pauli_error_to_decay_constant(pauli_error) - elif xeb_fidelity is not None: - self._p = self.xeb_fidelity_to_decay_constant(xeb_fidelity) +@dataclass +class ThermalNoiseModel(devices.NoiseModel): + gate_durations_ns: Dict[OpIdentifierType, float] + rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] - @property - def decay_constant(self): - return self._p + def noisy_moment( + self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] + ) -> 'cirq.OP_TREE': + noise_ops: List['cirq.Channel'] = [] + moment_ns = 0 + for op in moment: + if _REAL_GATE_TAG not in op.tags: + # Only real gates get noise applied. + return [moment] + op_data = (type(op.gate), *op.qubits) + op_duration: Optional[float] = None + for key, duration in self.gate_durations_ns.items(): + if not issubclass(op_data[0], key): + continue # gate type doesn't match + # TODO: remove assumption of same time across qubits + # if len(key) > 1 and op_data[:1] != key[:1]: + # continue # qubits don't match + op_duration = duration + break + if op_duration is None: + if not isinstance(op.gate, ops.WaitGate): + continue + # special case for wait gates if not predefined + op_duration = op.gate.duration.total_ns() + moment_ns = max(moment_ns, op_duration) + + for qubit in system_qubits: + rates = self.rate_matrix_GHz[qubit] * moment_ns + num_op = np.diag(np.sqrt(np.diag(rates))) + annihilation = np.sqrt(np.triu(rates, 1)) + creation = np.sqrt(np.triu(rates.T, 1)).T + # Lindbladian with three Lindblad ops for the three processes + # Note: 'time' parameter already specified implicitly through rates + op_a = lindbladian(annihilation) + op_c = lindbladian(creation) + op_n = lindbladian(num_op) + L = lindbladian(annihilation) + lindbladian(creation) + 2 * lindbladian(num_op) + superop = expm(L.real) + kraus_ops = qis.superoperator_to_kraus(superop) + noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit)) + return [moment, ops.Moment(noise_ops)] + + +def finite_temp_model( + qubit_dims: Dict['cirq.Qid', int], + gate_durations_ns: Dict[OpIdentifierType, float], + cool_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, + dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, +) -> ThermalNoiseModel: + """Construct a ThermalNoiseModel data object. + + Required Args: + qubit_dims: Dimension for all qubits in the system. + Note, if specifying dimensions other than 2, + LeakageData (of appropriate dims) must be added to the + circuit (either before or after the ThermalNoiseModel). + Optional Args: + cool_rate_GHz: single number (units GHz) specifying cooling rate, + either per qubit, or global value for all. + Given a rate gc, the Lindblad op will be sqrt(gc)*a + so that the cooling Lindbldian is + gc(a • a^dag - 0.5{n, •}) + This number is equivalent to 1/T1 + dephase_rate_GHz: single number (units GHz) specifying dephasing rate, + either per qubit, or global value for all. + Given a rate gd, Lindblad op will be sqrt(2*gd)*n where + n = a^dag * a, so that the dephasing Lindbldian is + 2 * gd * (n • n - 0.5{n^2, •}). + This number is equivalent to 1/Tphi. + + Returns: + The ThermalNoiseModel with specified parameters. + """ + + qubits = set(qubit_dims) + rate_dict = {} + + def _as_rate_dict(rate_or_dict: Optional[Union[float, dict]]) -> dict: + # Convert float or None input into dictionary form. Make sure no + # qubits are missing from dictionary input. + if np.isscalar(rate_or_dict): + return {qb: rate_or_dict for qb in qubits} + elif rate_or_dict is None: + return {qb: 0.0 for qb in qubits} + else: + out = rate_or_dict.copy() + for qb in qubits: + if qb not in rate_or_dict: + out[qb] = 0.0 + return out - @property - def p00(self): - return self._p00 + heat_rate_GHz = _as_rate_dict(None) + cool_rate_GHz = _as_rate_dict(cool_rate_GHz) + dephase_rate_GHz = _as_rate_dict(dephase_rate_GHz) - @property - def p11(self): - return self._p11 + for q, dim in qubit_dims.items(): + gamma_h = heat_rate_GHz[q] + gamma_c = cool_rate_GHz[q] + gamma_phi = dephase_rate_GHz[q] - @property - def pauli_error(self): - return self.decay_constant_to_pauli_error() + rate_dict[q] = decoherence_matrix(gamma_c, gamma_phi, gamma_h, dim) - @property - def t1_ns(self): - return self._t1_ns + _validate_rates(qubit_dims, rate_dict) - @property - def xeb(self): - return self.decay_constant_to_xeb_fidelity() + return ThermalNoiseModel(gate_durations_ns, rate_dict) - def decay_constant_to_xeb_fidelity(self, num_qubits: int = 2): - """Calculates the XEB fidelity from the depolarization decay constant. - Args: - num_qubits: number of qubits - """ - if self._p is not None: - N = 2 ** num_qubits - return 1 - ((1 - self._p) * (1 - 1 / N)) - return None +def decoherence_pauli_error(T1_ns: float, Tphi_ns: float, gate_time_ns: float) -> float: + """The component of Pauli error caused by decoherence.""" + Gamma2 = (1 / (2 * T1_ns)) + 1 / Tphi_ns - def decay_constant_to_pauli_error(self, num_qubits: int = 1): - """Calculates pauli error from the depolarization decay constant. - Args: - num_qubits: number of qubits - """ - if self._p is not None: - N = 2 ** num_qubits - return (1 - self._p) * (1 - 1 / N / N) - return None + exp1 = np.exp(-gate_time_ns / T1_ns) + exp2 = np.exp(-gate_time_ns * Gamma2) + px = 0.25 * (1 - exp1) + py = px + pz = 0.5 * (1 - exp2) - px + return px + py + pz - def pauli_error_to_decay_constant(self, pauli_error: float, num_qubits: int = 1): - """Calculates depolarization decay constant from pauli error. - Args: - pauli_error: The pauli error - num_qubits: Number of qubits - """ - N = 2 ** num_qubits - return 1 - (pauli_error / (1 - 1 / N / N)) +def shapes_broadcastable(shape_0: Tuple[int, ...], shape_1: Tuple[int, ...]) -> bool: + return all((m == n) or (m == 1) or (n == 1) for m, n in zip(shape_0[::-1], shape_1[::-1])) - def xeb_fidelity_to_decay_constant(self, xeb_fidelity: float, num_qubits: int = 2): - """Calculates the depolarization decay constant from the XEB noise_properties. - Args: - xeb_fidelity: The XEB noise_properties - num_qubits: Number of qubits - """ - N = 2 ** num_qubits - return 1 - (1 - xeb_fidelity) / (1 - 1 / N) +def unitary_entanglement_fidelity(U_actual: np.ndarray, U_ideal: np.ndarray) -> np.ndarray: + """Entanglement fidelity between two unitaries. - def pauli_error_from_t1(self, t: float, t1_ns: float): - """Calculates the pauli error from amplitude damping. - Unlike the other methods, this computes a specific case (over time t). + For unitary matrices, this is related to average unitary fidelity F by: - Args: - t: the duration of the gate - t1_ns: the t1 decay constant in ns - """ - t2 = 2 * t1_ns - return (1 - np.exp(-t / t2)) / 2 + (1 - np.exp(-t / t1_ns)) / 4 + :math:`F = \frac{F_e d + 1}{d + 1}` - def pauli_error_from_depolarization(self, t: float): - """Calculates the amount of pauli error from depolarization. - Unlike the other methods, this computes a specific case (over time t). + where d is the matrix dimension. - If pauli error from t1 decay is more than total pauli error, just return the pauli error. + Args: + U_actual : Matrix whose fidelity to U_ideal will be computed. This may + be a non-unitary matrix, i.e. the projection of a larger unitary + matrix into the computational subspace. + U_ideal : Unitary matrix to which U_actual will be compared. - Args: - t: the duration of the gate - """ - if self.t1_ns is not None: - pauli_error_from_t1 = self.pauli_error_from_t1(t, self.t1_ns) - if self.pauli_error >= pauli_error_from_t1: - return self.pauli_error - pauli_error_from_t1 + Both arguments may be vectorized, in that their shapes may be of the form + (...,M,M) (as long as both shapes can be broadcast together). + + Returns: + The entanglement fidelity between the two unitaries. For inputs with + shape (...,M,M), the output has shape (...). + """ + U_actual = np.asarray(U_actual) + U_ideal = np.asarray(U_ideal) + if not shapes_broadcastable(U_actual.shape, U_ideal.shape): + raise ValueError('Input arrays do not have matching shapes.') + if U_actual.shape[-1] != U_actual.shape[-2]: + raise ValueError("Inputs' trailing dimensions must be equal (square).") + + dim = U_ideal.shape[-1] + + prod_trace = np.einsum('...ba,...ba->...', U_actual.conj(), U_ideal) + + return np.real((np.abs(prod_trace)) / dim) ** 2 + + +@dataclass +class GenericNoiseModel(devices.NoiseModel): + ops_added: Dict[OpIdentifierType, 'cirq.Operation'] = field(default_factory=dict) + prepend: bool = False + context: Any = None + + def noisy_moment( + self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] + ) -> 'cirq.OP_TREE': + noise_ops = [] + for op in moment: + if _REAL_GATE_TAG not in op.tags: + # Only real gates get noise applied. + return [moment] + # TODO: virtual-tag ops? + op_data = (type(op.gate), *op.qubits) + if op_data in self.ops_added: + noise_ops.append(self.ops_added[op_data]) + elif op_data[:1] in self.ops_added: + noise_ops.append(self.ops_added[op_data[:1]]) else: - warnings.warn( - "Pauli error from T1 decay is greater than total Pauli error", RuntimeWarning - ) - return self.pauli_error + continue + if not noise_ops: + return [moment] + if self.prepend: + return [ops.Moment(noise_ops), moment] + return [moment, ops.Moment(noise_ops)] - def average_error(self, num_qubits: int = 1): - """Calculates the average error from the depolarization decay constant. - Args: - num_qubits: the number of qubits - """ - if self._p is not None: - N = 2 ** num_qubits - return (1 - self._p) * (1 - 1 / N) - return None - - -def get_duration_ns(gate): - # Gate durations based on sycamore durations. - # TODO: pull the gate durations from cirq_google - # or allow users to pass them in - if isinstance(gate, ops.FSimGate): - theta, _ = gate._value_equality_values_() - if np.abs(theta) % (np.pi / 2) == 0: - return 12.0 - return 32.0 - elif isinstance(gate, ops.ISwapPowGate): - return 32.0 - elif isinstance(gate, ops.ZPowGate): - return 0.0 - elif isinstance(gate, ops.MeasurementGate): - return 4000.0 - elif isinstance(gate, ops.WaitGate): - return gate.duration.total_nanos() - return 25.0 - - -def _apply_readout_noise(p00, p11, moments, measurement_qubits): - if p00 is None: - p = 1.0 - gamma = p11 - elif p11 is None: - p = 0.0 - gamma = p00 +def build_noise_models(data: NoiseProperties) -> List['cirq.NoiseModel']: + """Construct all NoiseModels associated with NoiseProperties.""" + noise_models = [] + + if set(data.T1_ns) != set(data.Tphi_ns): + raise ValueError( + f'T1 data has qubits {set(data.T1_ns)}, but Tphi has qubits {set(data.Tphi_ns)}.' + ) + if data.T1_ns: # level 1 sophistication + noise_models.append( + finite_temp_model( + {q: 2 for q in data.T1_ns}, + data.gate_times_ns, + cool_rate_GHz={q: 1 / T1 for q, T1 in data.T1_ns.items()}, + dephase_rate_GHz={q: 1 / Tp for q, Tp in data.Tphi_ns.items()}, + ) + ) + + # TODO: move to cirq-google subclass + # entangling gate coherent errors + if data.z_phase_errors is not None: + + def gate_error(key: Tuple[type, 'cirq.GridQubit', 'cirq.GridQubit']) -> 'cirq.Operation': + theta = data.entangler_errors[key].theta + phi = data.entangler_errors[key].phi + z_error = data.z_phase_errors.get(key, FSimPhaseAngles(0, 0, 0)) + return ops.PhasedFSimGate( + theta=theta, phi=phi, zeta=z_error.zeta, gamma=z_error.gamma, chi=z_error.chi + ).on(*key[1:]) + + entangler_errors = {k: gate_error(k) for k in data.entangler_errors} else: - p = p11 / (p00 + p11) - gamma = p11 / p - moments.append( - ops.Moment( - ops.GeneralizedAmplitudeDampingChannel(p=p, gamma=gamma)(q) for q in measurement_qubits + # only entangling parameter errors + entangler_errors = { + (gate_type, q0, q1): ops.FSimGate(theta=v.theta, phi=v.phi).on(q0, q1) + for (gate_type, q0, q1), v in data.entangler_errors.items() + } + if entangler_errors: # level 1 sophistication + noise_models.append(GenericNoiseModel(ops_added=entangler_errors)) + + # subtract the entangler and decoherence errors from the expected + # pauli errors. The remainder is added as depolarizing error after each + # gate. + + gate_types = set(k[0] for k in data.gate_pauli_errors) + if not gate_types.issubset(_EXPECTED_GATES): + raise ValueError( + 'Some gates are not in the supported set.' + f'\nGates: {gate_types}\nSupported: {_EXPECTED_GATES}' ) - ) + added_pauli_errors = {} + + for op_id, p_error in data.gate_pauli_errors.items(): + gate_type = op_id[0] + time_ns = float(data.gate_times_ns[gate_type]) + if gate_type in SINGLE_QUBIT_GATES: + if gate_type is MEASURE_GATE: + # Non-measurement error can be ignored on measurement gates. + continue + if len(op_id) != 2: + raise ValueError( + f'Gate {gate_type} only takes one qubit, but {len(op_id) - 1} were given.' + ) + q0 = op_id[1] + # add decoherence error + if q0 in data.T1_ns: + rp_0 = decoherence_pauli_error(data.T1_ns[q0], data.Tphi_ns[q0], time_ns) + p_error -= rp_0 -def _apply_depol_noise(pauli_error, moments, system_qubits): + else: + # this must be a 2-qubit gate + if gate_type not in TWO_QUBIT_GATES: + raise ValueError(f'Gate {gate_type} is not in the supported gate list.') + if len(op_id) != 3: + raise ValueError( + f'Gate {gate_type} takes two qubits, but {len(op_id) - 1} were given.' + ) + # add decoherence error + q0, q1 = op_id[1:] + if q0 in data.T1_ns: + rp_0 = decoherence_pauli_error(data.T1_ns[q0], data.Tphi_ns[q0], time_ns) + else: + rp_0 = 0 + if q1 in data.T1_ns: + rp_1 = decoherence_pauli_error(data.T1_ns[q1], data.Tphi_ns[q1], time_ns) + else: + rp_1 = 0 + # add entangling angle error + if op_id in entangler_errors: + unitary_err = protocols.unitary(entangler_errors[op_id]) + fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) + rp_fsim = 1 - fid + else: + rp_fsim = 0 - _sq_inds = np.arange(4) - pauli_inds = np.array(list(product(_sq_inds, repeat=1))) - num_inds = len(pauli_inds) - p_other = pauli_error / (num_inds - 1) # probability of X, Y, Z gates - moments.append(ops.Moment(ops.depolarize(p_other)(q) for q in system_qubits)) + p_error -= rp_0 + rp_1 + rp_fsim + if p_error > 0: + added_pauli_errors[op_id] = ops.depolarize(p_error, len(op_id[1:])).on(*op_id[1:]) -def _apply_amplitude_damp_noise(duration, t1, moments, system_qubits): - moments.append( - ops.Moment(ops.amplitude_damp(1 - np.exp(-duration / t1)).on_each(system_qubits)) - ) + # This adds per-qubit pauli error after ops on those qubits. + noise_models.append(GenericNoiseModel(ops_added=added_pauli_errors)) + + # This adds per-qubit measurement error BEFORE measurement on those qubits. + if data.ro_fidelities: + added_measure_errors: Dict[OpIdentifierType, 'cirq.Operation'] = {} + for qubit in data.ro_fidelities: + op_id = ('measure', qubit) + p_00, p_11 = data.ro_fidelities[qubit] + p = p_11 / (p_00 + p_11) + gamma = p_11 / p + added_measure_errors[(ops.MeasurementGate, qubit)] = ops.generalized_amplitude_damp( + p, gamma + ).on(qubit) + + noise_models.append(GenericNoiseModel(ops_added=added_measure_errors, prepend=True)) + + return noise_models class NoiseModelFromNoiseProperties(devices.NoiseModel): @@ -239,32 +541,54 @@ def __init__(self, noise_properties: NoiseProperties) -> None: else: raise ValueError('A NoiseProperties object must be specified') - def noisy_moment( - self, moment: ops.Moment, system_qubits: Sequence['cirq.Qid'] - ) -> 'cirq.OP_TREE': - moments: List[ops.Moment] = [] - - if any( - [protocols.is_measurement(op.gate) for op in moment.operations] - ): # Add readout error before measurement gate - p00 = self._noise_properties.p00 - p11 = self._noise_properties.p11 - measurement_qubits = [ - list(op.qubits)[0] for op in moment.operations if protocols.is_measurement(op.gate) - ] - if p00 is not None or p11 is not None: - _apply_readout_noise(p00, p11, moments, measurement_qubits) - moments.append(moment) - else: - moments.append(moment) - if self._noise_properties.pauli_error is not None: # Add depolarization error# - duration = max([get_duration_ns(op.gate) for op in moment.operations]) - pauli_error = self._noise_properties.pauli_error_from_depolarization(duration) - _apply_depol_noise(pauli_error, moments, system_qubits) - - if self._noise_properties.t1_ns is not None: # Add amplitude damping noise - duration = max([get_duration_ns(op.gate) for op in moment.operations]) - _apply_amplitude_damp_noise( - duration, self._noise_properties.t1_ns, moments, system_qubits - ) - return moments + self.noise_models = build_noise_models(self._noise_properties) + + def noisy_moments( + self, moments: Iterable['cirq.Moment'], system_qubits: Sequence['cirq.Qid'] + ) -> Sequence['cirq.OP_TREE']: + # Split multi-qubit measurements into single-qubit measurements. + # These will be recombined after noise is applied. + split_measure_moments = [] + multi_measurements = {} + for moment in moments: + split_measure_ops = [] + for op in moment: + if not protocols.is_measurement(op): + split_measure_ops.append(op) + continue + m_key = protocols.measurement_key_obj(op) + multi_measurements[m_key] = op + for q in op.qubits: + split_measure_ops.append(ops.measure(q, key=m_key)) + split_measure_moments.append(ops.Moment(split_measure_ops)) + + # TODO: HACK HACK HACK + from cirq import circuits + + split_measure_circuit = circuits.Circuit( + ops.Moment(op.with_tags(_REAL_GATE_TAG) for op in moment) + for moment in split_measure_moments + ) + + # TODO: virtual operation handling + # noisy_circuit = _split_software_and_real_moments(noisy_circuit) + + # Add actual noise. + noisy_circuit = split_measure_circuit.copy() + for model in self.noise_models: + noisy_circuit = noisy_circuit.with_noise(model) + + # Recombine measurements. + final_moments = [] + for moment in noisy_circuit: + combined_measure_ops = [] + restore_keys = set() + for op in moment: + if not protocols.is_measurement(op): + combined_measure_ops.append(op) + continue + restore_keys.add(protocols.measurement_key_obj(op)) + for key in restore_keys: + combined_measure_ops.append(multi_measurements[key]) + final_moments.append(ops.Moment(combined_measure_ops)) + return final_moments diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index eea1ed4d8d8..d41a146472b 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -1,298 +1,381 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice +from typing import List, Tuple +import numpy as np import pytest -import cirq -from cirq.testing import assert_equivalent_op_tree +import cirq, cirq_google + +# from cirq.testing import assert_equivalent_op_tree +from cirq.devices import noise_properties from cirq.devices.noise_properties import ( + FSimEntanglingAngles, + FSimPhaseAngles, NoiseProperties, NoiseModelFromNoiseProperties, - get_duration_ns, ) -import numpy as np - - -def test_invalid_arguments(): - with pytest.raises(ValueError, match='At least one metric must be specified'): - NoiseProperties() - - with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): - NoiseProperties(p00=1.2) - - with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): - NoiseProperties(pauli_error=-0.2) - - with pytest.raises( - ValueError, - match='Only one of xeb fidelity, pauli error, or decay constant should be defined', - ): - NoiseProperties(pauli_error=0.2, xeb_fidelity=0.5) - - with pytest.raises(ValueError, match='A NoiseProperties object must be specified'): - NoiseModelFromNoiseProperties(None) - - -def test_constructor_and_metrics(): - prop = NoiseProperties(p00=0.2) - assert prop.xeb is None - assert prop.pauli_error is None - assert prop.decay_constant is None - assert prop.average_error() is None - - # These and other metrics in the file are purely for testing and - # do not necessarily represent actual hardware behavior - xeb_fidelity = 0.95 - p00 = 0.1 - t1_ns = 200.0 - - # Create fidelity object with a defined XEB fidelity - from_xeb = NoiseProperties(xeb_fidelity=xeb_fidelity, p00=p00, t1_ns=t1_ns) - - assert from_xeb.p00 == p00 - assert from_xeb.p11 is None - assert from_xeb.t1_ns == t1_ns - assert from_xeb.xeb == xeb_fidelity - - # Create another fidelity object with the decay constant from the first one - decay_constant_from_xeb = from_xeb.decay_constant - - from_decay = NoiseProperties(decay_constant=decay_constant_from_xeb) - - # Check that their depolarization metrics match - assert np.isclose(xeb_fidelity, from_decay.xeb) - assert np.isclose(from_xeb.pauli_error, from_decay.pauli_error) - assert np.isclose(from_xeb.average_error(), from_decay.average_error()) - -def test_gate_durations(): - assert get_duration_ns(cirq.X) == 25.0 - assert get_duration_ns(cirq.FSimGate(3 * np.pi / 2, np.pi / 6)) == 12.0 - assert get_duration_ns(cirq.FSimGate(3 * np.pi / 4, np.pi / 6)) == 32.0 - assert get_duration_ns(cirq.ISWAP) == 32.0 - assert get_duration_ns(cirq.ZPowGate(exponent=5)) == 0.0 - assert get_duration_ns(cirq.MeasurementGate(1, 'a')) == 4000.0 - - wait_gate = cirq.WaitGate(cirq.Duration(nanos=4)) - assert get_duration_ns(wait_gate) == 4.0 - - assert get_duration_ns(cirq.CZ) == 25.0 +DEFAULT_GATE_NS = { + noise_properties.VIRTUAL_Z_GATE: 0.0, + noise_properties.MEASURE_GATE: 4000.0, + noise_properties.RESET_GATE: 250.0, + noise_properties.MW_GATE: 25.0, + noise_properties.FSIM_GATE: 32.0, + noise_properties.ISWAP_GATE: 32.0, + noise_properties.CZ_GATE: 32.0, + # WAIT_GATE is a special case. +} + +# These properties are for testing purposes only - they are not representative +# of device behavior for any existing hardware. +def sample_noise_properties( + system_qubits: List[cirq.Qid], qubit_pairs: List[Tuple[cirq.Qid, cirq.Qid]] +): + return NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q: 1e5 for q in system_qubits}, + Tphi_ns={q: 2e5 for q in system_qubits}, + ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, + entangler_errors={ + (g, q0, q1): FSimEntanglingAngles(0.01, 0.02) + for g in noise_properties.SYMMETRIC_TWO_QUBIT_GATES + for q0, q1 in qubit_pairs + }, + gate_pauli_errors={ + (g, q): 0.001 if g in noise_properties.SINGLE_QUBIT_GATES else 0.01 + for g in noise_properties.SINGLE_QUBIT_GATES + for q in system_qubits + }, + z_phase_errors={ + (g, q0, q1): FSimPhaseAngles(0.03, 0.04, 0.05) + for g in noise_properties.SYMMETRIC_TWO_QUBIT_GATES + for q0, q1 in qubit_pairs + }, + ) -def test_readout_error(): - p00 = 0.05 - p11 = 0.1 - p = p11 / (p00 + p11) - gamma = p11 / p +def test_model_from_props(): + system_qubits = cirq.GridQubit.rect(2, 2) + qubit_pairs = [ + (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), + (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), + (cirq.GridQubit(0, 1), cirq.GridQubit(0, 0)), + (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), + (cirq.GridQubit(1, 1), cirq.GridQubit(0, 1)), + (cirq.GridQubit(1, 1), cirq.GridQubit(1, 0)), + (cirq.GridQubit(1, 0), cirq.GridQubit(0, 0)), + (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), + ] + props = sample_noise_properties(system_qubits, qubit_pairs) + model = NoiseModelFromNoiseProperties(props) - # Create qubits and circuit - qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), + cirq.H(system_qubits[0]), + cirq.H(system_qubits[2]), + cirq.CNOT(*system_qubits[0:2]), + cirq.CNOT(*system_qubits[2:4]), + cirq.measure(*system_qubits, key='m'), ) + print('circuit') + print(circuit) + syc_circuit = cirq_google.optimized_for_sycamore(circuit) + print('syc_circuit') + print(syc_circuit) + noisy_circuit = syc_circuit.with_noise(model) + print('noisy_circuit') + print(noisy_circuit.moments) - # Create noise model from NoiseProperties object with specified noise - prop = NoiseProperties(p00=p00, p11=p11) - noise_model = NoiseModelFromNoiseProperties(prop) + assert False - noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - # Insert expected channels to circuit - expected_circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=p, gamma=gamma).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# TODO: rewrite tests - assert_equivalent_op_tree(expected_circuit, noisy_circuit) - # Create Noise Model with just p00 - prop_p00 = NoiseProperties(p00=p00) - noise_model_p00 = NoiseModelFromNoiseProperties(prop_p00) +# import numpy as np - noisy_circuit_p00 = cirq.Circuit(noise_model_p00.noisy_moments(circuit, qubits)) - # Insert expected channels to circuit - expected_circuit_p00 = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=0.0, gamma=p00).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# def test_invalid_arguments(): +# with pytest.raises(ValueError, match='At least one metric must be specified'): +# NoiseProperties() - assert_equivalent_op_tree(expected_circuit_p00, noisy_circuit_p00) +# with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): +# NoiseProperties(p00=1.2) - # Create Noise Model with just p11 - prop_p11 = NoiseProperties(p11=p11) - noise_model_p11 = NoiseModelFromNoiseProperties(prop_p11) +# with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): +# NoiseProperties(pauli_error=-0.2) - noisy_circuit_p11 = cirq.Circuit(noise_model_p11.noisy_moments(circuit, qubits)) +# with pytest.raises( +# ValueError, +# match='Only one of xeb fidelity, pauli error, or decay constant should be defined', +# ): +# NoiseProperties(pauli_error=0.2, xeb_fidelity=0.5) - # Insert expected channels to circuit - expected_circuit_p11 = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# with pytest.raises(ValueError, match='A NoiseProperties object must be specified'): +# NoiseModelFromNoiseProperties(None) - assert_equivalent_op_tree(expected_circuit_p11, noisy_circuit_p11) +# def test_constructor_and_metrics(): +# prop = NoiseProperties(p00=0.2) +# assert prop.xeb is None +# assert prop.pauli_error is None +# assert prop.decay_constant is None +# assert prop.average_error() is None -def test_depolarization_error(): - # Account for floating point errors - # Needs Cirq issue 3965 to be resolved - pauli_error = 0.09999999999999998 +# # These and other metrics in the file are purely for testing and +# # do not necessarily represent actual hardware behavior +# xeb_fidelity = 0.95 +# p00 = 0.1 +# t1_ns = 200.0 - # Create qubits and circuit - qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] - circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# # Create fidelity object with a defined XEB fidelity +# from_xeb = NoiseProperties(xeb_fidelity=xeb_fidelity, p00=p00, t1_ns=t1_ns) - # Create noise model from NoiseProperties object with specified noise - prop = NoiseProperties(pauli_error=pauli_error) - noise_model = NoiseModelFromNoiseProperties(prop) - - noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - - # Insert expected channels to circuit - expected_circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), - cirq.Moment([cirq.H(qubits[1])]), - cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), - ) - assert_equivalent_op_tree(expected_circuit, noisy_circuit) +# assert from_xeb.p00 == p00 +# assert from_xeb.p11 is None +# assert from_xeb.t1_ns == t1_ns +# assert from_xeb.xeb == xeb_fidelity +# # Create another fidelity object with the decay constant from the first one +# decay_constant_from_xeb = from_xeb.decay_constant -def test_ampl_damping_error(): - t1_ns = 200.0 +# from_decay = NoiseProperties(decay_constant=decay_constant_from_xeb) - # Create qubits and circuit - qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] - circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.FSimGate(5 * np.pi / 2, np.pi).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# # Check that their depolarization metrics match +# assert np.isclose(xeb_fidelity, from_decay.xeb) +# assert np.isclose(from_xeb.pauli_error, from_decay.pauli_error) +# assert np.isclose(from_xeb.average_error(), from_decay.average_error()) - # Create noise model from NoiseProperties object with specified noise - prop = NoiseProperties(t1_ns=t1_ns) - noise_model = NoiseModelFromNoiseProperties(prop) - - noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - - # Insert expected channels to circuit - expected_circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.FSimGate(np.pi / 2, np.pi).on_each(qubits)]), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-12.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), - ) - assert_equivalent_op_tree(expected_circuit, noisy_circuit) +# def test_gate_durations(): +# assert get_duration_ns(cirq.X) == 25.0 +# assert get_duration_ns(cirq.FSimGate(3 * np.pi / 2, np.pi / 6)) == 12.0 +# assert get_duration_ns(cirq.FSimGate(3 * np.pi / 4, np.pi / 6)) == 32.0 +# assert get_duration_ns(cirq.ISWAP) == 32.0 +# assert get_duration_ns(cirq.ZPowGate(exponent=5)) == 0.0 +# assert get_duration_ns(cirq.MeasurementGate(1, 'a')) == 4000.0 -def test_combined_error(): - # Helper function to calculate pauli error from depolarization - def pauli_error_from_depolarization(pauli_error, t1_ns, duration): - t2 = 2 * t1_ns - pauli_error_from_t1 = (1 - np.exp(-duration / t2)) / 2 + (1 - np.exp(-duration / t1_ns)) / 4 - if pauli_error >= pauli_error_from_t1: - return pauli_error - pauli_error_from_t1 - return pauli_error +# wait_gate = cirq.WaitGate(cirq.Duration(nanos=4)) +# assert get_duration_ns(wait_gate) == 4.0 - t1_ns = 2000.0 - p11 = 0.01 +# assert get_duration_ns(cirq.CZ) == 25.0 - # Account for floating point errors - # Needs Cirq issue 3965 to be resolved - pauli_error = 0.019999999999999962 - # Create qubits and circuit - qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] - circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment([cirq.measure(qubits[0], key='q0')]), - cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - ) +# def test_readout_error(): +# p00 = 0.05 +# p11 = 0.1 + +# p = p11 / (p00 + p11) +# gamma = p11 / p - # Create noise model from NoiseProperties object with specified noise - prop = NoiseProperties(t1_ns=t1_ns, p11=p11, pauli_error=pauli_error) - noise_model = NoiseModelFromNoiseProperties(prop) - - with pytest.warns( - RuntimeWarning, match='Pauli error from T1 decay is greater than total Pauli error' - ): - noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - - # Insert expected channels to circuit - expected_circuit = cirq.Circuit( - cirq.Moment([cirq.X(qubits[0])]), - cirq.Moment( - [ - cirq.depolarize( - pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 - ).on_each(qubits) - ] - ), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), - cirq.Moment( - [ - cirq.depolarize( - pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 - ).on_each(qubits) - ] - ), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on(qubits[0])]), - cirq.Moment([cirq.measure(qubits[0], key='q0')]), - cirq.Moment( - [ - cirq.depolarize( - pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 - ).on_each(qubits) - ] - ), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), - cirq.Moment( - [ - cirq.depolarize( - pauli_error_from_depolarization(pauli_error, t1_ns, 32.0) / 3 - ).on_each(qubits) - ] - ), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-32.0 / t1_ns)).on_each(qubits)]), - cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), - cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), - cirq.Moment( - [ - cirq.depolarize( - pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 - ).on_each(qubits) - ] - ), - cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), - ) - assert_equivalent_op_tree(expected_circuit, noisy_circuit) +# # Create qubits and circuit +# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] +# circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# # Create noise model from NoiseProperties object with specified noise +# prop = NoiseProperties(p00=p00, p11=p11) +# noise_model = NoiseModelFromNoiseProperties(prop) + +# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=p, gamma=gamma).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# assert_equivalent_op_tree(expected_circuit, noisy_circuit) + +# # Create Noise Model with just p00 +# prop_p00 = NoiseProperties(p00=p00) +# noise_model_p00 = NoiseModelFromNoiseProperties(prop_p00) + +# noisy_circuit_p00 = cirq.Circuit(noise_model_p00.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit_p00 = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=0.0, gamma=p00).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# assert_equivalent_op_tree(expected_circuit_p00, noisy_circuit_p00) + +# # Create Noise Model with just p11 +# prop_p11 = NoiseProperties(p11=p11) +# noise_model_p11 = NoiseModelFromNoiseProperties(prop_p11) + +# noisy_circuit_p11 = cirq.Circuit(noise_model_p11.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit_p11 = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# assert_equivalent_op_tree(expected_circuit_p11, noisy_circuit_p11) + + +# def test_depolarization_error(): +# # Account for floating point errors +# # Needs Cirq issue 3965 to be resolved +# pauli_error = 0.09999999999999998 + +# # Create qubits and circuit +# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] +# circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# # Create noise model from NoiseProperties object with specified noise +# prop = NoiseProperties(pauli_error=pauli_error) +# noise_model = NoiseModelFromNoiseProperties(prop) + +# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), +# cirq.Moment([cirq.H(qubits[1])]), +# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), +# ) +# assert_equivalent_op_tree(expected_circuit, noisy_circuit) + + +# def test_ampl_damping_error(): +# t1_ns = 200.0 + +# # Create qubits and circuit +# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] +# circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.FSimGate(5 * np.pi / 2, np.pi).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# # Create noise model from NoiseProperties object with specified noise +# prop = NoiseProperties(t1_ns=t1_ns) +# noise_model = NoiseModelFromNoiseProperties(prop) + +# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.FSimGate(np.pi / 2, np.pi).on_each(qubits)]), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-12.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), +# ) +# assert_equivalent_op_tree(expected_circuit, noisy_circuit) + + +# def test_combined_error(): +# # Helper function to calculate pauli error from depolarization +# def pauli_error_from_depolarization(pauli_error, t1_ns, duration): +# t2 = 2 * t1_ns +# pauli_error_from_t1 = (1 - np.exp(-duration / t2)) / 2 + (1 - np.exp(-duration / t1_ns)) / 4 +# if pauli_error >= pauli_error_from_t1: +# return pauli_error - pauli_error_from_t1 +# return pauli_error + +# t1_ns = 2000.0 +# p11 = 0.01 + +# # Account for floating point errors +# # Needs Cirq issue 3965 to be resolved +# pauli_error = 0.019999999999999962 + +# # Create qubits and circuit +# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] +# circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment([cirq.measure(qubits[0], key='q0')]), +# cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# ) + +# # Create noise model from NoiseProperties object with specified noise +# prop = NoiseProperties(t1_ns=t1_ns, p11=p11, pauli_error=pauli_error) +# noise_model = NoiseModelFromNoiseProperties(prop) + +# with pytest.warns( +# RuntimeWarning, match='Pauli error from T1 decay is greater than total Pauli error' +# ): +# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) + +# # Insert expected channels to circuit +# expected_circuit = cirq.Circuit( +# cirq.Moment([cirq.X(qubits[0])]), +# cirq.Moment( +# [ +# cirq.depolarize( +# pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 +# ).on_each(qubits) +# ] +# ), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), +# cirq.Moment( +# [ +# cirq.depolarize( +# pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 +# ).on_each(qubits) +# ] +# ), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on(qubits[0])]), +# cirq.Moment([cirq.measure(qubits[0], key='q0')]), +# cirq.Moment( +# [ +# cirq.depolarize( +# pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 +# ).on_each(qubits) +# ] +# ), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), +# cirq.Moment( +# [ +# cirq.depolarize( +# pauli_error_from_depolarization(pauli_error, t1_ns, 32.0) / 3 +# ).on_each(qubits) +# ] +# ), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-32.0 / t1_ns)).on_each(qubits)]), +# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), +# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), +# cirq.Moment( +# [ +# cirq.depolarize( +# pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 +# ).on_each(qubits) +# ] +# ), +# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), +# ) +# assert_equivalent_op_tree(expected_circuit, noisy_circuit) diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py new file mode 100644 index 00000000000..025304dbc30 --- /dev/null +++ b/cirq-core/cirq/devices/noise_utils.py @@ -0,0 +1,90 @@ +import numpy as np +import warnings + +# TODO: expose all from top-level cirq +def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: + """Calculates the XEB fidelity from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant + num_qubits: Number of qubits + """ + N = 2 ** num_qubits + return 1 - ((1 - decay_constant) * (1 - 1 / N)) + + +def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> float: + """Calculates pauli error from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant + num_qubits: Number of qubits + """ + N = 2 ** num_qubits + return (1 - decay_constant) * (1 - 1 / N / N) + + +def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> float: + """Calculates depolarization decay constant from pauli error. + + Args: + pauli_error: The pauli error + num_qubits: Number of qubits + """ + N = 2 ** num_qubits + return 1 - (pauli_error / (1 - 1 / N / N)) + + +def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> float: + """Calculates the depolarization decay constant from XEB fidelity. + + Args: + xeb_fidelity: The XEB fidelity + num_qubits: Number of qubits + """ + N = 2 ** num_qubits + return 1 - (1 - xeb_fidelity) / (1 - 1 / N) + + +def pauli_error_from_t1(t: float, t1_ns: float) -> float: + """Calculates the pauli error from T1 decay constant. + + This computes error for a specific duration, `t`. + + Args: + t: The duration of the gate + t1_ns: The T1 decay constant in ns + """ + t2 = 2 * t1_ns + return (1 - np.exp(-t / t2)) / 2 + (1 - np.exp(-t / t1_ns)) / 4 + + +def pauli_error_from_depolarization(t: float, t1_ns: float, pauli_error: float = 0) -> float: + """Calculates the amount of pauli error from depolarization. + + This computes non-T1 error for a specific duration, `t`. If pauli error + from T1 decay is more than total pauli error, this returns zero; otherwise, + it returns the portion of pauli error not attributable to T1 error. + + Args: + t: The duration of the gate + t1_ns: The T1 decay constant in ns + pauli_error: The pauli error + """ + t1_pauli_error = pauli_error_from_t1(t, t1_ns) + if pauli_error >= t1_pauli_error: + return pauli_error - t1_pauli_error + else: + warnings.warn("Pauli error from T1 decay is greater than total Pauli error", RuntimeWarning) + return 0 + + +def average_error(decay_constant: float, num_qubits: int = 1) -> float: + """Calculates the average error from the depolarization decay constant. + + Args: + decay_constant: Depolarization decay constant + num_qubits: Number of qubits + """ + N = 2 ** num_qubits + return (1 - decay_constant) * (1 - 1 / N) From 341b7a35b2d9ec9c8fc101346e50e232fdb49d02 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 2 Nov 2021 09:10:06 -0700 Subject: [PATCH 02/13] Time to split cirq from cirq_google --- cirq-core/cirq/devices/noise_properties.py | 53 ++- .../cirq/devices/noise_properties_test.py | 358 ++++-------------- 2 files changed, 102 insertions(+), 309 deletions(-) diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 3f3a1736ee0..e841fe55f1b 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,5 +1,4 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -import warnings from dataclasses import dataclass, field from scipy.linalg import expm from typing import Any, Dict, Iterable, Optional, Sequence, TYPE_CHECKING, List, Tuple, Union @@ -18,12 +17,12 @@ ] -VIRTUAL_Z_GATE = ops.ZPowGate +Z_GATE = ops.ZPowGate MW_GATE = ops.PhasedXZGate MEASURE_GATE = ops.MeasurementGate RESET_GATE = ops.ResetChannel WAIT_GATE = ops.WaitGate -SINGLE_QUBIT_GATES = {VIRTUAL_Z_GATE, MW_GATE, MEASURE_GATE, RESET_GATE} +SINGLE_QUBIT_GATES = {Z_GATE, MW_GATE, MEASURE_GATE, RESET_GATE} FSIM_GATE = ops.FSimGate ISWAP_GATE = ops.ISwapPowGate @@ -35,7 +34,7 @@ _EXPECTED_GATES = SINGLE_QUBIT_GATES | TWO_QUBIT_GATES -_REAL_GATE_TAG = 'real_gate' +_PHYSICAL_GATE_TAG = 'physical_gate' # TODO: merge into single "FSimAngles" dataclass @@ -184,6 +183,8 @@ def decoherence_matrix( heat_rate: Heating rate of the system (default 0). dim: Number of energy levels to include (default 2). + Returns: + np.ndarray rate matrix for decay and dephasing. """ # heating (related to a^dag) rate_matrix = np.diag(np.arange(1, dim) * heat_rate, 1).T.astype(float) @@ -223,8 +224,9 @@ def noisy_moment( noise_ops: List['cirq.Channel'] = [] moment_ns = 0 for op in moment: - if _REAL_GATE_TAG not in op.tags: - # Only real gates get noise applied. + if _PHYSICAL_GATE_TAG not in op.tags: + # TODO: split virtual/non-virtual moments + # Only non-virtual gates get noise applied. return [moment] op_data = (type(op.gate), *op.qubits) op_duration: Optional[float] = None @@ -263,6 +265,7 @@ def noisy_moment( def finite_temp_model( qubit_dims: Dict['cirq.Qid', int], gate_durations_ns: Dict[OpIdentifierType, float], + heat_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, cool_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, ) -> ThermalNoiseModel: @@ -270,16 +273,20 @@ def finite_temp_model( Required Args: qubit_dims: Dimension for all qubits in the system. - Note, if specifying dimensions other than 2, - LeakageData (of appropriate dims) must be added to the - circuit (either before or after the ThermalNoiseModel). + Currently only supports dimension=2 (qubits, not qudits) Optional Args: + heat_rate_GHz: single number (units GHz) specifying heating rate, + either per qubit, or global value for all. + Given a rate gh, the Lindblad op will be sqrt(gh)*a^dag + (where a is annihilation), + so that the heating Lindbldian is + gh(a^dag • a - 0.5{a*a^dag, •}). cool_rate_GHz: single number (units GHz) specifying cooling rate, either per qubit, or global value for all. Given a rate gc, the Lindblad op will be sqrt(gc)*a so that the cooling Lindbldian is gc(a • a^dag - 0.5{n, •}) - This number is equivalent to 1/T1 + This number is equivalent to 1/T1. dephase_rate_GHz: single number (units GHz) specifying dephasing rate, either per qubit, or global value for all. Given a rate gd, Lindblad op will be sqrt(2*gd)*n where @@ -387,10 +394,9 @@ def noisy_moment( ) -> 'cirq.OP_TREE': noise_ops = [] for op in moment: - if _REAL_GATE_TAG not in op.tags: + if _PHYSICAL_GATE_TAG not in op.tags: # Only real gates get noise applied. return [moment] - # TODO: virtual-tag ops? op_data = (type(op.gate), *op.qubits) if op_data in self.ops_added: noise_ops.append(self.ops_added[op_data]) @@ -564,11 +570,26 @@ def noisy_moments( # TODO: HACK HACK HACK from cirq import circuits + # TODO: is this a Google-only feature? + from cirq_google import PhysicalZTag + + new_moments = [] + for moment in split_measure_moments: + virtual_ops = { + op for op in moment + if isinstance(op.gate, Z_GATE) and PhysicalZTag not in op.tags + } + physical_ops = [ + op.with_tags(_PHYSICAL_GATE_TAG) for op in moment + if op not in virtual_ops + ] + if virtual_ops: + new_moments.append(ops.Moment(virtual_ops)) + if physical_ops: + new_moments.append(ops.Moment(physical_ops)) + + split_measure_circuit = circuits.Circuit(new_moments) - split_measure_circuit = circuits.Circuit( - ops.Moment(op.with_tags(_REAL_GATE_TAG) for op in moment) - for moment in split_measure_moments - ) # TODO: virtual operation handling # noisy_circuit = _split_software_and_real_moments(noisy_circuit) diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index d41a146472b..02c6a9bc51d 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -5,23 +5,32 @@ import cirq, cirq_google # from cirq.testing import assert_equivalent_op_tree -from cirq.devices import noise_properties from cirq.devices.noise_properties import ( FSimEntanglingAngles, FSimPhaseAngles, NoiseProperties, NoiseModelFromNoiseProperties, + Z_GATE, + MEASURE_GATE, + RESET_GATE, + MW_GATE, + FSIM_GATE, + ISWAP_GATE, + CZ_GATE, + SYMMETRIC_TWO_QUBIT_GATES, + SINGLE_QUBIT_GATES, + TWO_QUBIT_GATES, ) DEFAULT_GATE_NS = { - noise_properties.VIRTUAL_Z_GATE: 0.0, - noise_properties.MEASURE_GATE: 4000.0, - noise_properties.RESET_GATE: 250.0, - noise_properties.MW_GATE: 25.0, - noise_properties.FSIM_GATE: 32.0, - noise_properties.ISWAP_GATE: 32.0, - noise_properties.CZ_GATE: 32.0, + Z_GATE: 25.0, + MEASURE_GATE: 4000.0, + RESET_GATE: 250.0, + MW_GATE: 25.0, + FSIM_GATE: 32.0, + ISWAP_GATE: 32.0, + CZ_GATE: 32.0, # WAIT_GATE is a special case. } @@ -37,17 +46,16 @@ def sample_noise_properties( ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, entangler_errors={ (g, q0, q1): FSimEntanglingAngles(0.01, 0.02) - for g in noise_properties.SYMMETRIC_TWO_QUBIT_GATES + for g in SYMMETRIC_TWO_QUBIT_GATES for q0, q1 in qubit_pairs }, gate_pauli_errors={ - (g, q): 0.001 if g in noise_properties.SINGLE_QUBIT_GATES else 0.01 - for g in noise_properties.SINGLE_QUBIT_GATES - for q in system_qubits + **{(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, + **{(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, }, z_phase_errors={ (g, q0, q1): FSimPhaseAngles(0.03, 0.04, 0.05) - for g in noise_properties.SYMMETRIC_TWO_QUBIT_GATES + for g in SYMMETRIC_TWO_QUBIT_GATES for q0, q1 in qubit_pairs }, ) @@ -83,299 +91,63 @@ def test_model_from_props(): noisy_circuit = syc_circuit.with_noise(model) print('noisy_circuit') print(noisy_circuit.moments) + print(cirq.kraus_to_choi(cirq.kraus(noisy_circuit.moments[4].operations[0]))) - assert False + # assert False # TODO: rewrite tests +# Things to test: +# - each possible native gate is converted properly, including: +# Z phase, microwave, [sqrt_]iswap, sycamore (cirq_google), CZ, measure +# - Z's are virtual unless tagged as physical (cirq_google.ops.PhysicalZTag) -# import numpy as np - - -# def test_invalid_arguments(): -# with pytest.raises(ValueError, match='At least one metric must be specified'): -# NoiseProperties() - -# with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): -# NoiseProperties(p00=1.2) - -# with pytest.raises(ValueError, match='xeb, pauli error, p00, and p11 must be between 0 and 1'): -# NoiseProperties(pauli_error=-0.2) - -# with pytest.raises( -# ValueError, -# match='Only one of xeb fidelity, pauli error, or decay constant should be defined', -# ): -# NoiseProperties(pauli_error=0.2, xeb_fidelity=0.5) - -# with pytest.raises(ValueError, match='A NoiseProperties object must be specified'): -# NoiseModelFromNoiseProperties(None) - - -# def test_constructor_and_metrics(): -# prop = NoiseProperties(p00=0.2) -# assert prop.xeb is None -# assert prop.pauli_error is None -# assert prop.decay_constant is None -# assert prop.average_error() is None - -# # These and other metrics in the file are purely for testing and -# # do not necessarily represent actual hardware behavior -# xeb_fidelity = 0.95 -# p00 = 0.1 -# t1_ns = 200.0 - -# # Create fidelity object with a defined XEB fidelity -# from_xeb = NoiseProperties(xeb_fidelity=xeb_fidelity, p00=p00, t1_ns=t1_ns) - -# assert from_xeb.p00 == p00 -# assert from_xeb.p11 is None -# assert from_xeb.t1_ns == t1_ns -# assert from_xeb.xeb == xeb_fidelity - -# # Create another fidelity object with the decay constant from the first one -# decay_constant_from_xeb = from_xeb.decay_constant - -# from_decay = NoiseProperties(decay_constant=decay_constant_from_xeb) - -# # Check that their depolarization metrics match -# assert np.isclose(xeb_fidelity, from_decay.xeb) -# assert np.isclose(from_xeb.pauli_error, from_decay.pauli_error) -# assert np.isclose(from_xeb.average_error(), from_decay.average_error()) - - -# def test_gate_durations(): -# assert get_duration_ns(cirq.X) == 25.0 -# assert get_duration_ns(cirq.FSimGate(3 * np.pi / 2, np.pi / 6)) == 12.0 -# assert get_duration_ns(cirq.FSimGate(3 * np.pi / 4, np.pi / 6)) == 32.0 -# assert get_duration_ns(cirq.ISWAP) == 32.0 -# assert get_duration_ns(cirq.ZPowGate(exponent=5)) == 0.0 -# assert get_duration_ns(cirq.MeasurementGate(1, 'a')) == 4000.0 - -# wait_gate = cirq.WaitGate(cirq.Duration(nanos=4)) -# assert get_duration_ns(wait_gate) == 4.0 - -# assert get_duration_ns(cirq.CZ) == 25.0 - - -# def test_readout_error(): -# p00 = 0.05 -# p11 = 0.1 - -# p = p11 / (p00 + p11) -# gamma = p11 / p - -# # Create qubits and circuit -# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] -# circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# # Create noise model from NoiseProperties object with specified noise -# prop = NoiseProperties(p00=p00, p11=p11) -# noise_model = NoiseModelFromNoiseProperties(prop) - -# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - -# # Insert expected channels to circuit -# expected_circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=p, gamma=gamma).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# assert_equivalent_op_tree(expected_circuit, noisy_circuit) - -# # Create Noise Model with just p00 -# prop_p00 = NoiseProperties(p00=p00) -# noise_model_p00 = NoiseModelFromNoiseProperties(prop_p00) - -# noisy_circuit_p00 = cirq.Circuit(noise_model_p00.noisy_moments(circuit, qubits)) - -# # Insert expected channels to circuit -# expected_circuit_p00 = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=0.0, gamma=p00).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# assert_equivalent_op_tree(expected_circuit_p00, noisy_circuit_p00) - -# # Create Noise Model with just p11 -# prop_p11 = NoiseProperties(p11=p11) -# noise_model_p11 = NoiseModelFromNoiseProperties(prop_p11) - -# noisy_circuit_p11 = cirq.Circuit(noise_model_p11.noisy_moments(circuit, qubits)) - -# # Insert expected channels to circuit -# expected_circuit_p11 = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# assert_equivalent_op_tree(expected_circuit_p11, noisy_circuit_p11) - - -# def test_depolarization_error(): -# # Account for floating point errors -# # Needs Cirq issue 3965 to be resolved -# pauli_error = 0.09999999999999998 - -# # Create qubits and circuit -# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] -# circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# # Create noise model from NoiseProperties object with specified noise -# prop = NoiseProperties(pauli_error=pauli_error) -# noise_model = NoiseModelFromNoiseProperties(prop) - -# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) - -# # Insert expected channels to circuit -# expected_circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), -# cirq.Moment([cirq.H(qubits[1])]), -# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# cirq.Moment([cirq.depolarize(pauli_error / 3).on_each(qubits)]), -# ) -# assert_equivalent_op_tree(expected_circuit, noisy_circuit) - - -# def test_ampl_damping_error(): -# t1_ns = 200.0 - -# # Create qubits and circuit -# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] -# circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.FSimGate(5 * np.pi / 2, np.pi).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) - -# # Create noise model from NoiseProperties object with specified noise -# prop = NoiseProperties(t1_ns=t1_ns) -# noise_model = NoiseModelFromNoiseProperties(prop) +def test_zphase_gates(): + # TODO: Includes cirq.Z, cirq.Z ** t + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + model = NoiseModelFromNoiseProperties(props) + circuit = cirq.Circuit(cirq.Z(q0)) + noisy_circuit = circuit.with_noise(model) + print(noisy_circuit.moments) + assert len(noisy_circuit.moments) == 2 + assert len(noisy_circuit.moments[1].operations) == 1 + noise_op = noisy_circuit.moments[1].operations[0] + assert isinstance(noise_op.gate, cirq.KrausChannel) + noise_choi = cirq.kraus_to_choi(cirq.kraus(noise_op)) + assert np.allclose( + noise_choi, + [ + [1, 0, 0, 9.99750031e-01], + [0, 2.49968753e-04, 0, 0], + [0, 0, 0, 0], + [9.99750031e-01, 0, 0, 9.99750031e-01], + ], + ) + assert False -# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) -# # Insert expected channels to circuit -# expected_circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.FSimGate(np.pi / 2, np.pi).on_each(qubits)]), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-12.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), -# ) -# assert_equivalent_op_tree(expected_circuit, noisy_circuit) +def test_physical_zphase_gates(): + # TODO: Includes cirq.Z, cirq.Z ** t with PhysicalZTag + assert False -# def test_combined_error(): -# # Helper function to calculate pauli error from depolarization -# def pauli_error_from_depolarization(pauli_error, t1_ns, duration): -# t2 = 2 * t1_ns -# pauli_error_from_t1 = (1 - np.exp(-duration / t2)) / 2 + (1 - np.exp(-duration / t1_ns)) / 4 -# if pauli_error >= pauli_error_from_t1: -# return pauli_error - pauli_error_from_t1 -# return pauli_error +def test_microwave_gates(): + # TODO: Includes cirq.X, cirq.Y + assert False -# t1_ns = 2000.0 -# p11 = 0.01 -# # Account for floating point errors -# # Needs Cirq issue 3965 to be resolved -# pauli_error = 0.019999999999999962 +def test_iswap_gates(): + # TODO: Includes cirq.ISWAP, cirq.ISWAP ** 0.5 + assert False -# # Create qubits and circuit -# qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] -# circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment([cirq.measure(qubits[0], key='q0')]), -# cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# ) -# # Create noise model from NoiseProperties object with specified noise -# prop = NoiseProperties(t1_ns=t1_ns, p11=p11, pauli_error=pauli_error) -# noise_model = NoiseModelFromNoiseProperties(prop) +def test_cz_gates(): + # TODO: includes cirq.CZ + assert False -# with pytest.warns( -# RuntimeWarning, match='Pauli error from T1 decay is greater than total Pauli error' -# ): -# noisy_circuit = cirq.Circuit(noise_model.noisy_moments(circuit, qubits)) -# # Insert expected channels to circuit -# expected_circuit = cirq.Circuit( -# cirq.Moment([cirq.X(qubits[0])]), -# cirq.Moment( -# [ -# cirq.depolarize( -# pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 -# ).on_each(qubits) -# ] -# ), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.CNOT(qubits[0], qubits[1])]), -# cirq.Moment( -# [ -# cirq.depolarize( -# pauli_error_from_depolarization(pauli_error, t1_ns, 25.0) / 3 -# ).on_each(qubits) -# ] -# ), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-25.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on(qubits[0])]), -# cirq.Moment([cirq.measure(qubits[0], key='q0')]), -# cirq.Moment( -# [ -# cirq.depolarize( -# pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 -# ).on_each(qubits) -# ] -# ), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.ISwapPowGate().on_each(qubits)]), -# cirq.Moment( -# [ -# cirq.depolarize( -# pauli_error_from_depolarization(pauli_error, t1_ns, 32.0) / 3 -# ).on_each(qubits) -# ] -# ), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-32.0 / t1_ns)).on_each(qubits)]), -# cirq.Moment([cirq.GeneralizedAmplitudeDampingChannel(p=1.0, gamma=p11).on_each(qubits)]), -# cirq.Moment([cirq.measure(qubits[0], key='q0'), cirq.measure(qubits[1], key='q1')]), -# cirq.Moment( -# [ -# cirq.depolarize( -# pauli_error_from_depolarization(pauli_error, t1_ns, 4000.0) / 3 -# ).on_each(qubits) -# ] -# ), -# cirq.Moment([cirq.amplitude_damp(1 - np.exp(-4000.0 / t1_ns)).on_each(qubits)]), -# ) -# assert_equivalent_op_tree(expected_circuit, noisy_circuit) +def test_measure_gates(): + # TODO: includes single- and multi-qubit measure + assert False From 016438102478c9351d20a66d0391d97472cb5323 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Wed, 3 Nov 2021 14:06:00 -0700 Subject: [PATCH 03/13] mypy mostly happy --- cirq-core/cirq/__init__.py | 2 + cirq-core/cirq/devices/__init__.py | 22 +- .../cirq/devices/insertion_noise_model.py | 52 ++ cirq-core/cirq/devices/noise_properties.py | 625 ++++-------------- .../cirq/devices/noise_properties_test.py | 57 +- cirq-core/cirq/devices/noise_utils.py | 90 +++ cirq-core/cirq/devices/thermal_noise_model.py | 220 ++++++ .../devices/google_noise_properties.py | 98 +++ .../devices/google_noise_properties_test.py | 89 +++ .../calibration_to_noise_properties.py | 17 +- 10 files changed, 744 insertions(+), 528 deletions(-) create mode 100644 cirq-core/cirq/devices/insertion_noise_model.py create mode 100644 cirq-core/cirq/devices/thermal_noise_model.py create mode 100644 cirq-google/cirq_google/devices/google_noise_properties.py create mode 100644 cirq-google/cirq_google/devices/google_noise_properties_test.py diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index 35b84ca21da..ec5a173a6ab 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -88,6 +88,8 @@ NO_NOISE, NOISE_MODEL_LIKE, NoiseModel, + NoiseProperties, + NoiseModelFromNoiseProperties, SymmetricalQidPair, UNCONSTRAINED_DEVICE, NamedTopology, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index b0d02e0d2f5..fbf5eab8f2b 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -40,8 +40,6 @@ ) from cirq.devices.noise_properties import ( - FSimEntanglingAngles, - FSimPhaseAngles, NoiseProperties, NoiseModelFromNoiseProperties, ) @@ -54,3 +52,23 @@ get_placements, draw_placements, ) + +from cirq.devices.insertion_noise_model import ( + InsertionNoiseModel, +) + +from cirq.devices.thermal_noise_model import ( + ThermalNoiseModel, +) + +from cirq.devices.noise_utils import ( + decay_constant_to_xeb_fidelity, + decay_constant_to_pauli_error, + pauli_error_to_decay_constant, + xeb_fidelity_to_decay_constant, + pauli_error_from_t1, + pauli_error_from_depolarization, + average_error, + decoherence_pauli_error, + unitary_entanglement_fidelity, +) diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py new file mode 100644 index 00000000000..34989f5692f --- /dev/null +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -0,0 +1,52 @@ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Dict, Sequence + +from cirq import devices, ops +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, +) + +if TYPE_CHECKING: + import cirq + + +@dataclass +class InsertionNoiseModel(devices.NoiseModel): + """Simple base noise model for inserting operations. + + Operations generated by this model for a given moment are all added into a + single "noise moment", which is added before or after the original moment + based on `prepend`. + + Args: + ops_added: a map of gate types (and optionally, qubits they act on) to + operations that should be added by the model. + prepend: whether to add the new moment before the current one. + require_physical_tag: whether to only apply noise to operations tagged + with PHYSICAL_GATE_TAG. + """ + ops_added: Dict[OpIdentifier, 'cirq.Operation'] = field(default_factory=dict) + prepend: bool = False + require_physical_tag: bool = True + + def noisy_moment( + self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] + ) -> 'cirq.OP_TREE': + noise_ops = [] + for op in moment: + if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: + # Only real gates get noise applied. + return [moment] + op_id = OpIdentifier(type(op.gate), *op.qubits) + if op_id in self.ops_added: + noise_ops.append(self.ops_added[op_id]) + elif op_id.gate_only() in self.ops_added: + noise_ops.append(self.ops_added[op_id.gate_only()]) + else: + continue + if not noise_ops: + return [moment] + if self.prepend: + return [ops.Moment(noise_ops), moment] + return [moment, ops.Moment(noise_ops)] diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index e841fe55f1b..962a843cac3 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,77 +1,35 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice from dataclasses import dataclass, field -from scipy.linalg import expm -from typing import Any, Dict, Iterable, Optional, Sequence, TYPE_CHECKING, List, Tuple, Union -from cirq import ops, protocols, devices, qis +from typing import Dict, Iterable, Optional, Sequence, TYPE_CHECKING, List, Set import numpy as np +from cirq import ops, protocols, devices +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, + decoherence_pauli_error, +) + if TYPE_CHECKING: - from typing import Iterable import cirq - -OpIdentifierType = Union[ - Tuple[type], - Tuple[type, 'cirq.Qid'], - Tuple[type, 'cirq.Qid', 'cirq.Qid'], -] - - -Z_GATE = ops.ZPowGate -MW_GATE = ops.PhasedXZGate -MEASURE_GATE = ops.MeasurementGate -RESET_GATE = ops.ResetChannel -WAIT_GATE = ops.WaitGate -SINGLE_QUBIT_GATES = {Z_GATE, MW_GATE, MEASURE_GATE, RESET_GATE} - -FSIM_GATE = ops.FSimGate -ISWAP_GATE = ops.ISwapPowGate -CZ_GATE = ops.CZPowGate -SYMMETRIC_TWO_QUBIT_GATES = {ISWAP_GATE, FSIM_GATE, CZ_GATE} -ASYMMETRIC_TWO_QUBIT_GATES = set() +SINGLE_QUBIT_GATES = {ops.ZPowGate, ops.PhasedXZGate, ops.MeasurementGate, ops.ResetChannel} +SYMMETRIC_TWO_QUBIT_GATES = {ops.FSimGate, ops.ISwapPowGate, ops.CZPowGate} +ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES - _EXPECTED_GATES = SINGLE_QUBIT_GATES | TWO_QUBIT_GATES -_PHYSICAL_GATE_TAG = 'physical_gate' - - -# TODO: merge into single "FSimAngles" dataclass -@dataclass(frozen=True) -class FSimEntanglingAngles: - """Angles of an fsim gate that entangle. - - Parameters match cirq.PhasedFSimGate - """ - - theta: float - phi: float - - -@dataclass(frozen=True) -class FSimPhaseAngles: - """Angles of an fsim gate corresponding to z phases. - - Parameters match cirq.PhasedFSimGate - """ - - zeta: float - chi: float - gamma: float - - -@dataclass(frozen=True) +@dataclass class NoiseProperties: - gate_times_ns: Dict[str, float] - T1_ns: Dict['cirq.GridQubit', float] - Tphi_ns: Dict['cirq.GridQubit', float] - ro_fidelities: Dict['cirq.GridQubit', np.ndarray] - entangler_errors: Dict[OpIdentifierType, 'FSimEntanglingAngles'] - gate_pauli_errors: Dict[OpIdentifierType, float] - z_phase_errors: Optional[Dict[OpIdentifierType, 'FSimPhaseAngles']] = None + gate_times_ns: Dict[type, float] + T1_ns: Dict['cirq.Qid', float] + Tphi_ns: Dict['cirq.Qid', float] + ro_fidelities: Dict['cirq.Qid', np.ndarray] + gate_pauli_errors: Dict[OpIdentifier, float] - _qubits: Optional[List['cirq.GridQubit']] = field(init=False, default=None) + _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) + _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) def __attrs_post_init__(self): t1_qubits = set(self.T1_ns) @@ -90,446 +48,139 @@ def _validate_symmetric_errors(self, field_name: str) -> None: if gate_error_dict is None: return # some fields are optional for op_id, val in gate_error_dict.items(): - gate_type, qpair = op_id[0], op_id[1:] - if len(qpair) != 2: + if len(op_id.qubits) != 2: # single qubit op_ids also present, or generic values are # specified. Skip these cases - if len(qpair) > 2: + if len(op_id.qubits) > 2: raise ValueError( - f'Found gate {gate_type} with {len(qpair)} qubits. ' + f'Found gate {op_id.gate} with {len(op_id.qubits)} qubits. ' 'Symmetric errors can only apply to 2-qubit gates.' ) continue - if gate_type not in SYMMETRIC_TWO_QUBIT_GATES: - if gate_type not in ASYMMETRIC_TWO_QUBIT_GATES: + if op_id.gate not in SYMMETRIC_TWO_QUBIT_GATES: + if op_id.gate not in ASYMMETRIC_TWO_QUBIT_GATES: raise ValueError( - f'Found gate {gate_type} which does not appear in the' + f'Found gate {op_id.gate} which does not appear in the' 'symmetric or asymmetric gate sets.' ) continue - op_id_swapped = (gate_type, *qpair[::-1]) + op_id_swapped = (op_id.gate, *op_id.qubits[::-1]) if op_id_swapped not in gate_error_dict: raise ValueError( f'Operation {op_id} of field {field_name} has ' - f'errors but its symmetric id ' - f'{op_id_swapped} does not.' + f'errors but its symmetric id {op_id_swapped} does not.' ) @property - def qubits(self) -> List['cirq.GridQubit']: + def qubits(self) -> List['cirq.Qid']: """Qubits for which we have data""" - if self._qubits == None: + if not self._qubits: self._qubits = sorted(self.T1_ns) return self._qubits + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: + """Returns the portion of Pauli error from depolarization. -def _left_mul(mat: np.ndarray) -> np.ndarray: - """Superoperator associated with left multiplication by a square matrix.""" - mat = np.asarray(mat) - if mat.shape[-1] != mat.shape[-2]: - raise ValueError( - f'_left_mul only accepts square matrices, but input matrix has shape {mat.shape}.' - ) - dim = mat.shape[-1] - - return np.kron(mat, np.eye(dim)) - - -def _right_mul(mat: np.ndarray) -> np.ndarray: - """Superoperator associated with right multiplication by a square matrix.""" - mat = np.asarray(mat) - if mat.shape[-1] != mat.shape[-2]: - raise ValueError( - f'_right_mul only accepts square matrices, but input matrix has shape {mat.shape}.' - ) - dim = mat.shape[-1] - - return np.kron(np.eye(dim), np.swapaxes(mat, -2, -1)) - - -def lindbladian(left_op: np.ndarray) -> np.ndarray: - """Superoperator representing a Lindbladian. - - The Lindbladian generated by a single operator A is the superoperator - L(\rho) = A \rho A^\dagger - 0.5 (A^\dagger A \rho + \rho A^\dagger A) - - Args: - left_op: The operator acting on the left in the Lindbladian (A above). - - Returns: - Superoperator corresponding to the Lindbladian. - """ - left_op = np.asarray(left_op) - right_op = left_op.conj().T - square = right_op @ left_op - out = _left_mul(left_op) @ _right_mul(right_op) - out -= 0.5 * (_left_mul(square) + _right_mul(square)) - return out - - -def decoherence_matrix( - cool_rate: float, dephase_rate: float, heat_rate: float = 0.0, dim: int = 2 -) -> np.ndarray: - """Construct a rate matrix associated with decay and dephasing. - - The units of the matrix match the units of the rates specified. - This matrix can be used to construct a ThermalChannel after rescaling - by an idling time (to make it dimensionless). - - Args: - cool_rate: Decay rate of the system, usually 1 / T_1 - dephase_rate: Static dephasing rate of the system, usually 1 / T_phi - heat_rate: Heating rate of the system (default 0). - dim: Number of energy levels to include (default 2). - - Returns: - np.ndarray rate matrix for decay and dephasing. - """ - # heating (related to a^dag) - rate_matrix = np.diag(np.arange(1, dim) * heat_rate, 1).T.astype(float) - # cooling (related to a) - rate_matrix += np.diag(np.arange(1, dim) * cool_rate, 1) - # dephasing (related to n=a^dag * a) - # we specify i^2 since we take the sqrt to get - # Lindblad op later. - rate_matrix += np.diag(dephase_rate * np.arange(dim) ** 2) - return rate_matrix - - -def _validate_rates(qubit_dims: Dict['cirq.Qid', int], rates: Dict['cirq.Qid', np.ndarray]) -> None: - """ - Check all rate matrices are square, and of appropriate dim. - We check rates are positive in the class validator. - """ - if set(qubit_dims) != set(rates): - raise ValueError('qubits for rates inconsistent with those through qubit_dims') - for q in rates: - if rates[q].shape != (qubit_dims[q], qubit_dims[q]): - raise ValueError( - f'rate matrix invalid shape. ' - f'should be ({qubit_dims[q]}, {qubit_dims[q]}), ' - f'but got {rates[q].shape}' - ) - - -@dataclass -class ThermalNoiseModel(devices.NoiseModel): - gate_durations_ns: Dict[OpIdentifierType, float] - rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] - - def noisy_moment( - self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] - ) -> 'cirq.OP_TREE': - noise_ops: List['cirq.Channel'] = [] - moment_ns = 0 - for op in moment: - if _PHYSICAL_GATE_TAG not in op.tags: - # TODO: split virtual/non-virtual moments - # Only non-virtual gates get noise applied. - return [moment] - op_data = (type(op.gate), *op.qubits) - op_duration: Optional[float] = None - for key, duration in self.gate_durations_ns.items(): - if not issubclass(op_data[0], key): - continue # gate type doesn't match - # TODO: remove assumption of same time across qubits - # if len(key) > 1 and op_data[:1] != key[:1]: - # continue # qubits don't match - op_duration = duration - break - if op_duration is None: - if not isinstance(op.gate, ops.WaitGate): + The result of this method is memoized. + """ + if self._depolarizing_error: + return self._depolarizing_error + + depol_errors = {} + for op_id, p_error in self.gate_pauli_errors.items(): + gate_type = op_id.gate + time_ns = float(self.gate_times_ns[gate_type]) + if gate_type in SINGLE_QUBIT_GATES: + if issubclass(gate_type, ops.MeasurementGate): + # Non-measurement error can be ignored on measurement gates. continue - # special case for wait gates if not predefined - op_duration = op.gate.duration.total_ns() - moment_ns = max(moment_ns, op_duration) - - for qubit in system_qubits: - rates = self.rate_matrix_GHz[qubit] * moment_ns - num_op = np.diag(np.sqrt(np.diag(rates))) - annihilation = np.sqrt(np.triu(rates, 1)) - creation = np.sqrt(np.triu(rates.T, 1)).T - # Lindbladian with three Lindblad ops for the three processes - # Note: 'time' parameter already specified implicitly through rates - op_a = lindbladian(annihilation) - op_c = lindbladian(creation) - op_n = lindbladian(num_op) - L = lindbladian(annihilation) + lindbladian(creation) + 2 * lindbladian(num_op) - superop = expm(L.real) - kraus_ops = qis.superoperator_to_kraus(superop) - noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit)) - return [moment, ops.Moment(noise_ops)] - - -def finite_temp_model( - qubit_dims: Dict['cirq.Qid', int], - gate_durations_ns: Dict[OpIdentifierType, float], - heat_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, - cool_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, - dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, -) -> ThermalNoiseModel: - """Construct a ThermalNoiseModel data object. - - Required Args: - qubit_dims: Dimension for all qubits in the system. - Currently only supports dimension=2 (qubits, not qudits) - Optional Args: - heat_rate_GHz: single number (units GHz) specifying heating rate, - either per qubit, or global value for all. - Given a rate gh, the Lindblad op will be sqrt(gh)*a^dag - (where a is annihilation), - so that the heating Lindbldian is - gh(a^dag • a - 0.5{a*a^dag, •}). - cool_rate_GHz: single number (units GHz) specifying cooling rate, - either per qubit, or global value for all. - Given a rate gc, the Lindblad op will be sqrt(gc)*a - so that the cooling Lindbldian is - gc(a • a^dag - 0.5{n, •}) - This number is equivalent to 1/T1. - dephase_rate_GHz: single number (units GHz) specifying dephasing rate, - either per qubit, or global value for all. - Given a rate gd, Lindblad op will be sqrt(2*gd)*n where - n = a^dag * a, so that the dephasing Lindbldian is - 2 * gd * (n • n - 0.5{n^2, •}). - This number is equivalent to 1/Tphi. - - Returns: - The ThermalNoiseModel with specified parameters. - """ - - qubits = set(qubit_dims) - rate_dict = {} - - def _as_rate_dict(rate_or_dict: Optional[Union[float, dict]]) -> dict: - # Convert float or None input into dictionary form. Make sure no - # qubits are missing from dictionary input. - if np.isscalar(rate_or_dict): - return {qb: rate_or_dict for qb in qubits} - elif rate_or_dict is None: - return {qb: 0.0 for qb in qubits} - else: - out = rate_or_dict.copy() - for qb in qubits: - if qb not in rate_or_dict: - out[qb] = 0.0 - return out - - heat_rate_GHz = _as_rate_dict(None) - cool_rate_GHz = _as_rate_dict(cool_rate_GHz) - dephase_rate_GHz = _as_rate_dict(dephase_rate_GHz) - - for q, dim in qubit_dims.items(): - gamma_h = heat_rate_GHz[q] - gamma_c = cool_rate_GHz[q] - gamma_phi = dephase_rate_GHz[q] - - rate_dict[q] = decoherence_matrix(gamma_c, gamma_phi, gamma_h, dim) - - _validate_rates(qubit_dims, rate_dict) - - return ThermalNoiseModel(gate_durations_ns, rate_dict) - - -def decoherence_pauli_error(T1_ns: float, Tphi_ns: float, gate_time_ns: float) -> float: - """The component of Pauli error caused by decoherence.""" - Gamma2 = (1 / (2 * T1_ns)) + 1 / Tphi_ns - - exp1 = np.exp(-gate_time_ns / T1_ns) - exp2 = np.exp(-gate_time_ns * Gamma2) - px = 0.25 * (1 - exp1) - py = px - pz = 0.5 * (1 - exp2) - px - return px + py + pz - - -def shapes_broadcastable(shape_0: Tuple[int, ...], shape_1: Tuple[int, ...]) -> bool: - return all((m == n) or (m == 1) or (n == 1) for m, n in zip(shape_0[::-1], shape_1[::-1])) - - -def unitary_entanglement_fidelity(U_actual: np.ndarray, U_ideal: np.ndarray) -> np.ndarray: - """Entanglement fidelity between two unitaries. - - For unitary matrices, this is related to average unitary fidelity F by: - - :math:`F = \frac{F_e d + 1}{d + 1}` - - where d is the matrix dimension. - - Args: - U_actual : Matrix whose fidelity to U_ideal will be computed. This may - be a non-unitary matrix, i.e. the projection of a larger unitary - matrix into the computational subspace. - U_ideal : Unitary matrix to which U_actual will be compared. - - Both arguments may be vectorized, in that their shapes may be of the form - (...,M,M) (as long as both shapes can be broadcast together). - - Returns: - The entanglement fidelity between the two unitaries. For inputs with - shape (...,M,M), the output has shape (...). - """ - U_actual = np.asarray(U_actual) - U_ideal = np.asarray(U_ideal) - if not shapes_broadcastable(U_actual.shape, U_ideal.shape): - raise ValueError('Input arrays do not have matching shapes.') - if U_actual.shape[-1] != U_actual.shape[-2]: - raise ValueError("Inputs' trailing dimensions must be equal (square).") - - dim = U_ideal.shape[-1] - - prod_trace = np.einsum('...ba,...ba->...', U_actual.conj(), U_ideal) - - return np.real((np.abs(prod_trace)) / dim) ** 2 - + if len(op_id.qubits) != 1: + raise ValueError( + f'Gate {gate_type} only takes one qubit, but {op_id.qubits} were given.' + ) + q0 = op_id.qubits[0] + # Subtract decoherence error. + if q0 in self.T1_ns: + rp_0 = decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) + p_error -= rp_0 -@dataclass -class GenericNoiseModel(devices.NoiseModel): - ops_added: Dict[OpIdentifierType, 'cirq.Operation'] = field(default_factory=dict) - prepend: bool = False - context: Any = None - - def noisy_moment( - self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] - ) -> 'cirq.OP_TREE': - noise_ops = [] - for op in moment: - if _PHYSICAL_GATE_TAG not in op.tags: - # Only real gates get noise applied. - return [moment] - op_data = (type(op.gate), *op.qubits) - if op_data in self.ops_added: - noise_ops.append(self.ops_added[op_data]) - elif op_data[:1] in self.ops_added: - noise_ops.append(self.ops_added[op_data[:1]]) else: - continue - if not noise_ops: - return [moment] - if self.prepend: - return [ops.Moment(noise_ops), moment] - return [moment, ops.Moment(noise_ops)] - - -def build_noise_models(data: NoiseProperties) -> List['cirq.NoiseModel']: - """Construct all NoiseModels associated with NoiseProperties.""" - noise_models = [] - - if set(data.T1_ns) != set(data.Tphi_ns): - raise ValueError( - f'T1 data has qubits {set(data.T1_ns)}, but Tphi has qubits {set(data.Tphi_ns)}.' - ) - if data.T1_ns: # level 1 sophistication - noise_models.append( - finite_temp_model( - {q: 2 for q in data.T1_ns}, - data.gate_times_ns, - cool_rate_GHz={q: 1 / T1 for q, T1 in data.T1_ns.items()}, - dephase_rate_GHz={q: 1 / Tp for q, Tp in data.Tphi_ns.items()}, + # This must be a 2-qubit gate. + if gate_type not in TWO_QUBIT_GATES: + raise ValueError(f'Gate {gate_type} is not in the supported gate list.') + if len(op_id.qubits) != 2: + raise ValueError( + f'Gate {gate_type} takes two qubits, but {op_id.qubits} were given.' + ) + # Subtract decoherence error. + q0, q1 = op_id.qubits + if q0 in self.T1_ns: + rp_0 = decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) + else: + rp_0 = 0 + if q1 in self.T1_ns: + rp_1 = decoherence_pauli_error(self.T1_ns[q1], self.Tphi_ns[q1], time_ns) + else: + rp_1 = 0 + + p_error -= rp_0 + rp_1 + depol_errors[op_id] = p_error + # memoization is OK + self._depolarizing_error = depol_errors + return self._depolarizing_error + + def build_noise_models(self) -> List['cirq.NoiseModel']: + """Construct all NoiseModels associated with this NoiseProperties.""" + noise_models: List['cirq.NoiseModel'] = [] + + if set(self.T1_ns) != set(self.Tphi_ns): + raise ValueError( + f'T1 data has qubits {set(self.T1_ns)}, but Tphi has qubits {set(self.Tphi_ns)}.' ) - ) - - # TODO: move to cirq-google subclass - # entangling gate coherent errors - if data.z_phase_errors is not None: - - def gate_error(key: Tuple[type, 'cirq.GridQubit', 'cirq.GridQubit']) -> 'cirq.Operation': - theta = data.entangler_errors[key].theta - phi = data.entangler_errors[key].phi - z_error = data.z_phase_errors.get(key, FSimPhaseAngles(0, 0, 0)) - return ops.PhasedFSimGate( - theta=theta, phi=phi, zeta=z_error.zeta, gamma=z_error.gamma, chi=z_error.chi - ).on(*key[1:]) - - entangler_errors = {k: gate_error(k) for k in data.entangler_errors} - else: - # only entangling parameter errors - entangler_errors = { - (gate_type, q0, q1): ops.FSimGate(theta=v.theta, phi=v.phi).on(q0, q1) - for (gate_type, q0, q1), v in data.entangler_errors.items() - } - if entangler_errors: # level 1 sophistication - noise_models.append(GenericNoiseModel(ops_added=entangler_errors)) - - # subtract the entangler and decoherence errors from the expected - # pauli errors. The remainder is added as depolarizing error after each - # gate. - - gate_types = set(k[0] for k in data.gate_pauli_errors) - if not gate_types.issubset(_EXPECTED_GATES): - raise ValueError( - 'Some gates are not in the supported set.' - f'\nGates: {gate_types}\nSupported: {_EXPECTED_GATES}' - ) - - added_pauli_errors = {} - - for op_id, p_error in data.gate_pauli_errors.items(): - gate_type = op_id[0] - time_ns = float(data.gate_times_ns[gate_type]) - if gate_type in SINGLE_QUBIT_GATES: - if gate_type is MEASURE_GATE: - # Non-measurement error can be ignored on measurement gates. - continue - if len(op_id) != 2: - raise ValueError( - f'Gate {gate_type} only takes one qubit, but {len(op_id) - 1} were given.' - ) - q0 = op_id[1] - # add decoherence error - if q0 in data.T1_ns: - rp_0 = decoherence_pauli_error(data.T1_ns[q0], data.Tphi_ns[q0], time_ns) - p_error -= rp_0 - - else: - # this must be a 2-qubit gate - if gate_type not in TWO_QUBIT_GATES: - raise ValueError(f'Gate {gate_type} is not in the supported gate list.') - if len(op_id) != 3: - raise ValueError( - f'Gate {gate_type} takes two qubits, but {len(op_id) - 1} were given.' + if self.T1_ns: # level 1 sophistication + noise_models.append( + devices.ThermalNoiseModel.create( + {q: 2 for q in self.T1_ns}, + self.gate_times_ns, + cool_rate_GHz={q: 1 / T1 for q, T1 in self.T1_ns.items()}, + dephase_rate_GHz={q: 1 / Tp for q, Tp in self.Tphi_ns.items()}, ) - # add decoherence error - q0, q1 = op_id[1:] - if q0 in data.T1_ns: - rp_0 = decoherence_pauli_error(data.T1_ns[q0], data.Tphi_ns[q0], time_ns) - else: - rp_0 = 0 - if q1 in data.T1_ns: - rp_1 = decoherence_pauli_error(data.T1_ns[q1], data.Tphi_ns[q1], time_ns) - else: - rp_1 = 0 - # add entangling angle error - if op_id in entangler_errors: - unitary_err = protocols.unitary(entangler_errors[op_id]) - fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) - rp_fsim = 1 - fid - else: - rp_fsim = 0 - - p_error -= rp_0 + rp_1 + rp_fsim - - if p_error > 0: - added_pauli_errors[op_id] = ops.depolarize(p_error, len(op_id[1:])).on(*op_id[1:]) + ) - # This adds per-qubit pauli error after ops on those qubits. - noise_models.append(GenericNoiseModel(ops_added=added_pauli_errors)) + gate_types = set(op_id.gate for op_id in self.gate_pauli_errors) + if not gate_types.issubset(_EXPECTED_GATES): + raise ValueError( + 'Some gates are not in the supported set.' + f'\nGates: {gate_types}\nSupported: {_EXPECTED_GATES}' + ) - # This adds per-qubit measurement error BEFORE measurement on those qubits. - if data.ro_fidelities: - added_measure_errors: Dict[OpIdentifierType, 'cirq.Operation'] = {} - for qubit in data.ro_fidelities: - op_id = ('measure', qubit) - p_00, p_11 = data.ro_fidelities[qubit] - p = p_11 / (p_00 + p_11) - gamma = p_11 / p - added_measure_errors[(ops.MeasurementGate, qubit)] = ops.generalized_amplitude_damp( - p, gamma - ).on(qubit) + depolarizing_error = self.get_depolarizing_error() + added_pauli_errors = { + op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) + for op_id, p_error in depolarizing_error.items() + if p_error > 0 + } - noise_models.append(GenericNoiseModel(ops_added=added_measure_errors, prepend=True)) + # This adds per-qubit pauli error after ops on those qubits. + noise_models.append(devices.InsertionNoiseModel(ops_added=added_pauli_errors)) + + # This adds per-qubit measurement error BEFORE measurement on those qubits. + if self.ro_fidelities: + added_measure_errors: Dict[OpIdentifier, 'cirq.Operation'] = {} + for qubit in self.ro_fidelities: + p_00, p_11 = self.ro_fidelities[qubit] + p = p_11 / (p_00 + p_11) + gamma = p_11 / p + added_measure_errors[ + OpIdentifier(ops.MeasurementGate, qubit) + ] = ops.generalized_amplitude_damp(p, gamma).on(qubit) + + noise_models.append( + devices.InsertionNoiseModel(ops_added=added_measure_errors, prepend=True) + ) - return noise_models + return noise_models class NoiseModelFromNoiseProperties(devices.NoiseModel): @@ -542,16 +193,23 @@ def __init__(self, noise_properties: NoiseProperties) -> None: Raises: ValueError: if no NoiseProperties object is specified. """ - if noise_properties is not None: - self._noise_properties = noise_properties - else: - raise ValueError('A NoiseProperties object must be specified') + self._noise_properties = noise_properties + self.noise_models = self._noise_properties.build_noise_models() + + def virtual_predicate(self, op: 'cirq.Operation') -> bool: + """Returns True if an operation is virtual. - self.noise_models = build_noise_models(self._noise_properties) + Device-specific subclasses should implement this method to mark any + operations which their device handles outside the quantum hardware. + """ + return False def noisy_moments( self, moments: Iterable['cirq.Moment'], system_qubits: Sequence['cirq.Qid'] ) -> Sequence['cirq.OP_TREE']: + # Hack to bypass import order constraints + from cirq import circuits + # Split multi-qubit measurements into single-qubit measurements. # These will be recombined after noise is applied. split_measure_moments = [] @@ -568,20 +226,11 @@ def noisy_moments( split_measure_ops.append(ops.measure(q, key=m_key)) split_measure_moments.append(ops.Moment(split_measure_ops)) - # TODO: HACK HACK HACK - from cirq import circuits - # TODO: is this a Google-only feature? - from cirq_google import PhysicalZTag - new_moments = [] for moment in split_measure_moments: - virtual_ops = { - op for op in moment - if isinstance(op.gate, Z_GATE) and PhysicalZTag not in op.tags - } + virtual_ops = {op for op in moment if self.virtual_predicate(op)} physical_ops = [ - op.with_tags(_PHYSICAL_GATE_TAG) for op in moment - if op not in virtual_ops + op.with_tags(PHYSICAL_GATE_TAG) for op in moment if op not in virtual_ops ] if virtual_ops: new_moments.append(ops.Moment(virtual_ops)) @@ -590,10 +239,6 @@ def noisy_moments( split_measure_circuit = circuits.Circuit(new_moments) - - # TODO: virtual operation handling - # noisy_circuit = _split_software_and_real_moments(noisy_circuit) - # Add actual noise. noisy_circuit = split_measure_circuit.copy() for model in self.noise_models: diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index 02c6a9bc51d..6301259e8e5 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -1,39 +1,31 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -from typing import List, Tuple +from typing import Dict, List, Tuple import numpy as np import pytest import cirq, cirq_google # from cirq.testing import assert_equivalent_op_tree from cirq.devices.noise_properties import ( - FSimEntanglingAngles, - FSimPhaseAngles, NoiseProperties, NoiseModelFromNoiseProperties, - Z_GATE, - MEASURE_GATE, - RESET_GATE, - MW_GATE, - FSIM_GATE, - ISWAP_GATE, - CZ_GATE, - SYMMETRIC_TWO_QUBIT_GATES, SINGLE_QUBIT_GATES, TWO_QUBIT_GATES, ) - - -DEFAULT_GATE_NS = { - Z_GATE: 25.0, - MEASURE_GATE: 4000.0, - RESET_GATE: 250.0, - MW_GATE: 25.0, - FSIM_GATE: 32.0, - ISWAP_GATE: 32.0, - CZ_GATE: 32.0, - # WAIT_GATE is a special case. +from cirq.devices.noise_utils import OpIdentifier + + +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. } + # These properties are for testing purposes only - they are not representative # of device behavior for any existing hardware. def sample_noise_properties( @@ -44,19 +36,9 @@ def sample_noise_properties( T1_ns={q: 1e5 for q in system_qubits}, Tphi_ns={q: 2e5 for q in system_qubits}, ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, - entangler_errors={ - (g, q0, q1): FSimEntanglingAngles(0.01, 0.02) - for g in SYMMETRIC_TWO_QUBIT_GATES - for q0, q1 in qubit_pairs - }, gate_pauli_errors={ - **{(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, - **{(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, - }, - z_phase_errors={ - (g, q0, q1): FSimPhaseAngles(0.03, 0.04, 0.05) - for g in SYMMETRIC_TWO_QUBIT_GATES - for q0, q1 in qubit_pairs + **{OpIdentifier(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, + **{OpIdentifier(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, }, ) @@ -93,7 +75,7 @@ def test_model_from_props(): print(noisy_circuit.moments) print(cirq.kraus_to_choi(cirq.kraus(noisy_circuit.moments[4].operations[0]))) - # assert False + assert False # TODO: rewrite tests @@ -151,3 +133,8 @@ def test_cz_gates(): def test_measure_gates(): # TODO: includes single- and multi-qubit measure assert False + + +def test_wait_gates(): + # TODO: includes single- and multi-qubit measure + assert False diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 025304dbc30..071a127b58a 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -1,6 +1,38 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING, List, Tuple, Union import numpy as np import warnings +if TYPE_CHECKING: + import cirq + + +# Tag for gates to which noise must be applied. +PHYSICAL_GATE_TAG = 'physical_gate' + + +@dataclass(frozen=True) +class OpIdentifier: + """Identifies an operation by gate and (optionally) target qubits.""" + gate: type + qubits: List['cirq.Qid'] + + def __init__(self, gate, *qubits): + object.__setattr__(self, 'gate', gate) + object.__setattr__(self, 'qubits', qubits) + + def gate_only(self): + return OpIdentifier(self.gate) + + def __str__(self): + if self.qubits: + return f'{self.gate}{self.qubits}' + return f'{self.gate}' + + def __repr__(self) -> str: + return f'OpIdentifier({self.gate.__qualname__}, *{self.qubits})' + + # TODO: expose all from top-level cirq def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: """Calculates the XEB fidelity from the depolarization decay constant. @@ -88,3 +120,61 @@ def average_error(decay_constant: float, num_qubits: int = 1) -> float: """ N = 2 ** num_qubits return (1 - decay_constant) * (1 - 1 / N) + + +def decoherence_pauli_error(T1_ns: float, Tphi_ns: float, gate_time_ns: float) -> float: + """The component of Pauli error caused by decoherence. + + Args: + T1_ns: T1 time in nanoseconds. + Tphi_ns: Tphi time in nanoseconds. + gate_time_ns: Duration in nanoseconds of the gate affected by this error. + """ + Gamma2 = (1 / (2 * T1_ns)) + 1 / Tphi_ns + + exp1 = np.exp(-gate_time_ns / T1_ns) + exp2 = np.exp(-gate_time_ns * Gamma2) + px = 0.25 * (1 - exp1) + py = px + pz = 0.5 * (1 - exp2) - px + return px + py + pz + + +def unitary_entanglement_fidelity(U_actual: np.ndarray, U_ideal: np.ndarray) -> np.ndarray: + """Entanglement fidelity between two unitaries. + + For unitary matrices, this is related to average unitary fidelity F by: + + :math:`F = \frac{F_e d + 1}{d + 1}` + + where d is the matrix dimension. + + Args: + U_actual : Matrix whose fidelity to U_ideal will be computed. This may + be a non-unitary matrix, i.e. the projection of a larger unitary + matrix into the computational subspace. + U_ideal : Unitary matrix to which U_actual will be compared. + + Both arguments may be vectorized, in that their shapes may be of the form + (...,M,M) (as long as both shapes can be broadcast together). + + Returns: + The entanglement fidelity between the two unitaries. For inputs with + shape (...,M,M), the output has shape (...). + """ + + def shapes_broadcastable(shape_0: Tuple[int, ...], shape_1: Tuple[int, ...]) -> bool: + return all((m == n) or (m == 1) or (n == 1) for m, n in zip(shape_0[::-1], shape_1[::-1])) + + U_actual = np.asarray(U_actual) + U_ideal = np.asarray(U_ideal) + if not shapes_broadcastable(U_actual.shape, U_ideal.shape): + raise ValueError('Input arrays do not have matching shapes.') + if U_actual.shape[-1] != U_actual.shape[-2]: + raise ValueError("Inputs' trailing dimensions must be equal (square).") + + dim = U_ideal.shape[-1] + + prod_trace = np.einsum('...ba,...ba->...', U_actual.conj(), U_ideal) + + return np.real((np.abs(prod_trace)) / dim) ** 2 diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py new file mode 100644 index 00000000000..d986d872870 --- /dev/null +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -0,0 +1,220 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union +from scipy.linalg import expm +import numpy as np + +from cirq import devices, ops, qis +from cirq.devices.noise_utils import ( + PHYSICAL_GATE_TAG, +) + +if TYPE_CHECKING: + import cirq + + +def _left_mul(mat: np.ndarray) -> np.ndarray: + """Superoperator associated with left multiplication by a square matrix.""" + mat = np.asarray(mat) + if mat.shape[-1] != mat.shape[-2]: + raise ValueError( + f'_left_mul only accepts square matrices, but input matrix has shape {mat.shape}.' + ) + dim = mat.shape[-1] + + return np.kron(mat, np.eye(dim)) + + +def _right_mul(mat: np.ndarray) -> np.ndarray: + """Superoperator associated with right multiplication by a square matrix.""" + mat = np.asarray(mat) + if mat.shape[-1] != mat.shape[-2]: + raise ValueError( + f'_right_mul only accepts square matrices, but input matrix has shape {mat.shape}.' + ) + dim = mat.shape[-1] + + return np.kron(np.eye(dim), np.swapaxes(mat, -2, -1)) + + +def _lindbladian(left_op: np.ndarray) -> np.ndarray: + """Superoperator representing a Lindbladian. + + The Lindbladian generated by a single operator A is the superoperator + L(\rho) = A \rho A^\dagger - 0.5 (A^\dagger A \rho + \rho A^\dagger A) + + Args: + left_op: The operator acting on the left in the Lindbladian (A above). + + Returns: + Superoperator corresponding to the Lindbladian. + """ + left_op = np.asarray(left_op) + right_op = left_op.conj().T + square = right_op @ left_op + out = _left_mul(left_op) @ _right_mul(right_op) + out -= 0.5 * (_left_mul(square) + _right_mul(square)) + return out + + +def _decoherence_matrix( + cool_rate: float, dephase_rate: float, heat_rate: float = 0.0, dim: int = 2 +) -> np.ndarray: + """Construct a rate matrix associated with decay and dephasing. + + The units of the matrix match the units of the rates specified. + This matrix can be used to construct a ThermalChannel after rescaling + by an idling time (to make it dimensionless). + + Args: + cool_rate: Decay rate of the system, usually 1 / T_1 + dephase_rate: Static dephasing rate of the system, usually 1 / T_phi + heat_rate: Heating rate of the system (default 0). + dim: Number of energy levels to include (default 2). + + Returns: + np.ndarray rate matrix for decay and dephasing. + """ + # heating (related to a^dag) + rate_matrix = np.diag(np.arange(1, dim) * heat_rate, 1).T.astype(float) + # cooling (related to a) + rate_matrix += np.diag(np.arange(1, dim) * cool_rate, 1) + # dephasing (related to n=a^dag * a) + # we specify i^2 since we take the sqrt to get + # Lindblad op later. + rate_matrix += np.diag(dephase_rate * np.arange(dim) ** 2) + return rate_matrix + + +def _validate_rates(qubit_dims: Dict['cirq.Qid', int], rates: Dict['cirq.Qid', np.ndarray]) -> None: + """ + Check all rate matrices are square, and of appropriate dim. + We check rates are positive in the class validator. + """ + if set(qubit_dims) != set(rates): + raise ValueError('qubits for rates inconsistent with those through qubit_dims') + for q in rates: + if rates[q].shape != (qubit_dims[q], qubit_dims[q]): + raise ValueError( + f'rate matrix invalid shape. ' + f'should be ({qubit_dims[q]}, {qubit_dims[q]}), ' + f'but got {rates[q].shape}' + ) + + +@dataclass +class ThermalNoiseModel(devices.NoiseModel): + gate_durations_ns: Dict[type, float] + rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] + require_physical_tag: bool = True + + @staticmethod + def create( + qubit_dims: Dict['cirq.Qid', int], + gate_durations_ns: Dict[type, float], + heat_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, + cool_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, + dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, + require_physical_tag: bool = True, + ) -> 'ThermalNoiseModel': + """Construct a ThermalNoiseModel data object. + + Required Args: + qubit_dims: Dimension for all qubits in the system. + Currently only supports dimension=2 (qubits, not qudits) + Optional Args: + heat_rate_GHz: single number (units GHz) specifying heating rate, + either per qubit, or global value for all. + Given a rate gh, the Lindblad op will be sqrt(gh)*a^dag + (where a is annihilation), + so that the heating Lindbldian is + gh(a^dag • a - 0.5{a*a^dag, •}). + cool_rate_GHz: single number (units GHz) specifying cooling rate, + either per qubit, or global value for all. + Given a rate gc, the Lindblad op will be sqrt(gc)*a + so that the cooling Lindbldian is + gc(a • a^dag - 0.5{n, •}) + This number is equivalent to 1/T1. + dephase_rate_GHz: single number (units GHz) specifying dephasing rate, + either per qubit, or global value for all. + Given a rate gd, Lindblad op will be sqrt(2*gd)*n where + n = a^dag * a, so that the dephasing Lindbldian is + 2 * gd * (n • n - 0.5{n^2, •}). + This number is equivalent to 1/Tphi. + + Returns: + The ThermalNoiseModel with specified parameters. + """ + + qubits = set(qubit_dims) + rate_dict = {} + + def _as_rate_dict( + rate_or_dict: Optional[Union[float, Dict['cirq.Qid', float]]] + ) -> Dict['cirq.Qid', float]: + # Convert float or None input into dictionary form. Make sure no + # qubits are missing from dictionary input. + if rate_or_dict is None: + return {qb: 0.0 for qb in qubits} + elif isinstance(rate_or_dict, dict): + out = rate_or_dict.copy() + for qb in qubits: + if qb not in rate_or_dict: + out[qb] = 0.0 + return out + elif np.isscalar(rate_or_dict): + return {qb: rate_or_dict for qb in qubits} + else: + raise ValueError(f'invalid type passed to _as_rate_dict: {rate_or_dict}') + + heat_rate_GHz = _as_rate_dict(None) + cool_rate_GHz = _as_rate_dict(cool_rate_GHz) + dephase_rate_GHz = _as_rate_dict(dephase_rate_GHz) + + for q, dim in qubit_dims.items(): + gamma_h = heat_rate_GHz[q] + gamma_c = cool_rate_GHz[q] + gamma_phi = dephase_rate_GHz[q] + + rate_dict[q] = _decoherence_matrix(gamma_c, gamma_phi, gamma_h, dim) + + _validate_rates(qubit_dims, rate_dict) + + return ThermalNoiseModel(gate_durations_ns, rate_dict, require_physical_tag) + + def noisy_moment( + self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] + ) -> 'cirq.OP_TREE': + noise_ops: List['cirq.Operation'] = [] + moment_ns: float = 0 + for op in moment: + if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: + # Only non-virtual gates get noise applied. + return [moment] + op_duration: Optional[float] = None + for key, duration in self.gate_durations_ns.items(): + if not issubclass(type(op.gate), key): + continue # gate type doesn't match + # TODO: remove assumption of same time across qubits + # if len(key) > 1 and op_data[:1] != key[:1]: + # continue # qubits don't match + op_duration = duration + break + if op_duration is None: + if not isinstance(op.gate, ops.WaitGate): + continue + # special case for wait gates if not predefined + op_duration = op.gate.duration.total_nanos() + moment_ns = max(moment_ns, op_duration) + + for qubit in system_qubits: + rates = self.rate_matrix_GHz[qubit] * moment_ns + num_op = np.diag(np.sqrt(np.diag(rates))) + annihilation = np.sqrt(np.triu(rates, 1)) + creation = np.sqrt(np.triu(rates.T, 1)).T + # Lindbladian with three Lindblad ops for the three processes + # Note: 'time' parameter already specified implicitly through rates + L = _lindbladian(annihilation) + _lindbladian(creation) + 2 * _lindbladian(num_op) + superop = expm(L.real) + kraus_ops = qis.superoperator_to_kraus(superop) + noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit)) + return [moment, ops.Moment(noise_ops)] diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py new file mode 100644 index 00000000000..3b45f4b58b5 --- /dev/null +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -0,0 +1,98 @@ +from dataclasses import dataclass, field +from typing import Dict, Iterable, List, Sequence, Set +import numpy as np + +import cirq, cirq_google +from cirq import devices +from cirq.devices.noise_utils import ( + OpIdentifier, + unitary_entanglement_fidelity, +) + + +SYMMETRIC_TWO_QUBIT_GATES = {cirq.FSimGate, cirq.ISwapPowGate, cirq.CZPowGate} +ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() +TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES + + +@dataclass +class GoogleNoiseProperties(cirq.NoiseProperties): + """Noise-defining properties for a Google device. + + Inherited args: + gate_times_ns: Dict[type, float] of gate types to their duration on + Google hardware. + T1_ns: Dict[cirq.Qid, float] of qubits to their T1 time, in ns. + Tphi_ns: Dict[cirq.Qid, float] of qubits to their Tphi time, in ns. + ro_fidelities: Dict[cirq.Qid, np.ndarray] of qubits to their readout + fidelity matrix. + gate_pauli_errors: Dict[OpIdentifier, float] of gate types + (potentially on specific qubits) to the Pauli error for that gate. + + Additional args: + fsim_errors: Dict[OpIdentifier, cirq.PhasedFSimGate] of gate types + (potentially on specific qubits) to the PhasedFSim fix-up operation + for that gate. Defaults to no-op for all gates. + """ + fsim_errors: Dict[OpIdentifier, cirq.PhasedFSimGate] = field(default_factory=dict) + + def __attrs_post_init__(self): + t1_qubits = set(self.T1_ns) + tphi_qubits = set(self.Tphi_ns) + + if t1_qubits != tphi_qubits: + raise ValueError(f'Keys specified for T1 and Tphi are not' f' identical.') + + # validate two qubit gate errors. + self._validate_symmetric_errors('gate_pauli_errors') + self._validate_symmetric_errors('fsim_errors') + + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: + if self._depolarizing_error: + return self._depolarizing_error + + depol_errors = super().get_depolarizing_error() + + for op_id in depol_errors: + if op_id.gate not in TWO_QUBIT_GATES: + continue + if len(op_id.qubits) != 2: + raise ValueError( + f'Gate {op_id.gate} takes two qubits, but {op_id.qubits} were given.' + ) + # Subtract entangling angle error. + if op_id in self.fsim_errors: + unitary_err = cirq.unitary(self.fsim_errors[op_id]) + fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) + rp_fsim = 1 - fid + elif op_id.gate_only() in self.fsim_errors: + unitary_err = cirq.unitary(self.fsim_errors[op_id.gate_only()]) + fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) + rp_fsim = 1 - fid + else: + rp_fsim = 0 + + depol_errors[op_id] -= rp_fsim + # memoization is OK + self._depolarizing_error = depol_errors + return self._depolarizing_error + + def build_noise_models(self) -> List['cirq.NoiseModel']: + """Construct all NoiseModels associated with NoiseProperties.""" + noise_models = super().build_noise_models() + + # Insert ntangling gate coherent errors after thermal error. + if self.fsim_errors is not None: + fsim_ops = {op_id: gate.on(*op_id.qubits) for op_id, gate in self.fsim_errors.items()} + noise_models.insert(1, devices.InsertionNoiseModel(ops_added=fsim_ops)) + + return noise_models + + +class NoiseModelFromGoogleNoiseProperties(devices.NoiseModelFromNoiseProperties): + """A noise model defined from noise properties of a Google device.""" + + def virtual_predicate(self, op: cirq.Operation) -> bool: + return isinstance(op.gate, cirq.ZPowGate) and cirq_google.PhysicalZTag not in op.tags + + # noisy_moments is implemented by the superclass. diff --git a/cirq-google/cirq_google/devices/google_noise_properties_test.py b/cirq-google/cirq_google/devices/google_noise_properties_test.py new file mode 100644 index 00000000000..86886b7b741 --- /dev/null +++ b/cirq-google/cirq_google/devices/google_noise_properties_test.py @@ -0,0 +1,89 @@ +from typing import Dict, List, Tuple +import numpy as np +import cirq, cirq_google + +# from cirq.testing import assert_equivalent_op_tree +from cirq.devices.noise_properties import ( + SYMMETRIC_TWO_QUBIT_GATES, + SINGLE_QUBIT_GATES, + TWO_QUBIT_GATES, +) +from cirq.devices.noise_utils import OpIdentifier + +from cirq_google.devices.google_noise_properties import ( + GoogleNoiseProperties, + NoiseModelFromGoogleNoiseProperties, +) + + +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. +} + + +# These properties are for testing purposes only - they are not representative +# of device behavior for any existing hardware. +def sample_noise_properties( + system_qubits: List[cirq.Qid], qubit_pairs: List[Tuple[cirq.Qid, cirq.Qid]] +): + return GoogleNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q: 1e5 for q in system_qubits}, + Tphi_ns={q: 2e5 for q in system_qubits}, + ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, + gate_pauli_errors={ + **{OpIdentifier(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, + **{OpIdentifier(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, + }, + fsim_errors={ + OpIdentifier(g, q0, q1): cirq.PhasedFSimGate(0.01, 0.03, 0.04, 0.05, 0.02) + for g in SYMMETRIC_TWO_QUBIT_GATES + for q0, q1 in qubit_pairs + }, + ) + + +def test_model_from_props(): + system_qubits = cirq.GridQubit.rect(2, 2) + qubit_pairs = [ + (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), + (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), + (cirq.GridQubit(0, 1), cirq.GridQubit(0, 0)), + (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), + (cirq.GridQubit(1, 1), cirq.GridQubit(0, 1)), + (cirq.GridQubit(1, 1), cirq.GridQubit(1, 0)), + (cirq.GridQubit(1, 0), cirq.GridQubit(0, 0)), + (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), + ] + props = sample_noise_properties(system_qubits, qubit_pairs) + model = NoiseModelFromGoogleNoiseProperties(props) + + circuit = cirq.Circuit( + cirq.H(system_qubits[0]), + cirq.H(system_qubits[2]), + cirq.CNOT(*system_qubits[0:2]), + cirq.CNOT(*system_qubits[2:4]), + cirq.measure(*system_qubits, key='m'), + ) + print('circuit') + print(circuit) + syc_circuit = cirq_google.optimized_for_sycamore(circuit) + print('syc_circuit') + print(syc_circuit) + noisy_circuit = syc_circuit.with_noise(model) + print('noisy_circuit') + print(noisy_circuit.moments) + choi = cirq.kraus_to_choi(cirq.kraus(noisy_circuit.moments[4].operations[0])) + print(choi) + + assert False + + +# TODO: all the other tests diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py index 06f202a1b7f..8bb9c06c278 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py @@ -1,5 +1,6 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -import cirq_google +from typing import Dict +import cirq, cirq_google import numpy as np from cirq.devices.noise_properties import NoiseProperties @@ -72,6 +73,20 @@ class (cirq.devices.noise_properties) to create a NoiseModel that can be used wi 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. + } + + # TODO: realign with new NoiseProperties # 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 From 4a66387f29e0c2558bb313b60a1197954688838b Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 4 Nov 2021 08:36:06 -0700 Subject: [PATCH 04/13] All mypy complaints resolved --- .../cirq/devices/insertion_noise_model.py | 5 +- cirq-core/cirq/devices/noise_properties.py | 4 +- .../cirq/devices/noise_properties_test.py | 2 +- cirq-core/cirq/devices/noise_utils.py | 9 +- cirq-core/cirq/devices/thermal_noise_model.py | 20 +- .../devices/google_noise_properties.py | 5 +- .../calibration_to_noise_properties.py | 154 +++++------ .../calibration_to_noise_properties_test.py | 254 +++--------------- .../compare_generated_noise_to_metrics.py | 82 +++--- 9 files changed, 163 insertions(+), 372 deletions(-) diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index 34989f5692f..f48ef17e8bc 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Dict, Sequence +from typing import TYPE_CHECKING, Dict, Sequence from cirq import devices, ops from cirq.devices.noise_utils import ( @@ -14,7 +14,7 @@ @dataclass class InsertionNoiseModel(devices.NoiseModel): """Simple base noise model for inserting operations. - + Operations generated by this model for a given moment are all added into a single "noise moment", which is added before or after the original moment based on `prepend`. @@ -26,6 +26,7 @@ class InsertionNoiseModel(devices.NoiseModel): require_physical_tag: whether to only apply noise to operations tagged with PHYSICAL_GATE_TAG. """ + ops_added: Dict[OpIdentifier, 'cirq.Operation'] = field(default_factory=dict) prepend: bool = False require_physical_tag: bool = True diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 962a843cac3..1347c1d515a 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,6 +1,6 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice from dataclasses import dataclass, field -from typing import Dict, Iterable, Optional, Sequence, TYPE_CHECKING, List, Set +from typing import Dict, Iterable, Sequence, TYPE_CHECKING, List, Set import numpy as np from cirq import ops, protocols, devices @@ -47,7 +47,7 @@ def _validate_symmetric_errors(self, field_name: str) -> None: gate_error_dict = getattr(self, field_name) if gate_error_dict is None: return # some fields are optional - for op_id, val in gate_error_dict.items(): + for op_id in gate_error_dict: if len(op_id.qubits) != 2: # single qubit op_ids also present, or generic values are # specified. Skip these cases diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index 6301259e8e5..48dd0960945 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -1,7 +1,6 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice from typing import Dict, List, Tuple import numpy as np -import pytest import cirq, cirq_google # from cirq.testing import assert_equivalent_op_tree @@ -67,6 +66,7 @@ def test_model_from_props(): ) print('circuit') print(circuit) + # TODO: remove cirq_google dependency syc_circuit = cirq_google.optimized_for_sycamore(circuit) print('syc_circuit') print(syc_circuit) diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 071a127b58a..0690fbd4d83 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -1,7 +1,7 @@ from dataclasses import dataclass -from typing import TYPE_CHECKING, List, Tuple, Union -import numpy as np +from typing import TYPE_CHECKING, List, Tuple import warnings +import numpy as np if TYPE_CHECKING: import cirq @@ -14,6 +14,7 @@ @dataclass(frozen=True) class OpIdentifier: """Identifies an operation by gate and (optionally) target qubits.""" + gate: type qubits: List['cirq.Qid'] @@ -158,6 +159,10 @@ def unitary_entanglement_fidelity(U_actual: np.ndarray, U_ideal: np.ndarray) -> Both arguments may be vectorized, in that their shapes may be of the form (...,M,M) (as long as both shapes can be broadcast together). + Raises: + ValueError: if input arrays cannot be broadcast or have mismatching + trailing dimensions. + Returns: The entanglement fidelity between the two unitaries. For inputs with shape (...,M,M), the output has shape (...). diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py index d986d872870..e030f54c417 100644 --- a/cirq-core/cirq/devices/thermal_noise_model.py +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -37,10 +37,13 @@ def _right_mul(mat: np.ndarray) -> np.ndarray: def _lindbladian(left_op: np.ndarray) -> np.ndarray: - """Superoperator representing a Lindbladian. + r"""Superoperator representing a Lindbladian. The Lindbladian generated by a single operator A is the superoperator - L(\rho) = A \rho A^\dagger - 0.5 (A^\dagger A \rho + \rho A^\dagger A) + + $$ + L(\rho) = A \rho A^\dagger - 0.5 (A^\dagger A \rho + \rho A^\dagger A) + $$ Args: left_op: The operator acting on the left in the Lindbladian (A above). @@ -74,20 +77,19 @@ def _decoherence_matrix( Returns: np.ndarray rate matrix for decay and dephasing. """ - # heating (related to a^dag) + # Heating (related to a^dag) rate_matrix = np.diag(np.arange(1, dim) * heat_rate, 1).T.astype(float) - # cooling (related to a) + # Cooling (related to a) rate_matrix += np.diag(np.arange(1, dim) * cool_rate, 1) - # dephasing (related to n=a^dag * a) - # we specify i^2 since we take the sqrt to get - # Lindblad op later. + # Dephasing (related to n=a^dag * a) + # We specify i^2 since we take square root to get the Lindblad op later. rate_matrix += np.diag(dephase_rate * np.arange(dim) ** 2) return rate_matrix def _validate_rates(qubit_dims: Dict['cirq.Qid', int], rates: Dict['cirq.Qid', np.ndarray]) -> None: - """ - Check all rate matrices are square, and of appropriate dim. + """Check all rate matrices are square and of appropriate dimension. + We check rates are positive in the class validator. """ if set(qubit_dims) != set(rates): diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index 3b45f4b58b5..525bd8813ca 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import Dict, Iterable, List, Sequence, Set +from typing import Dict, List, Set import numpy as np import cirq, cirq_google @@ -18,7 +18,7 @@ @dataclass class GoogleNoiseProperties(cirq.NoiseProperties): """Noise-defining properties for a Google device. - + Inherited args: gate_times_ns: Dict[type, float] of gate types to their duration on Google hardware. @@ -34,6 +34,7 @@ class GoogleNoiseProperties(cirq.NoiseProperties): (potentially on specific qubits) to the PhasedFSim fix-up operation for that gate. Defaults to no-op for all gates. """ + fsim_errors: Dict[OpIdentifier, cirq.PhasedFSimGate] = field(default_factory=dict) def __attrs_post_init__(self): diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py index 8bb9c06c278..727013bb84b 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py @@ -1,76 +1,42 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -from typing import Dict +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. @@ -86,47 +52,49 @@ class (cirq.devices.noise_properties) to create a NoiseModel that can be used wi # cirq.WaitGate is a special case. } - # TODO: realign with new NoiseProperties # 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) + ro_fidelities = { + q: np.array([p00.get(q, 0), p11.get(q, 0)]) for q in set(p00.keys()) | set(p11.keys()) + } - 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.' - ) + # TODO: include entangling errors. - 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) + 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, + ) diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py index 5412130a321..f8be725057e 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py @@ -1,94 +1,98 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -import pytest +import cirq import cirq_google from cirq_google.api import v2 from cirq_google.experimental.noise_models.calibration_to_noise_properties import ( noise_properties_from_calibration, ) +from cirq.devices.noise_utils import ( + OpIdentifier, +) from google.protobuf.text_format import Merge import numpy as np def test_noise_properties_from_calibration(): - xeb_error_1 = 0.999 - xeb_error_2 = 0.996 - - p00_1 = 0.001 - p00_2 = 0.002 - p00_3 = 0.003 - - t1_1 = 0.005 - t1_2 = 0.007 - t1_3 = 0.003 + qubits = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1), cirq.GridQubit(1, 0)] + pauli_error = [0.001, 0.002, 0.003] + p00_error = [0.004, 0.005, 0.006] + p11_error = [0.007, 0.008, 0.009] + t1_micros = [10, 20, 30] _CALIBRATION_DATA = Merge( f""" timestamp_ms: 1579214873, metrics: [{{ - name: 'xeb', - targets: ['0_0', '0_1'], + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['0_0'], + values: [{{ + double_val: {pauli_error[0]} + }}] + }}, {{ + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['0_1'], values: [{{ - double_val: {xeb_error_1} + double_val:{pauli_error[1]} }}] }}, {{ - name: 'xeb', - targets: ['0_0', '1_0'], + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['1_0'], values: [{{ - double_val:{xeb_error_2} + double_val:{pauli_error[2]} }}] }}, {{ name: 'single_qubit_p00_error', targets: ['0_0'], values: [{{ - double_val: {p00_1} + double_val: {p00_error[0]} }}] }}, {{ name: 'single_qubit_p00_error', targets: ['0_1'], values: [{{ - double_val: {p00_2} + double_val: {p00_error[1]} }}] }}, {{ name: 'single_qubit_p00_error', targets: ['1_0'], values: [{{ - double_val: {p00_3} + double_val: {p00_error[2]} }}] }}, {{ - name: 'single_qubit_readout_separation_error', + name: 'single_qubit_p11_error', targets: ['0_0'], values: [{{ - double_val: .004 + double_val: {p11_error[0]} }}] }}, {{ - name: 'single_qubit_readout_separation_error', + name: 'single_qubit_p11_error', targets: ['0_1'], values: [{{ - double_val: .005 + double_val: {p11_error[1]} }}] - }},{{ - name: 'single_qubit_readout_separation_error', + }}, {{ + name: 'single_qubit_p11_error', targets: ['1_0'], values: [{{ - double_val: .006 + double_val: {p11_error[2]} }}] }}, {{ name: 'single_qubit_idle_t1_micros', targets: ['0_0'], values: [{{ - double_val: {t1_1} + double_val: {t1_micros[0]} }}] }}, {{ name: 'single_qubit_idle_t1_micros', targets: ['0_1'], values: [{{ - double_val: {t1_2} + double_val: {t1_micros[1]} }}] }}, {{ name: 'single_qubit_idle_t1_micros', targets: ['1_0'], values: [{{ - double_val: {t1_3} + double_val: {t1_micros[2]} }}] }}] """, @@ -99,186 +103,10 @@ def test_noise_properties_from_calibration(): calibration = cirq_google.Calibration(_CALIBRATION_DATA) prop = noise_properties_from_calibration(calibration) - expected_t1_nanos = np.mean([t1_1, t1_2, t1_3]) * 1000 - expected_xeb_fidelity = 1 - np.mean([xeb_error_1, xeb_error_2]) - expected_p00 = np.mean([p00_1, p00_2, p00_3]) - - assert np.isclose(prop.t1_ns, expected_t1_nanos) - assert np.isclose(prop.xeb, expected_xeb_fidelity) - assert np.isclose(prop.p00, expected_p00) - - -def test_from_calibration_rb(): - rb_pauli_1 = 0.001 - rb_pauli_2 = 0.002 - rb_pauli_3 = 0.003 - - _CALIBRATION_DATA_RB = Merge( - f""" - timestamp_ms: 1579214873, - metrics: [{{ - - name: 'single_qubit_rb_pauli_error_per_gate', - targets: ['0_0'], - values: [{{ - double_val: {rb_pauli_1} - }}] - }}, {{ - name: 'single_qubit_rb_pauli_error_per_gate', - targets: ['0_1'], - values: [{{ - double_val: {rb_pauli_2} - }}] - }}, {{ - name: 'single_qubit_rb_pauli_error_per_gate', - targets: ['1_0'], - values: [{{ - double_val: {rb_pauli_3} - }}] - }}] - """, - v2.metrics_pb2.MetricsSnapshot(), - ) - - # Create NoiseProperties object from Calibration - rb_calibration = cirq_google.Calibration(_CALIBRATION_DATA_RB) - rb_noise_prop = noise_properties_from_calibration(rb_calibration) - - average_pauli_rb = np.mean([rb_pauli_1, rb_pauli_2, rb_pauli_3]) - assert np.isclose(average_pauli_rb, rb_noise_prop.pauli_error) - - -def test_validate_calibration(): - # RB Pauli error and RB Average Error disagree - rb_pauli_error = 0.05 - rb_average_error = 0.1 - - decay_constant_pauli = 1 - rb_pauli_error / (1 - 1 / 4) - decay_constant_average = 1 - rb_average_error / (1 - 1 / 2) - _CALIBRATION_DATA_PAULI_AVERAGE = Merge( - f""" - timestamp_ms: 1579214873, - metrics: [{{ - - name: 'single_qubit_rb_pauli_error_per_gate', - targets: ['0_0'], - values: [{{ - double_val: {rb_pauli_error} - }}] - }}, {{ - name: 'single_qubit_rb_average_error_per_gate', - targets: ['0_1'], - values: [{{ - double_val: {rb_average_error} - }}] - }}] - """, - v2.metrics_pb2.MetricsSnapshot(), - ) - bad_calibration_pauli_average = cirq_google.Calibration(_CALIBRATION_DATA_PAULI_AVERAGE) - with pytest.raises( - ValueError, - match=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.', - ): - noise_properties_from_calibration(bad_calibration_pauli_average) - - assert np.isclose( - noise_properties_from_calibration( - bad_calibration_pauli_average, validate=False - ).pauli_error, - rb_pauli_error, - ) - - # RB Pauli Error and XEB Fidelity disagree - xeb_fidelity = 0.99 - - decay_constant_from_xeb = 1 - (1 - xeb_fidelity) / (1 - 1 / 4) - - _CALIBRATION_DATA_PAULI_XEB = Merge( - f""" - timestamp_ms: 1579214873, - metrics: [{{ - - name: 'single_qubit_rb_pauli_error_per_gate', - targets: ['0_0'], - values: [{{ - double_val: {rb_pauli_error} - }}] - }}, {{ - name: 'xeb', - targets: ['0_0', '1_0'], - values: [{{ - double_val:{1 - xeb_fidelity} - }}] - }}] - """, - v2.metrics_pb2.MetricsSnapshot(), - ) - - bad_calibration_pauli_xeb = cirq_google.Calibration(_CALIBRATION_DATA_PAULI_XEB) - with pytest.raises( - ValueError, - match=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.', - ): - noise_properties_from_calibration(bad_calibration_pauli_xeb) - - # RB Average Error and XEB Fidelity disagree - _CALIBRATION_DATA_AVERAGE_XEB = Merge( - f""" - timestamp_ms: 1579214873, - metrics: [{{ - - name: 'single_qubit_rb_average_error_per_gate', - targets: ['0_0'], - values: [{{ - double_val: {rb_average_error} - }}] - }}, {{ - name: 'xeb', - targets: ['0_0', '1_0'], - values: [{{ - double_val:{1 - xeb_fidelity} - }}] - }}] - """, - v2.metrics_pb2.MetricsSnapshot(), - ) - - bad_calibration_average_xeb = cirq_google.Calibration(_CALIBRATION_DATA_AVERAGE_XEB) - with pytest.raises( - ValueError, - match=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.', - ): - noise_properties_from_calibration(bad_calibration_average_xeb) - - assert np.isclose( - noise_properties_from_calibration(bad_calibration_average_xeb, validate=False).xeb, - xeb_fidelity, - ) - - # Calibration data with no RB error or XEB fidelity - t1 = 2.0 # microseconds - - _CALIBRATION_DATA_T1 = Merge( - f""" - timestamp_ms: 1579214873, - metrics: [{{ - name: 'single_qubit_idle_t1_micros', - targets: ['0_0'], - values: [{{ - double_val: {t1} - }}] - }}] - """, - v2.metrics_pb2.MetricsSnapshot(), - ) - - calibration_t1 = cirq_google.Calibration(_CALIBRATION_DATA_T1) - - assert np.isclose(noise_properties_from_calibration(calibration_t1).t1_ns, t1 * 1000) + for i, q in enumerate(qubits): + assert np.isclose( + prop.gate_pauli_errors[OpIdentifier(cirq.PhasedXZGate, q)], pauli_error[i] + ) + assert np.allclose(prop.ro_fidelities[q], np.array([p00_error[i], p11_error[i]])) + assert np.isclose(prop.T1_ns[q], t1_micros[i] * 1000) + # TODO: test Tphi diff --git a/cirq-google/cirq_google/experimental/noise_models/compare_generated_noise_to_metrics.py b/cirq-google/cirq_google/experimental/noise_models/compare_generated_noise_to_metrics.py index 2996d5fb75d..15f12820c8f 100644 --- a/cirq-google/cirq_google/experimental/noise_models/compare_generated_noise_to_metrics.py +++ b/cirq-google/cirq_google/experimental/noise_models/compare_generated_noise_to_metrics.py @@ -1,8 +1,12 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice # coverage: ignore +from typing import Any, List import cirq_google -import numpy as np from cirq.devices.noise_properties import NoiseModelFromNoiseProperties +from cirq.devices.noise_utils import ( + OpIdentifier, + decay_constant_to_pauli_error, +) from cirq_google.experimental.noise_models.calibration_to_noise_properties import ( noise_properties_from_calibration, ) @@ -10,73 +14,55 @@ import pandas as pd -def compare_generated_noise_to_metrics( - calibration: cirq_google.Calibration, validate: bool = True, tolerance: float = 0.01 -): +def compare_generated_noise_to_metrics(calibration: cirq_google.Calibration): """Compares the metrics from a Calibration object to those measured from a Noise Model created with cirq.devices.noise_properties_from_calibration. Args: - calibration: Calibration object to be turned into a Noise Model - validate: check calibration metrics are in agreement (arg for noise_properties_from_calibration) - tolerance: tolerance for calibration metrics (argument for noise_properties_from_calibration) + calibration: Calibration object to convert to noise Returns: - df: Pandas dataframe comparing input and measured values for each calibration metric + Pandas dataframe comparing input and measured values for each calibration metric """ # Create Noise Model from Calibration object - noise_prop = noise_properties_from_calibration(calibration, validate, tolerance) + noise_prop = noise_properties_from_calibration(calibration) noise_model = NoiseModelFromNoiseProperties(noise_prop) - p00 = noise_prop.p00 - p11 = noise_prop.p11 - xeb_fidelity = noise_prop.xeb - pauli_error = noise_prop.pauli_error - t1_ns = noise_prop.t1_ns - average_error = noise_prop.average_error() - - qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] + # TODO: refactor this experiment + q0 = cirq.LineQubit(0) + p00 = noise_prop.ro_fidelities[q0][0] + p11 = noise_prop.ro_fidelities[q0][1] + pauli_error = noise_prop.gate_pauli_errors[OpIdentifier(cirq.PhasedXZGate, q0)] + t1_ns = noise_prop.T1_ns[q0] - # Create simulator for experiments with noise model + # # Create simulator for experiments with noise model simulator = cirq.sim.DensityMatrixSimulator(noise=noise_model) - # Experiments to measure metrics + # # Experiments to measure metrics estimate_readout = cirq.experiments.estimate_single_qubit_readout_errors( - simulator, qubits=[qubits[0]], repetitions=1000 + simulator, qubits=[q0], repetitions=1000 ) xeb_result = cirq.experiments.cross_entropy_benchmarking( - simulator, qubits, num_circuits=50, repetitions=1000 + simulator, [q0], num_circuits=50, repetitions=1000 ) - measured_xeb = np.mean([datum.xeb_fidelity for datum in xeb_result.data]) - decay_constant = xeb_result.depolarizing_model().cycle_depolarization + decay_constant_result = xeb_result.depolarizing_model().cycle_depolarization + pauli_error_result = decay_constant_to_pauli_error(decay_constant_result) - output = [] + output: List[Any] = [] - if p00 is not None: - output.append(['p00', p00, estimate_readout.zero_state_errors[cirq.LineQubit(0)]]) - if p11 is not None: - output.append(['p11', p11, estimate_readout.one_state_errors[cirq.LineQubit(0)]]) - if xeb_fidelity is not None: - output.append(['XEB Fidelity', xeb_fidelity, measured_xeb]) - if t1_ns is not None: - t1_results = cirq.experiments.t1_decay( - simulator, - qubit=qubits[0], - num_points=100, - repetitions=100, - min_delay=cirq.Duration(nanos=10), - max_delay=cirq.Duration(micros=1), - ) - output.append(['T1', t1_ns, t1_results.constant]) - if pauli_error is not None: - N = 2 # Dimension of Hilbert Space - measured_pauli_error = (1 - decay_constant) * (1 - 1 / N / N) - output.append(['Pauli Error', pauli_error, measured_pauli_error]) - if average_error is not None: - N = 2 # Dimension of Hilbert Space - measured_average_error = (1 - decay_constant) * (1 - 1 / N) - output.append(['Average Error', average_error, measured_average_error]) + output.append(['p00', p00, estimate_readout.zero_state_errors[cirq.LineQubit(0)]]) + output.append(['p11', p11, estimate_readout.one_state_errors[cirq.LineQubit(0)]]) + t1_results = cirq.experiments.t1_decay( + simulator, + qubit=q0, + num_points=100, + repetitions=100, + min_delay=cirq.Duration(nanos=10), + max_delay=cirq.Duration(micros=1), + ) + output.append(['T1', t1_ns, t1_results.constant]) + output.append(['Pauli Error', pauli_error, pauli_error_result]) columns = ["Metric", "Initial value", "Measured value"] df = pd.DataFrame(output, columns=columns) From c1acebd30919f1fa5e5beb323611a783b2e3bcc9 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 4 Nov 2021 12:39:53 -0700 Subject: [PATCH 05/13] Tests are live --- .../cirq/devices/insertion_noise_model.py | 19 +- cirq-core/cirq/devices/noise_properties.py | 33 ++- .../cirq/devices/noise_properties_test.py | 205 +++++++++++------- cirq-core/cirq/devices/thermal_noise_model.py | 22 +- .../devices/google_noise_properties.py | 28 ++- .../devices/google_noise_properties_test.py | 205 +++++++++++++++--- 6 files changed, 382 insertions(+), 130 deletions(-) diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index f48ef17e8bc..e18a1cdb7cf 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Dict, Sequence +from typing import TYPE_CHECKING, Callable, Dict, Sequence, Union from cirq import devices, ops from cirq.devices.noise_utils import ( @@ -21,13 +21,16 @@ class InsertionNoiseModel(devices.NoiseModel): Args: ops_added: a map of gate types (and optionally, qubits they act on) to - operations that should be added by the model. + either an operations that should be added OR a function that + generates the operation to add. prepend: whether to add the new moment before the current one. require_physical_tag: whether to only apply noise to operations tagged with PHYSICAL_GATE_TAG. """ - ops_added: Dict[OpIdentifier, 'cirq.Operation'] = field(default_factory=dict) + ops_added: Dict[ + OpIdentifier, Union['cirq.Operation', Callable[['cirq.Operation'], 'cirq.Operation']] + ] = field(default_factory=dict) prepend: bool = False require_physical_tag: bool = True @@ -37,11 +40,15 @@ def noisy_moment( noise_ops = [] for op in moment: if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: - # Only real gates get noise applied. - return [moment] + # Only non-virtual gates get noise applied. + continue op_id = OpIdentifier(type(op.gate), *op.qubits) if op_id in self.ops_added: - noise_ops.append(self.ops_added[op_id]) + val = self.ops_added[op_id] + if isinstance(val, ops.Operation): + noise_ops.append(val) + else: + noise_ops.append(val(op)) elif op_id.gate_only() in self.ops_added: noise_ops.append(self.ops_added[op_id.gate_only()]) else: diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 1347c1d515a..181c7086df3 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -13,11 +13,15 @@ if TYPE_CHECKING: import cirq -SINGLE_QUBIT_GATES = {ops.ZPowGate, ops.PhasedXZGate, ops.MeasurementGate, ops.ResetChannel} -SYMMETRIC_TWO_QUBIT_GATES = {ops.FSimGate, ops.ISwapPowGate, ops.CZPowGate} +SINGLE_QUBIT_GATES = { + ops.ZPowGate, + ops.PhasedXZGate, + ops.MeasurementGate, + ops.ResetChannel, +} +SYMMETRIC_TWO_QUBIT_GATES = {ops.FSimGate, ops.PhasedFSimGate, ops.ISwapPowGate, ops.CZPowGate} ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES -_EXPECTED_GATES = SINGLE_QUBIT_GATES | TWO_QUBIT_GATES @dataclass @@ -79,6 +83,18 @@ def qubits(self) -> List['cirq.Qid']: self._qubits = sorted(self.T1_ns) return self._qubits + @classmethod + def single_qubit_gates(cls) -> Set[type]: + return SINGLE_QUBIT_GATES + + @classmethod + def two_qubit_gates(cls) -> Set[type]: + return TWO_QUBIT_GATES + + @classmethod + def expected_gates(cls) -> Set[type]: + return cls.single_qubit_gates() | cls.two_qubit_gates() + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: """Returns the portion of Pauli error from depolarization. @@ -90,8 +106,7 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): gate_type = op_id.gate - time_ns = float(self.gate_times_ns[gate_type]) - if gate_type in SINGLE_QUBIT_GATES: + if gate_type in self.single_qubit_gates(): if issubclass(gate_type, ops.MeasurementGate): # Non-measurement error can be ignored on measurement gates. continue @@ -99,6 +114,7 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: raise ValueError( f'Gate {gate_type} only takes one qubit, but {op_id.qubits} were given.' ) + time_ns = float(self.gate_times_ns[gate_type]) q0 = op_id.qubits[0] # Subtract decoherence error. if q0 in self.T1_ns: @@ -107,12 +123,13 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: else: # This must be a 2-qubit gate. - if gate_type not in TWO_QUBIT_GATES: + if gate_type not in self.two_qubit_gates(): raise ValueError(f'Gate {gate_type} is not in the supported gate list.') if len(op_id.qubits) != 2: raise ValueError( f'Gate {gate_type} takes two qubits, but {op_id.qubits} were given.' ) + time_ns = float(self.gate_times_ns[gate_type]) # Subtract decoherence error. q0, q1 = op_id.qubits if q0 in self.T1_ns: @@ -149,10 +166,10 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: ) gate_types = set(op_id.gate for op_id in self.gate_pauli_errors) - if not gate_types.issubset(_EXPECTED_GATES): + if not gate_types.issubset(self.expected_gates()): raise ValueError( 'Some gates are not in the supported set.' - f'\nGates: {gate_types}\nSupported: {_EXPECTED_GATES}' + f'\nGates: {gate_types}\nSupported: {self.expected_gates()}' ) depolarizing_error = self.get_depolarizing_error() diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index 48dd0960945..3b89901ea5e 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -1,7 +1,8 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice from typing import Dict, List, Tuple import numpy as np -import cirq, cirq_google +import cirq +import pytest # from cirq.testing import assert_equivalent_op_tree from cirq.devices.noise_properties import ( @@ -10,7 +11,10 @@ SINGLE_QUBIT_GATES, TWO_QUBIT_GATES, ) -from cirq.devices.noise_utils import OpIdentifier +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, +) DEFAULT_GATE_NS: Dict[type, float] = { @@ -19,6 +23,7 @@ cirq.ResetChannel: 250.0, cirq.PhasedXZGate: 25.0, cirq.FSimGate: 32.0, + cirq.PhasedFSimGate: 32.0, cirq.ISwapPowGate: 32.0, cirq.CZPowGate: 32.0, # cirq.WaitGate is a special case. @@ -42,64 +47,38 @@ def sample_noise_properties( ) -def test_model_from_props(): - system_qubits = cirq.GridQubit.rect(2, 2) - qubit_pairs = [ - (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), - (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), - (cirq.GridQubit(0, 1), cirq.GridQubit(0, 0)), - (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), - (cirq.GridQubit(1, 1), cirq.GridQubit(0, 1)), - (cirq.GridQubit(1, 1), cirq.GridQubit(1, 0)), - (cirq.GridQubit(1, 0), cirq.GridQubit(0, 0)), - (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), - ] - props = sample_noise_properties(system_qubits, qubit_pairs) - model = NoiseModelFromNoiseProperties(props) - - circuit = cirq.Circuit( - cirq.H(system_qubits[0]), - cirq.H(system_qubits[2]), - cirq.CNOT(*system_qubits[0:2]), - cirq.CNOT(*system_qubits[2:4]), - cirq.measure(*system_qubits, key='m'), - ) - print('circuit') - print(circuit) - # TODO: remove cirq_google dependency - syc_circuit = cirq_google.optimized_for_sycamore(circuit) - print('syc_circuit') - print(syc_circuit) - noisy_circuit = syc_circuit.with_noise(model) - print('noisy_circuit') - print(noisy_circuit.moments) - print(cirq.kraus_to_choi(cirq.kraus(noisy_circuit.moments[4].operations[0]))) - - assert False - - -# TODO: rewrite tests -# Things to test: -# - each possible native gate is converted properly, including: -# Z phase, microwave, [sqrt_]iswap, sycamore (cirq_google), CZ, measure -# - Z's are virtual unless tagged as physical (cirq_google.ops.PhysicalZTag) - - -def test_zphase_gates(): - # TODO: Includes cirq.Z, cirq.Z ** t +@pytest.mark.parametrize( + 'op', + [ + cirq.Z(cirq.LineQubit(0)) ** 0.3, + cirq.PhasedXZGate(x_exponent=0.8, z_exponent=0.2, axis_phase_exponent=0.1).on( + cirq.LineQubit(0) + ), + ], +) +def test_single_qubit_gates(op): q0 = cirq.LineQubit(0) props = sample_noise_properties([q0], []) model = NoiseModelFromNoiseProperties(props) - circuit = cirq.Circuit(cirq.Z(q0)) + circuit = cirq.Circuit(op) noisy_circuit = circuit.with_noise(model) - print(noisy_circuit.moments) - assert len(noisy_circuit.moments) == 2 + assert len(noisy_circuit.moments) == 3 + assert len(noisy_circuit.moments[0].operations) == 1 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) + + # Depolarizing noise assert len(noisy_circuit.moments[1].operations) == 1 - noise_op = noisy_circuit.moments[1].operations[0] - assert isinstance(noise_op.gate, cirq.KrausChannel) - noise_choi = cirq.kraus_to_choi(cirq.kraus(noise_op)) + depol_op = noisy_circuit.moments[1].operations[0] + assert isinstance(depol_op.gate, cirq.DepolarizingChannel) + assert np.isclose(depol_op.gate.p, 0.00081252) + + # Thermal noise + assert len(noisy_circuit.moments[2].operations) == 1 + thermal_op = noisy_circuit.moments[2].operations[0] + assert isinstance(thermal_op.gate, cirq.KrausChannel) + thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) assert np.allclose( - noise_choi, + thermal_choi, [ [1, 0, 0, 9.99750031e-01], [0, 2.49968753e-04, 0, 0], @@ -107,34 +86,112 @@ def test_zphase_gates(): [9.99750031e-01, 0, 0, 9.99750031e-01], ], ) - assert False - -def test_physical_zphase_gates(): - # TODO: Includes cirq.Z, cirq.Z ** t with PhysicalZTag - assert False +@pytest.mark.parametrize( + 'op', + [ + cirq.ISWAP(*cirq.LineQubit.range(2)) ** 0.6, + cirq.CZ(*cirq.LineQubit.range(2)) ** 0.3, + ], +) +def test_two_qubit_gates(op): + q0, q1 = cirq.LineQubit.range(2) + props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + model = NoiseModelFromNoiseProperties(props) + circuit = cirq.Circuit(op) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 3 + assert len(noisy_circuit.moments[0].operations) == 1 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) -def test_microwave_gates(): - # TODO: Includes cirq.X, cirq.Y - assert False + # Depolarizing noise + assert len(noisy_circuit.moments[1].operations) == 1 + depol_op = noisy_circuit.moments[1].operations[0] + assert isinstance(depol_op.gate, cirq.DepolarizingChannel) + assert np.isclose(depol_op.gate.p, 0.00952008) + + # Thermal noise + assert len(noisy_circuit.moments[2].operations) == 2 + thermal_op_0 = noisy_circuit.moments[2].operation_at(q0) + thermal_op_1 = noisy_circuit.moments[2].operation_at(q1) + assert isinstance(thermal_op_0.gate, cirq.KrausChannel) + assert isinstance(thermal_op_1.gate, cirq.KrausChannel) + thermal_choi_0 = cirq.kraus_to_choi(cirq.kraus(thermal_op_0)) + print(thermal_choi_0) + thermal_choi_1 = cirq.kraus_to_choi(cirq.kraus(thermal_op_1)) + # TODO: check iswap noise + expected_thermal_choi = np.array( + [ + [1, 0, 0, 9.99680051e-01], + [0, 3.19948805e-04, 0, 0], + [0, 0, 0, 0], + [9.99680051e-01, 0, 0, 9.99680051e-01], + ] + ) + assert np.allclose(thermal_choi_0, expected_thermal_choi) + assert np.allclose(thermal_choi_1, expected_thermal_choi) -def test_iswap_gates(): - # TODO: Includes cirq.ISWAP, cirq.ISWAP ** 0.5 - assert False +def test_measure_gates(): + q00, q01, q10, q11 = cirq.GridQubit.rect(2, 2) + qubits = [q00, q01, q10, q11] + props = sample_noise_properties( + qubits, + [ + (q00, q01), + (q01, q00), + (q10, q11), + (q11, q10), + (q00, q10), + (q10, q00), + (q01, q11), + (q11, q01), + ], + ) + model = NoiseModelFromNoiseProperties(props) + op = cirq.measure(*qubits, key='m') + circuit = cirq.Circuit(cirq.measure(*qubits, key='m')) + noisy_circuit = circuit.with_noise(model) + print(noisy_circuit.moments) + assert len(noisy_circuit.moments) == 2 + # Amplitude damping before measurement + assert len(noisy_circuit.moments[0].operations) == 4 + for q in qubits: + op = noisy_circuit.moments[0].operation_at(q) + assert isinstance(op.gate, cirq.GeneralizedAmplitudeDampingChannel), q + assert np.isclose(op.gate.p, 0.90909090), q + assert np.isclose(op.gate.gamma, 0.011), q -def test_cz_gates(): - # TODO: includes cirq.CZ - assert False + # Original measurement is after the noise. + assert len(noisy_circuit.moments[1].operations) == 1 + # Measurements are untagged during reconstruction. + assert noisy_circuit.moments[1] == circuit.moments[0] -def test_measure_gates(): - # TODO: includes single- and multi-qubit measure - assert False +def test_wait_gates(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + model = NoiseModelFromNoiseProperties(props) + op = cirq.wait(q0, nanos=100) + circuit = cirq.Circuit(op) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 2 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) + # No depolarizing noise because WaitGate has none. -def test_wait_gates(): - # TODO: includes single- and multi-qubit measure - assert False + assert len(noisy_circuit.moments[1].operations) == 1 + thermal_op = noisy_circuit.moments[1].operations[0] + assert isinstance(thermal_op.gate, cirq.KrausChannel) + thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) + assert np.allclose( + thermal_choi, + [ + [1, 0, 0, 9.990005e-01], + [0, 9.99500167e-04, 0, 0], + [0, 0, 0, 0], + [9.990005e-01, 0, 0, 9.990005e-01], + ], + ) diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py index e030f54c417..933e7e72396 100644 --- a/cirq-core/cirq/devices/thermal_noise_model.py +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -3,7 +3,7 @@ from scipy.linalg import expm import numpy as np -from cirq import devices, ops, qis +from cirq import devices, ops, protocols, qis from cirq.devices.noise_utils import ( PHYSICAL_GATE_TAG, ) @@ -108,6 +108,7 @@ class ThermalNoiseModel(devices.NoiseModel): gate_durations_ns: Dict[type, float] rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] require_physical_tag: bool = True + skip_measurements: bool = True @staticmethod def create( @@ -117,6 +118,7 @@ def create( cool_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, require_physical_tag: bool = True, + skip_measurements: bool = True, ) -> 'ThermalNoiseModel': """Construct a ThermalNoiseModel data object. @@ -136,12 +138,15 @@ def create( so that the cooling Lindbldian is gc(a • a^dag - 0.5{n, •}) This number is equivalent to 1/T1. - dephase_rate_GHz: single number (units GHz) specifying dephasing rate, - either per qubit, or global value for all. + dephase_rate_GHz: single number (units GHz) specifying dephasing + rate, either per qubit, or global value for all. Given a rate gd, Lindblad op will be sqrt(2*gd)*n where n = a^dag * a, so that the dephasing Lindbldian is 2 * gd * (n • n - 0.5{n^2, •}). This number is equivalent to 1/Tphi. + require_physical_tag: whether to only apply noise to operations + tagged with PHYSICAL_GATE_TAG. + skip_measurements: whether to skip applying noise to measurements. Returns: The ThermalNoiseModel with specified parameters. @@ -189,9 +194,6 @@ def noisy_moment( noise_ops: List['cirq.Operation'] = [] moment_ns: float = 0 for op in moment: - if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: - # Only non-virtual gates get noise applied. - return [moment] op_duration: Optional[float] = None for key, duration in self.gate_durations_ns.items(): if not issubclass(type(op.gate), key): @@ -209,6 +211,12 @@ def noisy_moment( moment_ns = max(moment_ns, op_duration) for qubit in system_qubits: + op = moment.operation_at(qubit) + if self.skip_measurements and protocols.is_measurement(op): + continue + if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: + # Only non-virtual gates get noise applied. + continue rates = self.rate_matrix_GHz[qubit] * moment_ns num_op = np.diag(np.sqrt(np.diag(rates))) annihilation = np.sqrt(np.triu(rates, 1)) @@ -219,4 +227,6 @@ def noisy_moment( superop = expm(L.real) kraus_ops = qis.superoperator_to_kraus(superop) noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit)) + if not noise_ops: + return [moment] return [moment, ops.Moment(noise_ops)] diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index 525bd8813ca..b2711337c23 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -10,7 +10,19 @@ ) -SYMMETRIC_TWO_QUBIT_GATES = {cirq.FSimGate, cirq.ISwapPowGate, cirq.CZPowGate} +SINGLE_QUBIT_GATES = { + cirq.ZPowGate, + cirq.PhasedXZGate, + cirq.MeasurementGate, + cirq.ResetChannel, +} +SYMMETRIC_TWO_QUBIT_GATES = { + cirq_google.SycamoreGate, + cirq.FSimGate, + cirq.PhasedFSimGate, + cirq.ISwapPowGate, + cirq.CZPowGate, +} ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES @@ -48,6 +60,14 @@ def __attrs_post_init__(self): self._validate_symmetric_errors('gate_pauli_errors') self._validate_symmetric_errors('fsim_errors') + @classmethod + def single_qubit_gates(cls) -> Set[type]: + return SINGLE_QUBIT_GATES + + @classmethod + def two_qubit_gates(cls) -> Set[type]: + return TWO_QUBIT_GATES + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: if self._depolarizing_error: return self._depolarizing_error @@ -55,7 +75,7 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: depol_errors = super().get_depolarizing_error() for op_id in depol_errors: - if op_id.gate not in TWO_QUBIT_GATES: + if op_id.gate not in self.two_qubit_gates(): continue if len(op_id.qubits) != 2: raise ValueError( @@ -82,8 +102,8 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: """Construct all NoiseModels associated with NoiseProperties.""" noise_models = super().build_noise_models() - # Insert ntangling gate coherent errors after thermal error. - if self.fsim_errors is not None: + # Insert entangling gate coherent errors after thermal error. + if self.fsim_errors: fsim_ops = {op_id: gate.on(*op_id.qubits) for op_id, gate in self.fsim_errors.items()} noise_models.insert(1, devices.InsertionNoiseModel(ops_added=fsim_ops)) diff --git a/cirq-google/cirq_google/devices/google_noise_properties_test.py b/cirq-google/cirq_google/devices/google_noise_properties_test.py index 86886b7b741..502e905aabe 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties_test.py +++ b/cirq-google/cirq_google/devices/google_noise_properties_test.py @@ -1,14 +1,20 @@ from typing import Dict, List, Tuple +from cirq.ops.common_channels import GeneralizedAmplitudeDampingChannel +from cirq.ops.fsim_gate import PhasedFSimGate import numpy as np +import pytest import cirq, cirq_google # from cirq.testing import assert_equivalent_op_tree -from cirq.devices.noise_properties import ( +from cirq_google.devices.google_noise_properties import ( SYMMETRIC_TWO_QUBIT_GATES, SINGLE_QUBIT_GATES, TWO_QUBIT_GATES, ) -from cirq.devices.noise_utils import OpIdentifier +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, +) from cirq_google.devices.google_noise_properties import ( GoogleNoiseProperties, @@ -21,7 +27,11 @@ cirq.MeasurementGate: 4000.0, cirq.ResetChannel: 250.0, cirq.PhasedXZGate: 25.0, + # SYC is normally 12ns, but setting it equal to other two-qubit gates + # simplifies the tests. + cirq_google.SycamoreGate: 32.0, cirq.FSimGate: 32.0, + cirq.PhasedFSimGate: 32.0, cirq.ISwapPowGate: 32.0, cirq.CZPowGate: 32.0, # cirq.WaitGate is a special case. @@ -50,40 +60,171 @@ def sample_noise_properties( ) -def test_model_from_props(): - system_qubits = cirq.GridQubit.rect(2, 2) - qubit_pairs = [ - (cirq.GridQubit(0, 0), cirq.GridQubit(0, 1)), - (cirq.GridQubit(0, 0), cirq.GridQubit(1, 0)), - (cirq.GridQubit(0, 1), cirq.GridQubit(0, 0)), - (cirq.GridQubit(0, 1), cirq.GridQubit(1, 1)), - (cirq.GridQubit(1, 1), cirq.GridQubit(0, 1)), - (cirq.GridQubit(1, 1), cirq.GridQubit(1, 0)), - (cirq.GridQubit(1, 0), cirq.GridQubit(0, 0)), - (cirq.GridQubit(1, 0), cirq.GridQubit(1, 1)), - ] - props = sample_noise_properties(system_qubits, qubit_pairs) +# TODO: all the other tests + + +def test_zphase_gates(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) model = NoiseModelFromGoogleNoiseProperties(props) + circuit = cirq.Circuit(cirq.Z(q0) ** 0.3) + noisy_circuit = circuit.with_noise(model) + assert noisy_circuit == circuit + - circuit = cirq.Circuit( - cirq.H(system_qubits[0]), - cirq.H(system_qubits[2]), - cirq.CNOT(*system_qubits[0:2]), - cirq.CNOT(*system_qubits[2:4]), - cirq.measure(*system_qubits, key='m'), +@pytest.mark.parametrize( + 'op', + [ + (cirq.Z(cirq.LineQubit(0)) ** 0.3).with_tags(cirq_google.PhysicalZTag), + cirq.PhasedXZGate(x_exponent=0.8, z_exponent=0.2, axis_phase_exponent=0.1).on( + cirq.LineQubit(0) + ), + ], +) +def test_single_qubit_gates(op): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + model = NoiseModelFromGoogleNoiseProperties(props) + circuit = cirq.Circuit(op) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 3 + assert len(noisy_circuit.moments[0].operations) == 1 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) + + # Depolarizing noise + assert len(noisy_circuit.moments[1].operations) == 1 + depol_op = noisy_circuit.moments[1].operations[0] + assert isinstance(depol_op.gate, cirq.DepolarizingChannel) + assert np.isclose(depol_op.gate.p, 0.00081252) + + # Thermal noise + assert len(noisy_circuit.moments[2].operations) == 1 + thermal_op = noisy_circuit.moments[2].operations[0] + assert isinstance(thermal_op.gate, cirq.KrausChannel) + thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) + assert np.allclose( + thermal_choi, + [ + [1, 0, 0, 9.99750031e-01], + [0, 2.49968753e-04, 0, 0], + [0, 0, 0, 0], + [9.99750031e-01, 0, 0, 9.99750031e-01], + ], ) - print('circuit') - print(circuit) - syc_circuit = cirq_google.optimized_for_sycamore(circuit) - print('syc_circuit') - print(syc_circuit) - noisy_circuit = syc_circuit.with_noise(model) - print('noisy_circuit') + + +@pytest.mark.parametrize( + 'op', + [ + cirq.ISWAP(*cirq.LineQubit.range(2)) ** 0.6, + cirq.CZ(*cirq.LineQubit.range(2)) ** 0.3, + cirq_google.SYC(*cirq.LineQubit.range(2)), + ], +) +def test_two_qubit_gates(op): + q0, q1 = cirq.LineQubit.range(2) + props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + model = NoiseModelFromGoogleNoiseProperties(props) + circuit = cirq.Circuit(op) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 4 + assert len(noisy_circuit.moments[0].operations) == 1 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) + + # Depolarizing noise + assert len(noisy_circuit.moments[1].operations) == 1 + depol_op = noisy_circuit.moments[1].operations[0] + assert isinstance(depol_op.gate, cirq.DepolarizingChannel) + assert np.isclose(depol_op.gate.p, 0.00719705) + + # FSim angle corrections + assert len(noisy_circuit.moments[2].operations) == 1 + fsim_op = noisy_circuit.moments[2].operations[0] + assert isinstance(fsim_op.gate, cirq.PhasedFSimGate) + assert fsim_op == PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02).on( + q0, q1 + ) + + # Thermal noise + assert len(noisy_circuit.moments[3].operations) == 2 + thermal_op_0 = noisy_circuit.moments[3].operation_at(q0) + thermal_op_1 = noisy_circuit.moments[3].operation_at(q1) + assert isinstance(thermal_op_0.gate, cirq.KrausChannel) + assert isinstance(thermal_op_1.gate, cirq.KrausChannel) + thermal_choi_0 = cirq.kraus_to_choi(cirq.kraus(thermal_op_0)) + thermal_choi_1 = cirq.kraus_to_choi(cirq.kraus(thermal_op_1)) + # TODO: check iswap noise + expected_thermal_choi = np.array( + [ + [1, 0, 0, 9.99680051e-01], + [0, 3.19948805e-04, 0, 0], + [0, 0, 0, 0], + [9.99680051e-01, 0, 0, 9.99680051e-01], + ] + ) + assert np.allclose(thermal_choi_0, expected_thermal_choi) + assert np.allclose(thermal_choi_1, expected_thermal_choi) + + +def test_measure_gates(): + q00, q01, q10, q11 = cirq.GridQubit.rect(2, 2) + qubits = [q00, q01, q10, q11] + props = sample_noise_properties( + qubits, + [ + (q00, q01), + (q01, q00), + (q10, q11), + (q11, q10), + (q00, q10), + (q10, q00), + (q01, q11), + (q11, q01), + ], + ) + model = NoiseModelFromGoogleNoiseProperties(props) + op = cirq.measure(*qubits, key='m') + circuit = cirq.Circuit(cirq.measure(*qubits, key='m')) + noisy_circuit = circuit.with_noise(model) print(noisy_circuit.moments) - choi = cirq.kraus_to_choi(cirq.kraus(noisy_circuit.moments[4].operations[0])) - print(choi) + assert len(noisy_circuit.moments) == 2 - assert False + # Amplitude damping before measurement + assert len(noisy_circuit.moments[0].operations) == 4 + for q in qubits: + op = noisy_circuit.moments[0].operation_at(q) + assert isinstance(op.gate, cirq.GeneralizedAmplitudeDampingChannel), q + assert np.isclose(op.gate.p, 0.90909090), q + assert np.isclose(op.gate.gamma, 0.011), q + # Original measurement is after the noise. + assert len(noisy_circuit.moments[1].operations) == 1 + # Measurements are untagged during reconstruction. + assert noisy_circuit.moments[1] == circuit.moments[0] -# TODO: all the other tests + +def test_wait_gates(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + model = NoiseModelFromGoogleNoiseProperties(props) + op = cirq.wait(q0, nanos=100) + circuit = cirq.Circuit(op) + noisy_circuit = circuit.with_noise(model) + assert len(noisy_circuit.moments) == 2 + assert noisy_circuit.moments[0].operations[0] == op.with_tags(PHYSICAL_GATE_TAG) + + # No depolarizing noise because WaitGate has none. + + assert len(noisy_circuit.moments[1].operations) == 1 + thermal_op = noisy_circuit.moments[1].operations[0] + assert isinstance(thermal_op.gate, cirq.KrausChannel) + thermal_choi = cirq.kraus_to_choi(cirq.kraus(thermal_op)) + assert np.allclose( + thermal_choi, + [ + [1, 0, 0, 9.990005e-01], + [0, 9.99500167e-04, 0, 0], + [0, 0, 0, 0], + [9.990005e-01, 0, 0, 9.990005e-01], + ], + ) From b32ca22c09b75b322bfb9d859bbec4f97175e022 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 5 Nov 2021 13:00:38 -0700 Subject: [PATCH 06/13] serialization working modulo sorting --- cirq-core/cirq/devices/noise_properties.py | 73 +++++++- .../cirq/devices/noise_properties_test.py | 17 +- cirq-core/cirq/devices/noise_utils.py | 3 +- cirq-core/cirq/json_resolver_cache.py | 2 + .../json_test_data/NoiseProperties.json | 174 ++++++++++++++++++ .../json_test_data/NoiseProperties.repr | 21 +++ 6 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 cirq-core/cirq/protocols/json_test_data/NoiseProperties.json create mode 100644 cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 181c7086df3..98aa7fb4012 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -4,6 +4,7 @@ import numpy as np from cirq import ops, protocols, devices +from cirq._compat import proper_repr from cirq.devices.noise_utils import ( OpIdentifier, PHYSICAL_GATE_TAG, @@ -29,7 +30,7 @@ class NoiseProperties: gate_times_ns: Dict[type, float] T1_ns: Dict['cirq.Qid', float] Tphi_ns: Dict['cirq.Qid', float] - ro_fidelities: Dict['cirq.Qid', np.ndarray] + ro_fidelities: Dict['cirq.Qid', List[float]] gate_pauli_errors: Dict[OpIdentifier, float] _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) @@ -199,6 +200,76 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: return noise_models + def __str__(self) -> str: + return 'NoiseProperties' + + def _repr_args(self) -> List[str]: + args = [] + gate_times_repr = ', '.join( + f'{key.__module__}.{key.__qualname__}: {val}' + for key, val in self.gate_times_ns.items() + ) + args.append(f' gate_times_ns={{{gate_times_repr}}}') + args.append(f' T1_ns={self.T1_ns!r}') + args.append(f' Tphi_ns={self.Tphi_ns!r}') + args.append(f' ro_fidelities={self.ro_fidelities!r}') + args.append(f' gate_pauli_errors={self.gate_pauli_errors!r}') + return args + + def __repr__(self) -> str: + args_str = ',\n'.join(self._repr_args()) + return f'cirq.NoiseProperties(\n{args_str}\n)' + + def _json_dict_(self): + canonical_gate = { + ops.ZPowGate: ops.ZPowGate(), + ops.PhasedXZGate: ops.PhasedXZGate(x_exponent=0, z_exponent=0, axis_phase_exponent=0), + ops.MeasurementGate: ops.MeasurementGate(num_qubits=1), + ops.ResetChannel: ops.ResetChannel(), + ops.FSimGate: ops.FSimGate(theta=0, phi=0), + ops.PhasedFSimGate: ops.PhasedFSimGate(theta=0), + ops.ISwapPowGate: ops.ISwapPowGate(), + ops.CZPowGate: ops.CZPowGate() + } + + storage_gate_times = {canonical_gate[key]: val for key, val in self.gate_times_ns.items()} + storage_pauli_errors = { + canonical_gate[op_id.gate].on(*op_id.qubits): val + for op_id, val in self.gate_pauli_errors.items() + } + return { + 'cirq_type': 'NoiseProperties', + # JSON requires mappings to have keys of basic types. + # TODO: Pairs must be sorted to ensure consistent serialization. + 'gate_times_ns': list(storage_gate_times.items()), + 'T1_ns': list(self.T1_ns.items()), + 'Tphi_ns': list(self.Tphi_ns.items()), + 'ro_fidelities': list(self.ro_fidelities.items()), + 'gate_pauli_errors': list(storage_pauli_errors.items()), + } + + @classmethod + def _from_json_dict_( + cls, + gate_times_ns, + T1_ns, + Tphi_ns, + ro_fidelities, + gate_pauli_errors, + **kwargs + ): + gate_type_times = {type(gate): val for gate, val in gate_times_ns} + gate_type_pauli_errors = { + OpIdentifier(type(op.gate), *op.qubits): val for op, val in gate_pauli_errors + } + return NoiseProperties( + gate_type_times, + {k: v for k, v in T1_ns}, + {k: v for k, v in Tphi_ns}, + {k: v for k, v in ro_fidelities}, + gate_type_pauli_errors + ) + class NoiseModelFromNoiseProperties(devices.NoiseModel): def __init__(self, noise_properties: NoiseProperties) -> None: diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index 3b89901ea5e..cbfc5c0b574 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -39,7 +39,7 @@ def sample_noise_properties( gate_times_ns=DEFAULT_GATE_NS, T1_ns={q: 1e5 for q in system_qubits}, Tphi_ns={q: 2e5 for q in system_qubits}, - ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, + ro_fidelities={q: [0.001, 0.01] for q in system_qubits}, gate_pauli_errors={ **{OpIdentifier(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, **{OpIdentifier(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, @@ -47,6 +47,21 @@ def sample_noise_properties( ) +def test_repr_evaluation(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + props_from_repr = eval(repr(props)) + assert props_from_repr == props + + +def test_json_serialization(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + props_json = cirq.to_json(props) + props_from_json = cirq.read_json(json_text=props_json) + assert props_from_json == props + + @pytest.mark.parametrize( 'op', [ diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 0690fbd4d83..58bcdf7c372 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -31,7 +31,8 @@ def __str__(self): return f'{self.gate}' def __repr__(self) -> str: - return f'OpIdentifier({self.gate.__qualname__}, *{self.qubits})' + fullname = f'{self.gate.__module__}.{self.gate.__qualname__}' + return f'cirq.devices.noise_utils.OpIdentifier({fullname}, *{self.qubits})' # TODO: expose all from top-level cirq diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index f18fbc90c91..0ff17b782ee 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -114,6 +114,8 @@ def _parallel_gate_op(gate, qubits): 'NamedQubit': cirq.NamedQubit, 'NamedQid': cirq.NamedQid, 'NoIdentifierQubit': cirq.testing.NoIdentifierQubit, + 'NoiseProperties': cirq.NoiseProperties, + 'NoiseModelFromNoiseProperties': cirq.NoiseModelFromNoiseProperties, '_PauliX': cirq.ops.pauli_gates._PauliX, '_PauliY': cirq.ops.pauli_gates._PauliY, '_PauliZ': cirq.ops.pauli_gates._PauliZ, diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json new file mode 100644 index 00000000000..791a8c29804 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json @@ -0,0 +1,174 @@ +{ + "cirq_type": "NoiseProperties", + "gate_times_ns": [ + [ + { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 25.0 + ], + [ + { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + 4000.0 + ], + [ + { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + 250.0 + ], + [ + { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + 25.0 + ], + [ + { + "cirq_type": "FSimGate", + "theta": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "PhasedFSimGate", + "theta": 0.0, + "zeta": 0.0, + "chi": 0.0, + "gamma": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "ISwapPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "CZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ] + ], + "T1_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 100000.0 + ] + ], + "Tphi_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 200000.0 + ] + ], + "ro_fidelities": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + [ + 0.001, + 0.01 + ] + ] + ], + "gate_pauli_errors": [ + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ] + ] + } \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr new file mode 100644 index 00000000000..2682c34bd60 --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr @@ -0,0 +1,21 @@ +cirq.NoiseProperties( + gate_times_ns={ + cirq.ops.common_gates.ZPowGate: 25.0, + cirq.ops.measurement_gate.MeasurementGate: 4000.0, + cirq.ops.common_channels.ResetChannel: 250.0, + cirq.ops.phased_x_z_gate.PhasedXZGate: 25.0, + cirq.ops.fsim_gate.FSimGate: 32.0, + cirq.ops.fsim_gate.PhasedFSimGate: 32.0, + cirq.ops.swap_gates.ISwapPowGate: 32.0, + cirq.ops.common_gates.CZPowGate: 32.0 + }, + T1_ns={cirq.LineQubit(0): 100000.0}, + Tphi_ns={cirq.LineQubit(0): 200000.0}, + ro_fidelities={cirq.LineQubit(0): [0.001, 0.01]}, + gate_pauli_errors={ + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(0),)): 0.001 + } +) \ No newline at end of file From ca16550b337987cf631c013cf39c74e31cb343c5 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 5 Nov 2021 13:13:49 -0700 Subject: [PATCH 07/13] Serialization working --- cirq-core/cirq/devices/noise_properties.py | 10 +- cirq-core/cirq/json_resolver_cache.py | 1 - .../json_test_data/NoiseProperties.json | 318 +++++++++--------- .../json_test_data/NoiseProperties.repr | 8 +- .../cirq/protocols/json_test_data/spec.py | 1 + 5 files changed, 169 insertions(+), 169 deletions(-) diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 98aa7fb4012..09930ce944d 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -241,11 +241,11 @@ def _json_dict_(self): 'cirq_type': 'NoiseProperties', # JSON requires mappings to have keys of basic types. # TODO: Pairs must be sorted to ensure consistent serialization. - 'gate_times_ns': list(storage_gate_times.items()), - 'T1_ns': list(self.T1_ns.items()), - 'Tphi_ns': list(self.Tphi_ns.items()), - 'ro_fidelities': list(self.ro_fidelities.items()), - 'gate_pauli_errors': list(storage_pauli_errors.items()), + 'gate_times_ns': sorted(storage_gate_times.items(), key=str), + 'T1_ns': sorted(self.T1_ns.items()), + 'Tphi_ns': sorted(self.Tphi_ns.items()), + 'ro_fidelities': sorted(self.ro_fidelities.items()), + 'gate_pauli_errors': sorted(storage_pauli_errors.items(), key=str), } @classmethod diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 0ff17b782ee..6c532f641b4 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -115,7 +115,6 @@ def _parallel_gate_op(gate, qubits): 'NamedQid': cirq.NamedQid, 'NoIdentifierQubit': cirq.testing.NoIdentifierQubit, 'NoiseProperties': cirq.NoiseProperties, - 'NoiseModelFromNoiseProperties': cirq.NoiseModelFromNoiseProperties, '_PauliX': cirq.ops.pauli_gates._PauliX, '_PauliY': cirq.ops.pauli_gates._PauliY, '_PauliZ': cirq.ops.pauli_gates._PauliZ, diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json index 791a8c29804..81625633699 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json @@ -1,174 +1,174 @@ { - "cirq_type": "NoiseProperties", - "gate_times_ns": [ - [ - { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, - 25.0 - ], - [ - { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, - 4000.0 - ], - [ - { - "cirq_type": "ResetChannel", - "dimension": 2 - }, - 250.0 - ], + "cirq_type": "NoiseProperties", + "gate_times_ns": [ + [ + { + "cirq_type": "CZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "FSimGate", + "theta": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "ISwapPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + 4000.0 + ], + [ + { + "cirq_type": "PhasedFSimGate", + "theta": 0.0, + "zeta": 0.0, + "chi": 0.0, + "gamma": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + 25.0 + ], + [ + { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + 250.0 + ], + [ + { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 25.0 + ] + ], + "T1_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 100000.0 + ] + ], + "Tphi_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 200000.0 + ] + ], + "ro_fidelities": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, [ - { + 0.001, + 0.01 + ] + ] + ], + "gate_pauli_errors": [ + [ + { + "cirq_type": "GateOperation", + "gate": { "cirq_type": "PhasedXZGate", "axis_phase_exponent": 0, "x_exponent": 0, "z_exponent": 0 }, - 25.0 - ], - [ - { - "cirq_type": "FSimGate", - "theta": 0.0, - "phi": 0.0 - }, - 32.0 - ], - [ - { - "cirq_type": "PhasedFSimGate", - "theta": 0.0, - "zeta": 0.0, - "chi": 0.0, - "gamma": 0.0, - "phi": 0.0 - }, - 32.0 - ], - [ - { - "cirq_type": "ISwapPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, - 32.0 - ], - [ - { - "cirq_type": "CZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, - 32.0 - ] - ], - "T1_ns": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 - }, - 100000.0 - ] + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 ], - "Tphi_ns": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ResetChannel", + "dimension": 2 }, - 200000.0 - ] + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 ], - "ro_fidelities": [ - [ - { - "cirq_type": "LineQubit", - "x": 0 + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 }, - [ - 0.001, - 0.01 + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } ] - ] + }, + 0.001 ], - "gate_pauli_errors": [ - [ - { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ResetChannel", - "dimension": 2 - }, - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] - }, - 0.001 - ], - [ - { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, - "qubits": [ - { - "cirq_type": "LineQubit", - "x": 0 - } - ] + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] }, - 0.001 - ] + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 ] - } \ No newline at end of file + ] +} diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr index 2682c34bd60..1f09c65684d 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr @@ -13,9 +13,9 @@ cirq.NoiseProperties( Tphi_ns={cirq.LineQubit(0): 200000.0}, ro_fidelities={cirq.LineQubit(0): [0.001, 0.01]}, gate_pauli_errors={ - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(0),)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(0),)): 0.001, cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, *(cirq.LineQubit(0),)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(0),)): 0.001 + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(0),)): 0.001 } -) \ No newline at end of file +) diff --git a/cirq-core/cirq/protocols/json_test_data/spec.py b/cirq-core/cirq/protocols/json_test_data/spec.py index a1602e8e7f2..3c8d8ea259f 100644 --- a/cirq-core/cirq/protocols/json_test_data/spec.py +++ b/cirq-core/cirq/protocols/json_test_data/spec.py @@ -174,6 +174,7 @@ 'ParamDictType', # utility: 'CliffordSimulator', + 'NoiseModelFromNoiseProperties', 'Simulator', 'StabilizerSampler', 'Unique', From 5314872873fd71935b3d453427fdc88e4069f4ef Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 12:28:53 -0800 Subject: [PATCH 08/13] a veritable cornucopia of test coverage --- .../cirq/devices/insertion_noise_model.py | 21 +- .../devices/insertion_noise_model_test.py | 4 + cirq-core/cirq/devices/noise_properties.py | 151 ++--- .../cirq/devices/noise_properties_test.py | 160 ++++++ cirq-core/cirq/devices/noise_utils.py | 23 +- cirq-core/cirq/devices/noise_utils_test.py | 119 ++++ cirq-core/cirq/devices/thermal_noise_model.py | 43 +- .../cirq/devices/thermal_noise_model_test.py | 214 +++++++ .../json_test_data/NoiseProperties.json | 3 +- .../json_test_data/NoiseProperties.repr | 8 +- cirq-google/cirq_google/__init__.py | 2 + cirq-google/cirq_google/devices/__init__.py | 5 + .../devices/google_noise_properties.py | 114 ++-- .../devices/google_noise_properties_test.py | 88 ++- .../calibration_to_noise_properties_test.py | 119 ++++ .../cirq_google/json_resolver_cache.py | 1 + .../json_test_data/GoogleNoiseProperties.json | 523 ++++++++++++++++++ .../json_test_data/GoogleNoiseProperties.repr | 57 ++ .../cirq_google/json_test_data/spec.py | 1 + 19 files changed, 1493 insertions(+), 163 deletions(-) create mode 100644 cirq-core/cirq/devices/insertion_noise_model_test.py create mode 100644 cirq-core/cirq/devices/noise_utils_test.py create mode 100644 cirq-core/cirq/devices/thermal_noise_model_test.py create mode 100644 cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json create mode 100644 cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.repr diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index e18a1cdb7cf..872d2d5d94d 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -1,5 +1,5 @@ from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Callable, Dict, Sequence, Union +from typing import TYPE_CHECKING, Dict, List, Sequence from cirq import devices, ops from cirq.devices.noise_utils import ( @@ -21,38 +21,27 @@ class InsertionNoiseModel(devices.NoiseModel): Args: ops_added: a map of gate types (and optionally, qubits they act on) to - either an operations that should be added OR a function that - generates the operation to add. + operations that should be added. prepend: whether to add the new moment before the current one. require_physical_tag: whether to only apply noise to operations tagged with PHYSICAL_GATE_TAG. """ - ops_added: Dict[ - OpIdentifier, Union['cirq.Operation', Callable[['cirq.Operation'], 'cirq.Operation']] - ] = field(default_factory=dict) + ops_added: Dict[OpIdentifier, 'cirq.Operation'] = field(default_factory=dict) prepend: bool = False require_physical_tag: bool = True def noisy_moment( self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] ) -> 'cirq.OP_TREE': - noise_ops = [] + noise_ops: List['cirq.Operation'] = [] for op in moment: if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: # Only non-virtual gates get noise applied. continue op_id = OpIdentifier(type(op.gate), *op.qubits) if op_id in self.ops_added: - val = self.ops_added[op_id] - if isinstance(val, ops.Operation): - noise_ops.append(val) - else: - noise_ops.append(val(op)) - elif op_id.gate_only() in self.ops_added: - noise_ops.append(self.ops_added[op_id.gate_only()]) - else: - continue + noise_ops.append(self.ops_added[op_id]) if not noise_ops: return [moment] if self.prepend: diff --git a/cirq-core/cirq/devices/insertion_noise_model_test.py b/cirq-core/cirq/devices/insertion_noise_model_test.py new file mode 100644 index 00000000000..25cfe4f42e6 --- /dev/null +++ b/cirq-core/cirq/devices/insertion_noise_model_test.py @@ -0,0 +1,4 @@ + + +def test_insertion_noise(): + assert False diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 09930ce944d..3dc2b6ac289 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,10 +1,8 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice from dataclasses import dataclass, field from typing import Dict, Iterable, Sequence, TYPE_CHECKING, List, Set -import numpy as np from cirq import ops, protocols, devices -from cirq._compat import proper_repr from cirq.devices.noise_utils import ( OpIdentifier, PHYSICAL_GATE_TAG, @@ -14,63 +12,82 @@ if TYPE_CHECKING: import cirq -SINGLE_QUBIT_GATES = { +SINGLE_QUBIT_GATES: Set[type] = { ops.ZPowGate, ops.PhasedXZGate, ops.MeasurementGate, ops.ResetChannel, } -SYMMETRIC_TWO_QUBIT_GATES = {ops.FSimGate, ops.PhasedFSimGate, ops.ISwapPowGate, ops.CZPowGate} +SYMMETRIC_TWO_QUBIT_GATES: Set[type] = { + ops.FSimGate, + ops.PhasedFSimGate, + ops.ISwapPowGate, + ops.CZPowGate, +} ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES @dataclass class NoiseProperties: + """Noise-defining properties for a quantum device. + + Args: + gate_times_ns: Dict[type, float] of gate types to their duration on + Google hardware. + T1_ns: Dict[cirq.Qid, float] of qubits to their T1 time, in ns. + Tphi_ns: Dict[cirq.Qid, float] of qubits to their Tphi time, in ns. + ro_fidelities: Dict[cirq.Qid, np.ndarray] of qubits to their readout + fidelity matrix. + gate_pauli_errors: Dict[OpIdentifier, float] of gate types + (potentially on specific qubits) to the Pauli error for that gate. + validate: If True, performs validation on input arguments. Defaults + to True. + """ + gate_times_ns: Dict[type, float] T1_ns: Dict['cirq.Qid', float] Tphi_ns: Dict['cirq.Qid', float] ro_fidelities: Dict['cirq.Qid', List[float]] gate_pauli_errors: Dict[OpIdentifier, float] + validate: bool = True _qubits: List['cirq.Qid'] = field(init=False, default_factory=list) _depolarizing_error: Dict[OpIdentifier, float] = field(init=False, default_factory=dict) - def __attrs_post_init__(self): + def __post_init__(self): + if not self.validate: + return t1_qubits = set(self.T1_ns) tphi_qubits = set(self.Tphi_ns) if t1_qubits != tphi_qubits: - raise ValueError(f'Keys specified for T1 and Tphi are not' f' identical.') + raise ValueError('Keys specified for T1 and Tphi are not identical.') # validate two qubit gate errors. - self._validate_symmetric_errors('entangler_errors') self._validate_symmetric_errors('gate_pauli_errors') - self._validate_symmetric_errors('z_phase_errors') def _validate_symmetric_errors(self, field_name: str) -> None: gate_error_dict = getattr(self, field_name) - if gate_error_dict is None: - return # some fields are optional for op_id in gate_error_dict: if len(op_id.qubits) != 2: # single qubit op_ids also present, or generic values are # specified. Skip these cases if len(op_id.qubits) > 2: raise ValueError( - f'Found gate {op_id.gate} with {len(op_id.qubits)} qubits. ' + f'Found gate {op_id.gate_type} with {len(op_id.qubits)} qubits. ' 'Symmetric errors can only apply to 2-qubit gates.' ) continue - if op_id.gate not in SYMMETRIC_TWO_QUBIT_GATES: - if op_id.gate not in ASYMMETRIC_TWO_QUBIT_GATES: - raise ValueError( - f'Found gate {op_id.gate} which does not appear in the' - 'symmetric or asymmetric gate sets.' - ) - continue - op_id_swapped = (op_id.gate, *op_id.qubits[::-1]) + if op_id.gate_type not in self.two_qubit_gates(): + raise ValueError( + f'Found gate {op_id.gate_type} which does not appear in the ' + 'symmetric or asymmetric gate sets.' + ) + # TODO: this assumes op is symmetric. + # If asymmetric gates are added, we will need to update it. + op_id_swapped = op_id.swapped() if op_id_swapped not in gate_error_dict: raise ValueError( f'Operation {op_id} of field {field_name} has ' @@ -96,6 +113,31 @@ def two_qubit_gates(cls) -> Set[type]: def expected_gates(cls) -> Set[type]: return cls.single_qubit_gates() | cls.two_qubit_gates() + @classmethod + def canonical_gates(cls) -> Dict[type, 'cirq.Gate']: + return { + ops.ZPowGate: ops.ZPowGate(), + ops.PhasedXZGate: ops.PhasedXZGate(x_exponent=0, z_exponent=0, axis_phase_exponent=0), + ops.MeasurementGate: ops.MeasurementGate(num_qubits=1), + ops.ResetChannel: ops.ResetChannel(), + ops.FSimGate: ops.FSimGate(theta=0, phi=0), + ops.PhasedFSimGate: ops.PhasedFSimGate(theta=0), + ops.ISwapPowGate: ops.ISwapPowGate(), + ops.CZPowGate: ops.CZPowGate(), + } + + @classmethod + def get_canonical_gate(cls, gate_type: type) -> 'cirq.Gate': + return cls.canonical_gates()[gate_type] + + @classmethod + def _identifier_to_op(cls, op_id: OpIdentifier) -> 'cirq.Operation': + return cls.get_canonical_gate(op_id.gate_type).on(*op_id.qubits) + + @classmethod + def _op_to_identifier(cls, op: 'cirq.Operation'): + return OpIdentifier(type(op.gate), *op.qubits) + def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: """Returns the portion of Pauli error from depolarization. @@ -106,7 +148,7 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: depol_errors = {} for op_id, p_error in self.gate_pauli_errors.items(): - gate_type = op_id.gate + gate_type = op_id.gate_type if gate_type in self.single_qubit_gates(): if issubclass(gate_type, ops.MeasurementGate): # Non-measurement error can be ignored on measurement gates. @@ -119,8 +161,7 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: q0 = op_id.qubits[0] # Subtract decoherence error. if q0 in self.T1_ns: - rp_0 = decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) - p_error -= rp_0 + p_error -= decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) else: # This must be a 2-qubit gate. @@ -134,15 +175,10 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: # Subtract decoherence error. q0, q1 = op_id.qubits if q0 in self.T1_ns: - rp_0 = decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) - else: - rp_0 = 0 + p_error -= decoherence_pauli_error(self.T1_ns[q0], self.Tphi_ns[q0], time_ns) if q1 in self.T1_ns: - rp_1 = decoherence_pauli_error(self.T1_ns[q1], self.Tphi_ns[q1], time_ns) - else: - rp_1 = 0 + p_error -= decoherence_pauli_error(self.T1_ns[q1], self.Tphi_ns[q1], time_ns) - p_error -= rp_0 + rp_1 depol_errors[op_id] = p_error # memoization is OK self._depolarizing_error = depol_errors @@ -158,7 +194,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: ) if self.T1_ns: # level 1 sophistication noise_models.append( - devices.ThermalNoiseModel.create( + devices.ThermalNoiseModel( {q: 2 for q in self.T1_ns}, self.gate_times_ns, cool_rate_GHz={q: 1 / T1 for q, T1 in self.T1_ns.items()}, @@ -166,7 +202,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: ) ) - gate_types = set(op_id.gate for op_id in self.gate_pauli_errors) + gate_types = set(op_id.gate_type for op_id in self.gate_pauli_errors) if not gate_types.issubset(self.expected_gates()): raise ValueError( 'Some gates are not in the supported set.' @@ -175,6 +211,7 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: depolarizing_error = self.get_depolarizing_error() added_pauli_errors = { + # TODO: handle op_id not having qubits. op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits) for op_id, p_error in depolarizing_error.items() if p_error > 0 @@ -203,71 +240,51 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: def __str__(self) -> str: return 'NoiseProperties' - def _repr_args(self) -> List[str]: + def __repr__(self) -> str: args = [] gate_times_repr = ', '.join( - f'{key.__module__}.{key.__qualname__}: {val}' - for key, val in self.gate_times_ns.items() + f'{key.__module__}.{key.__qualname__}: {val}' for key, val in self.gate_times_ns.items() ) args.append(f' gate_times_ns={{{gate_times_repr}}}') args.append(f' T1_ns={self.T1_ns!r}') args.append(f' Tphi_ns={self.Tphi_ns!r}') args.append(f' ro_fidelities={self.ro_fidelities!r}') args.append(f' gate_pauli_errors={self.gate_pauli_errors!r}') - return args - - def __repr__(self) -> str: - args_str = ',\n'.join(self._repr_args()) + args_str = ',\n'.join(args) return f'cirq.NoiseProperties(\n{args_str}\n)' def _json_dict_(self): - canonical_gate = { - ops.ZPowGate: ops.ZPowGate(), - ops.PhasedXZGate: ops.PhasedXZGate(x_exponent=0, z_exponent=0, axis_phase_exponent=0), - ops.MeasurementGate: ops.MeasurementGate(num_qubits=1), - ops.ResetChannel: ops.ResetChannel(), - ops.FSimGate: ops.FSimGate(theta=0, phi=0), - ops.PhasedFSimGate: ops.PhasedFSimGate(theta=0), - ops.ISwapPowGate: ops.ISwapPowGate(), - ops.CZPowGate: ops.CZPowGate() + storage_gate_times = { + self.get_canonical_gate(key): val for key, val in self.gate_times_ns.items() } - - storage_gate_times = {canonical_gate[key]: val for key, val in self.gate_times_ns.items()} storage_pauli_errors = { - canonical_gate[op_id.gate].on(*op_id.qubits): val - for op_id, val in self.gate_pauli_errors.items() + self._identifier_to_op(op_id): val for op_id, val in self.gate_pauli_errors.items() } return { 'cirq_type': 'NoiseProperties', # JSON requires mappings to have keys of basic types. - # TODO: Pairs must be sorted to ensure consistent serialization. + # Pairs must be sorted to ensure consistent serialization. 'gate_times_ns': sorted(storage_gate_times.items(), key=str), 'T1_ns': sorted(self.T1_ns.items()), 'Tphi_ns': sorted(self.Tphi_ns.items()), 'ro_fidelities': sorted(self.ro_fidelities.items()), 'gate_pauli_errors': sorted(storage_pauli_errors.items(), key=str), + 'validate': self.validate, } @classmethod def _from_json_dict_( - cls, - gate_times_ns, - T1_ns, - Tphi_ns, - ro_fidelities, - gate_pauli_errors, - **kwargs + cls, gate_times_ns, T1_ns, Tphi_ns, ro_fidelities, gate_pauli_errors, validate, **kwargs ): gate_type_times = {type(gate): val for gate, val in gate_times_ns} - gate_type_pauli_errors = { - OpIdentifier(type(op.gate), *op.qubits): val for op, val in gate_pauli_errors - } + op_id_pauli_errors = {cls._op_to_identifier(op): val for op, val in gate_pauli_errors} return NoiseProperties( - gate_type_times, - {k: v for k, v in T1_ns}, - {k: v for k, v in Tphi_ns}, - {k: v for k, v in ro_fidelities}, - gate_type_pauli_errors + gate_times_ns=gate_type_times, + T1_ns={k: v for k, v in T1_ns}, + Tphi_ns={k: v for k, v in Tphi_ns}, + ro_fidelities={k: v for k, v in ro_fidelities}, + gate_pauli_errors=op_id_pauli_errors, + validate=validate, ) diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index cbfc5c0b574..fa56e34c58a 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -47,6 +47,12 @@ def sample_noise_properties( ) +def test_str(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + assert str(props) == 'NoiseProperties' + + def test_repr_evaluation(): q0 = cirq.LineQubit(0) props = sample_noise_properties([q0], []) @@ -62,6 +68,160 @@ def test_json_serialization(): assert props_from_json == props +def test_init_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + with pytest.raises(ValueError, match='Keys specified for T1 and Tphi are not identical.'): + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={}, + ) + + with pytest.raises(ValueError, match='Symmetric errors can only apply to 2-qubit gates.'): + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + ) + + with pytest.raises(ValueError, match='does not appear in the symmetric or asymmetric'): + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.CNOT, q0, q1): 0.1, + OpIdentifier(cirq.CNOT, q1, q0): 0.1, + }, + ) + + with pytest.raises(ValueError, match='has errors but its symmetric id'): + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1): 0.1}, + ) + + # Single-qubit gates are ignored in symmetric-gate validation. + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.ZPowGate, q0): 0.1, + OpIdentifier(cirq.CZPowGate, q0, q1): 0.1, + OpIdentifier(cirq.CZPowGate, q1, q0): 0.1, + }, + ) + + # All errors are ignored if validation is disabled. + _ = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1, + OpIdentifier(cirq.CNOT, q0, q1): 0.1, + }, + validate=False, + ) + + +def test_qubits(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + assert props.qubits == [q0] + # Confirm memoization behavior. + assert props.qubits == [q0] + + +def test_depol_memoization(): + # Verify that depolarizing error is memoized. + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + depol_error_a = props.get_depolarizing_error() + depol_error_b = props.get_depolarizing_error() + assert depol_error_a == depol_error_b + assert depol_error_a is depol_error_b + + +def test_depol_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + # Create unvalidated properties with too many qubits on a Z gate. + z_2q_props = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.ZPowGate, q0, q1): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='only takes one qubit'): + _ = z_2q_props.get_depolarizing_error() + + # Create unvalidated properties with an unsupported gate. + toffoli_props = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='not in the supported gate list'): + _ = toffoli_props.get_depolarizing_error() + + # Create unvalidated properties with too many qubits on a CZ gate. + cz_3q_props = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CZPowGate, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='takes two qubits'): + _ = cz_3q_props.get_depolarizing_error() + + # If T1_ns is missing, values are filled in as needed. + + +def test_build_noise_model_validation(): + q0, q1, q2 = cirq.LineQubit.range(3) + # Create unvalidated properties with mismatched T1 and Tphi qubits. + t1_tphi_props = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={}, + validate=False, + ) + with pytest.raises(ValueError, match='but Tphi has qubits'): + _ = t1_tphi_props.build_noise_models() + + # Create unvalidated properties with unsupported gates. + toffoli_props = NoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={OpIdentifier(cirq.CCNOT, q0, q1, q2): 0.1}, + validate=False, + ) + with pytest.raises(ValueError, match='Some gates are not in the supported set.'): + _ = toffoli_props.build_noise_models() + + @pytest.mark.parametrize( 'op', [ diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 58bcdf7c372..77801ce47ff 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import TYPE_CHECKING, List, Tuple +from typing import TYPE_CHECKING, Sequence, Tuple import warnings import numpy as np @@ -15,24 +15,23 @@ class OpIdentifier: """Identifies an operation by gate and (optionally) target qubits.""" - gate: type - qubits: List['cirq.Qid'] + gate_type: type + qubits: Sequence['cirq.Qid'] - def __init__(self, gate, *qubits): - object.__setattr__(self, 'gate', gate) + def __init__(self, gate_type: type, *qubits: 'cirq.Qid'): + object.__setattr__(self, 'gate_type', gate_type) object.__setattr__(self, 'qubits', qubits) - def gate_only(self): - return OpIdentifier(self.gate) + def swapped(self): + return OpIdentifier(self.gate_type, *self.qubits[::-1]) def __str__(self): - if self.qubits: - return f'{self.gate}{self.qubits}' - return f'{self.gate}' + return f'{self.gate_type}{self.qubits}' def __repr__(self) -> str: - fullname = f'{self.gate.__module__}.{self.gate.__qualname__}' - return f'cirq.devices.noise_utils.OpIdentifier({fullname}, *{self.qubits})' + fullname = f'{self.gate_type.__module__}.{self.gate_type.__qualname__}' + qubits = ', '.join(map(repr, self.qubits)) + return f'cirq.devices.noise_utils.OpIdentifier({fullname}, {qubits})' # TODO: expose all from top-level cirq diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py new file mode 100644 index 00000000000..599b7258db6 --- /dev/null +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -0,0 +1,119 @@ +import numpy as np +import pytest + +from cirq.devices.noise_utils import ( + decay_constant_to_xeb_fidelity, + decay_constant_to_pauli_error, + pauli_error_to_decay_constant, + xeb_fidelity_to_decay_constant, + pauli_error_from_t1, + pauli_error_from_depolarization, + average_error, + decoherence_pauli_error, + unitary_entanglement_fidelity, +) + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', + [ + (0.01, 1, 1 - (0.99 * 1 / 2)), + (0.05, 2, 1 - (0.95 * 3 / 4)), + ], +) +def test_decay_constant_to_xeb_fidelity(decay_constant, num_qubits, expected_output): + val = decay_constant_to_xeb_fidelity(decay_constant, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', + [ + (0.01, 1, 0.99 * 3 / 4), + (0.05, 2, 0.95 * 15 / 16), + ], +) +def test_decay_constant_to_pauli_error(decay_constant, num_qubits, expected_output): + val = decay_constant_to_pauli_error(decay_constant, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'pauli_error,num_qubits,expected_output', + [ + (0.01, 1, 1 - (0.01 / (3 / 4))), + (0.05, 2, 1 - (0.05 / (15 / 16))), + ], +) +def test_pauli_error_to_decay_constant(pauli_error, num_qubits, expected_output): + val = pauli_error_to_decay_constant(pauli_error, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 'xeb_fidelity,num_qubits,expected_output', + [ + (0.01, 1, 1 - 0.99 / (1 / 2)), + (0.05, 2, 1 - 0.95 / (3 / 4)), + ], +) +def test_xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits, expected_output): + val = xeb_fidelity_to_decay_constant(xeb_fidelity, num_qubits) + assert val == expected_output + + +@pytest.mark.parametrize( + 't,t1_ns,expected_output', + [ + (20, 1e5, (1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4), + (4000, 1e4, (1 - np.exp(-4000 / 2e4)) / 2 + (1 - np.exp(-4000 / 1e4)) / 4), + ], +) +def test_pauli_error_from_t1(t, t1_ns, expected_output): + val = pauli_error_from_t1(t, t1_ns) + assert val == expected_output + + +@pytest.mark.parametrize( + 't,t1_ns,pauli_error,expected_output', + [ + (20, 1e5, 0.01, 0.01 - ((1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4)), + # In this case, the formula produces a negative result. + (4000, 1e4, 0.01, 0), + ], +) +def test_pauli_error_from_depolarization(t, t1_ns, pauli_error, expected_output): + val = pauli_error_from_depolarization(t, t1_ns, pauli_error) + assert val == expected_output + + +@pytest.mark.parametrize( + 'decay_constant,num_qubits,expected_output', + [ + (0.01, 1, 0.99 * 1 / 2), + (0.05, 2, 0.95 * 3 / 4), + ], +) +def test_average_error(decay_constant, num_qubits, expected_output): + val = average_error(decay_constant, num_qubits) + assert val == expected_output + + +# TODO: finish decoherence_pauli_error and unitary_entanglement_fidelity tests +# @pytest.mark.parametrize( +# 'T1_ns,Tphi_ns,gate_time_ns,expected_output', +# [], +# ) +# def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns, expected_output): +# val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns) +# assert val == expected_output + + +# @pytest.mark.parametrize( +# 'U_actual,U_ideal,expected_output', +# [], +# ) +# def test_unitary_entanglement_fidelity(U_actual, U_ideal, expected_output): +# val = unitary_entanglement_fidelity(U_actual, U_ideal) +# assert val == expected_output + diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py index 933e7e72396..016d4286f8a 100644 --- a/cirq-core/cirq/devices/thermal_noise_model.py +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -97,21 +97,22 @@ def _validate_rates(qubit_dims: Dict['cirq.Qid', int], rates: Dict['cirq.Qid', n for q in rates: if rates[q].shape != (qubit_dims[q], qubit_dims[q]): raise ValueError( - f'rate matrix invalid shape. ' - f'should be ({qubit_dims[q]}, {qubit_dims[q]}), ' + f'Invalid shape for rate matrix: should be ({qubit_dims[q]}, {qubit_dims[q]}), ' f'but got {rates[q].shape}' ) @dataclass class ThermalNoiseModel(devices.NoiseModel): - gate_durations_ns: Dict[type, float] - rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] - require_physical_tag: bool = True - skip_measurements: bool = True + """NoiseModel representing simulated thermalization of a qubit. - @staticmethod - def create( + This model is designed for qubits which use energy levels as their states. + "Heating" and "cooling" here are used to refer to environmental noise which + transitions a qubit to higher or lower energy levels, respectively. + """ + + def __init__( + self, qubit_dims: Dict['cirq.Qid', int], gate_durations_ns: Dict[type, float], heat_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, @@ -119,7 +120,7 @@ def create( dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, require_physical_tag: bool = True, skip_measurements: bool = True, - ) -> 'ThermalNoiseModel': + ): """Construct a ThermalNoiseModel data object. Required Args: @@ -151,7 +152,6 @@ def create( Returns: The ThermalNoiseModel with specified parameters. """ - qubits = set(qubit_dims) rate_dict = {} @@ -168,12 +168,10 @@ def _as_rate_dict( if qb not in rate_or_dict: out[qb] = 0.0 return out - elif np.isscalar(rate_or_dict): - return {qb: rate_or_dict for qb in qubits} else: - raise ValueError(f'invalid type passed to _as_rate_dict: {rate_or_dict}') + return {qb: rate_or_dict for qb in qubits} - heat_rate_GHz = _as_rate_dict(None) + heat_rate_GHz = _as_rate_dict(heat_rate_GHz) cool_rate_GHz = _as_rate_dict(cool_rate_GHz) dephase_rate_GHz = _as_rate_dict(dephase_rate_GHz) @@ -185,8 +183,10 @@ def _as_rate_dict( rate_dict[q] = _decoherence_matrix(gamma_c, gamma_phi, gamma_h, dim) _validate_rates(qubit_dims, rate_dict) - - return ThermalNoiseModel(gate_durations_ns, rate_dict, require_physical_tag) + self.gate_durations_ns: Dict[type, float] = gate_durations_ns + self.rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] = rate_dict + self.require_physical_tag: bool = require_physical_tag + self.skip_measurements: bool = skip_measurements def noisy_moment( self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] @@ -210,11 +210,16 @@ def noisy_moment( op_duration = op.gate.duration.total_nanos() moment_ns = max(moment_ns, op_duration) + if moment_ns == 0: + return [moment] + for qubit in system_qubits: - op = moment.operation_at(qubit) - if self.skip_measurements and protocols.is_measurement(op): + qubit_op = moment.operation_at(qubit) + if qubit_op is None: + continue + if self.skip_measurements and protocols.is_measurement(qubit_op): continue - if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: + if self.require_physical_tag and PHYSICAL_GATE_TAG not in qubit_op.tags: # Only non-virtual gates get noise applied. continue rates = self.rate_matrix_GHz[qubit] * moment_ns diff --git a/cirq-core/cirq/devices/thermal_noise_model_test.py b/cirq-core/cirq/devices/thermal_noise_model_test.py new file mode 100644 index 00000000000..5e1ebeed129 --- /dev/null +++ b/cirq-core/cirq/devices/thermal_noise_model_test.py @@ -0,0 +1,214 @@ +from cirq.ops import moment +import numpy as np +import pytest + +import cirq +from cirq.devices.noise_utils import ( + PHYSICAL_GATE_TAG, +) +from cirq.devices.thermal_noise_model import ( + ThermalNoiseModel +) + + +def test_create_thermal_noise_per_qubit(): + q0, q1 = cirq.LineQubit.range(2) + gate_durations = {cirq.PhasedXZGate: 25.0} + heat_rate_GHz={q0: 1e-5, q1: 2e-5} + cool_rate_GHz={q0: 1e-4, q1: 2e-4} + dephase_rate_GHz={q0: 3e-4, q1: 4e-4} + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=dephase_rate_GHz, + ) + assert model.gate_durations_ns == gate_durations + assert model.require_physical_tag == True + assert model.skip_measurements == True + assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [1e-5, 3e-4]])) + assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 2e-4], [2e-5, 4e-4]])) + + +def test_create_thermal_noise_mixed_type(): + q0, q1 = cirq.LineQubit.range(2) + gate_durations = {cirq.PhasedXZGate: 25.0} + heat_rate_GHz=None + cool_rate_GHz={q0: 1e-4, q1: 2e-4} + dephase_rate_GHz=3e-4 + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=dephase_rate_GHz, + ) + assert model.gate_durations_ns == gate_durations + assert model.require_physical_tag == True + assert model.skip_measurements == True + assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [0, 3e-4]])) + assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 2e-4], [0, 3e-4]])) + + +def test_incomplete_rates(): + q0, q1 = cirq.LineQubit.range(2) + gate_durations = {cirq.PhasedXZGate: 25.0} + heat_rate_GHz={q1: 1e-5} + cool_rate_GHz={q0: 1e-4} + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=None, + ) + assert model.gate_durations_ns == gate_durations + assert model.require_physical_tag == True + assert model.skip_measurements == True + assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [0, 0]])) + assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 0], [1e-5, 0]])) + + +def test_noise_from_zero_duration(): + # Verify that a moment with no duration has no noise. + q0, q1 = cirq.LineQubit.range(2) + gate_durations = {} + heat_rate_GHz={q1: 1e-5} + cool_rate_GHz={q0: 1e-4} + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=None, + require_physical_tag=False, + skip_measurements=False + ) + moment = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) + assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] + + +def test_noise_from_virtual_gates(): + # Verify that a moment with only virtual gates has no noise if + # require_physical_tag is True. + q0, q1 = cirq.LineQubit.range(2) + gate_durations = {cirq.ZPowGate: 25.0} + heat_rate_GHz={q1: 1e-5} + cool_rate_GHz={q0: 1e-4} + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=None, + require_physical_tag=True, + skip_measurements=False + ) + moment = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) + assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] + + part_virtual_moment = cirq.Moment(cirq.Z(q0), cirq.Z(q1).with_tags(PHYSICAL_GATE_TAG)) + assert len(model.noisy_moment(part_virtual_moment, system_qubits=[q0, q1])) == 2 + + model.require_physical_tag = False + assert len(model.noisy_moment(moment, system_qubits=[q0, q1])) == 2 + + +def test_noise_from_measurement(): + # Verify that a moment with only measurement gates has no noise if + # skip_measurements is True. + q0, q1 = cirq.LineQubit.range(2) + gate_durations = { + cirq.ZPowGate: 25.0, + cirq.MeasurementGate: 4000.0, + } + heat_rate_GHz={q1: 1e-5} + cool_rate_GHz={q0: 1e-4} + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns=gate_durations, + heat_rate_GHz=heat_rate_GHz, + cool_rate_GHz=cool_rate_GHz, + dephase_rate_GHz=None, + require_physical_tag=False, + skip_measurements=True + ) + moment = cirq.Moment(cirq.measure(q0, q1, key='m')) + assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] + + part_measure_moment = cirq.Moment(cirq.measure(q0, key='m'), cirq.Z(q1)) + assert len(model.noisy_moment(part_measure_moment, system_qubits=[q0, q1])) == 2 + + model.skip_measurements = False + assert len(model.noisy_moment(moment, system_qubits=[q0, q1])) == 2 + + +def test_noisy_moment_one_qubit(): + q0, q1 = cirq.LineQubit.range(2) + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns={ + cirq.PhasedXZGate: 25.0, + cirq.CZPowGate: 25.0, + }, + heat_rate_GHz={q0: 1e-5, q1: 2e-5}, + cool_rate_GHz={q0: 1e-4, q1: 2e-4}, + dephase_rate_GHz={q0: 3e-4, q1: 4e-4}, + require_physical_tag=False + ) + gate = cirq.PhasedXZGate(x_exponent=1, z_exponent=0.5, axis_phase_exponent=0.25) + moment = cirq.Moment(gate.on(q0)) + noisy_moment = model.noisy_moment(moment, system_qubits=[q0, q1]) + assert noisy_moment[0] == moment + assert len(noisy_moment[1]) == 1 + noisy_choi = cirq.kraus_to_choi(cirq.kraus(noisy_moment[1].operations[0])) + assert np.allclose( + noisy_choi, + [ + [9.99750343e-01, 0, 0, 9.91164267e-01], + [0, 2.49656565e-03, 0, 0], + [0, 0, 2.49656565e-04, 0], + [9.91164267e-01, 0, 0, 9.97503434e-01] + ] + ) + + +def test_noisy_moment_two_qubit(): + q0, q1 = cirq.LineQubit.range(2) + model = ThermalNoiseModel( + qubit_dims={q0: 2, q1: 2}, + gate_durations_ns={ + cirq.PhasedXZGate: 25.0, + cirq.CZPowGate: 25.0, + }, + heat_rate_GHz={q0: 1e-5, q1: 2e-5}, + cool_rate_GHz={q0: 1e-4, q1: 2e-4}, + dephase_rate_GHz={q0: 3e-4, q1: 4e-4}, + require_physical_tag=False + ) + gate = cirq.CZ ** 0.5 + moment = cirq.Moment(gate.on(q0, q1)) + noisy_moment = model.noisy_moment(moment, system_qubits=[q0, q1]) + assert noisy_moment[0] == moment + assert len(noisy_moment[1]) == 2 + noisy_choi_0 = cirq.kraus_to_choi(cirq.kraus(noisy_moment[1].operations[0])) + assert np.allclose( + noisy_choi_0, + [ + [9.99750343e-01, 0, 0, 9.91164267e-01], + [0, 2.49656565e-03, 0, 0], + [0, 0, 2.49656565e-04, 0], + [9.91164267e-01, 0, 0, 9.97503434e-01] + ] + ) + noisy_choi_1 = cirq.kraus_to_choi(cirq.kraus(noisy_moment[1].operations[1])) + assert np.allclose( + noisy_choi_1, + [ + [9.99501372e-01, 0, 0, 9.87330937e-01], + [0, 4.98627517e-03, 0, 0], + [0, 0, 4.98627517e-04, 0], + [9.87330937e-01, 0, 0, 9.95013725e-01] + ] + ) diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json index 81625633699..c825fa1598d 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json @@ -170,5 +170,6 @@ }, 0.001 ] - ] + ], + "validate": true } diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr index 1f09c65684d..e17be7d33d1 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr @@ -13,9 +13,9 @@ cirq.NoiseProperties( Tphi_ns={cirq.LineQubit(0): 200000.0}, ro_fidelities={cirq.LineQubit(0): [0.001, 0.01]}, gate_pauli_errors={ - cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(0),)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, *(cirq.LineQubit(0),)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(0),)): 0.001, - cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(0),)): 0.001 + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, cirq.LineQubit(0)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, cirq.LineQubit(0)): 0.001 } ) diff --git a/cirq-google/cirq_google/__init__.py b/cirq-google/cirq_google/__init__.py index 6a5bef1fc00..53a17a09a6a 100644 --- a/cirq-google/cirq_google/__init__.py +++ b/cirq-google/cirq_google/__init__.py @@ -57,6 +57,8 @@ from cirq_google.devices import ( Bristlecone, Foxtail, + GoogleNoiseProperties, + NoiseModelFromGoogleNoiseProperties, SerializableDevice, Sycamore, Sycamore23, diff --git a/cirq-google/cirq_google/devices/__init__.py b/cirq-google/cirq_google/devices/__init__.py index 9746693735a..2ba59d16b53 100644 --- a/cirq-google/cirq_google/devices/__init__.py +++ b/cirq-google/cirq_google/devices/__init__.py @@ -12,6 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +from cirq_google.devices.google_noise_properties import ( + GoogleNoiseProperties, + NoiseModelFromGoogleNoiseProperties, +) + from cirq_google.devices.known_devices import ( Bristlecone, Foxtail, diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index b2711337c23..58755b76656 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -10,23 +10,6 @@ ) -SINGLE_QUBIT_GATES = { - cirq.ZPowGate, - cirq.PhasedXZGate, - cirq.MeasurementGate, - cirq.ResetChannel, -} -SYMMETRIC_TWO_QUBIT_GATES = { - cirq_google.SycamoreGate, - cirq.FSimGate, - cirq.PhasedFSimGate, - cirq.ISwapPowGate, - cirq.CZPowGate, -} -ASYMMETRIC_TWO_QUBIT_GATES: Set[type] = set() -TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES - - @dataclass class GoogleNoiseProperties(cirq.NoiseProperties): """Noise-defining properties for a Google device. @@ -40,6 +23,8 @@ class GoogleNoiseProperties(cirq.NoiseProperties): fidelity matrix. gate_pauli_errors: Dict[OpIdentifier, float] of gate types (potentially on specific qubits) to the Pauli error for that gate. + validate: If True, performs validation on input arguments. Defaults + to True. Additional args: fsim_errors: Dict[OpIdentifier, cirq.PhasedFSimGate] of gate types @@ -49,7 +34,9 @@ class GoogleNoiseProperties(cirq.NoiseProperties): fsim_errors: Dict[OpIdentifier, cirq.PhasedFSimGate] = field(default_factory=dict) - def __attrs_post_init__(self): + def __post_init__(self): + if not self.validate: + return t1_qubits = set(self.T1_ns) tphi_qubits = set(self.Tphi_ns) @@ -58,15 +45,19 @@ def __attrs_post_init__(self): # validate two qubit gate errors. self._validate_symmetric_errors('gate_pauli_errors') - self._validate_symmetric_errors('fsim_errors') + if self.fsim_errors is not None: + self._validate_symmetric_errors('fsim_errors') @classmethod - def single_qubit_gates(cls) -> Set[type]: - return SINGLE_QUBIT_GATES + def two_qubit_gates(cls) -> Set[type]: + return super().two_qubit_gates() | {cirq_google.SycamoreGate} @classmethod - def two_qubit_gates(cls) -> Set[type]: - return TWO_QUBIT_GATES + def canonical_gates(cls) -> Dict[type, 'cirq.Gate']: + return { + **super().canonical_gates(), + cirq_google.SycamoreGate: cirq_google.SycamoreGate(), + } def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: if self._depolarizing_error: @@ -75,25 +66,15 @@ def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: depol_errors = super().get_depolarizing_error() for op_id in depol_errors: - if op_id.gate not in self.two_qubit_gates(): + if op_id.gate_type not in self.two_qubit_gates(): continue - if len(op_id.qubits) != 2: - raise ValueError( - f'Gate {op_id.gate} takes two qubits, but {op_id.qubits} were given.' - ) + # Number of qubits is checked by the parent class. # Subtract entangling angle error. if op_id in self.fsim_errors: unitary_err = cirq.unitary(self.fsim_errors[op_id]) fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) - rp_fsim = 1 - fid - elif op_id.gate_only() in self.fsim_errors: - unitary_err = cirq.unitary(self.fsim_errors[op_id.gate_only()]) - fid = unitary_entanglement_fidelity(unitary_err, np.eye(4)) - rp_fsim = 1 - fid - else: - rp_fsim = 0 + depol_errors[op_id] -= 1 - fid - depol_errors[op_id] -= rp_fsim # memoization is OK self._depolarizing_error = depol_errors return self._depolarizing_error @@ -109,6 +90,67 @@ def build_noise_models(self) -> List['cirq.NoiseModel']: return noise_models + def __str__(self) -> str: + return 'GoogleNoiseProperties' + + def __repr__(self) -> str: + args = [] + gate_times_repr = ', '.join( + f'{key.__module__}.{key.__qualname__}: {val}' for key, val in self.gate_times_ns.items() + ) + args.append(f' gate_times_ns={{{gate_times_repr}}}') + args.append(f' T1_ns={self.T1_ns!r}') + args.append(f' Tphi_ns={self.Tphi_ns!r}') + args.append(f' ro_fidelities={self.ro_fidelities!r}') + args.append(f' gate_pauli_errors={self.gate_pauli_errors!r}') + args.append(f' fsim_errors={self.fsim_errors!r}') + args_str = ',\n'.join(args) + return f'cirq_google.GoogleNoiseProperties(\n{args_str}\n)' + + def _json_dict_(self): + json_dict = super()._json_dict_() + json_dict['cirq_type'] = 'GoogleNoiseProperties' + storage_fsim_errors = { + self.get_canonical_gate(op_id.gate_type).on(*op_id.qubits): val + for op_id, val in self.fsim_errors.items() + } + json_dict['fsim_errors'] = sorted(storage_fsim_errors.items(), key=str) + return json_dict + + @classmethod + def _from_json_dict_( + cls, + gate_times_ns, + T1_ns, + Tphi_ns, + ro_fidelities, + gate_pauli_errors, + validate, + fsim_errors, + **kwargs, + ): + # TODO: overlaps op_ids with same qubits in different order + gate_type_times = {type(gate): val for gate, val in gate_times_ns} + op_id_pauli_errors = {} + for op, val in gate_pauli_errors: + op_id = cls._op_to_identifier(op) + op_id_pauli_errors[op_id] = val + op_id_pauli_errors[op_id.swapped()] = val + op_id_fsim_errors = {} + for op, val in fsim_errors: + op_id = cls._op_to_identifier(op) + op_id_fsim_errors[op_id] = val + op_id_fsim_errors[op_id.swapped()] = val + return GoogleNoiseProperties( + gate_times_ns=gate_type_times, + T1_ns={k: v for k, v in T1_ns}, + Tphi_ns={k: v for k, v in Tphi_ns}, + ro_fidelities={k: v for k, v in ro_fidelities}, + gate_pauli_errors=op_id_pauli_errors, + validate=validate, + fsim_errors=op_id_fsim_errors, + ) + class NoiseModelFromGoogleNoiseProperties(devices.NoiseModelFromNoiseProperties): """A noise model defined from noise properties of a Google device.""" diff --git a/cirq-google/cirq_google/devices/google_noise_properties_test.py b/cirq-google/cirq_google/devices/google_noise_properties_test.py index 502e905aabe..4dd89dbd425 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties_test.py +++ b/cirq-google/cirq_google/devices/google_noise_properties_test.py @@ -1,16 +1,10 @@ from typing import Dict, List, Tuple -from cirq.ops.common_channels import GeneralizedAmplitudeDampingChannel from cirq.ops.fsim_gate import PhasedFSimGate import numpy as np import pytest import cirq, cirq_google # from cirq.testing import assert_equivalent_op_tree -from cirq_google.devices.google_noise_properties import ( - SYMMETRIC_TWO_QUBIT_GATES, - SINGLE_QUBIT_GATES, - TWO_QUBIT_GATES, -) from cirq.devices.noise_utils import ( OpIdentifier, PHYSICAL_GATE_TAG, @@ -22,6 +16,10 @@ ) +SINGLE_QUBIT_GATES = GoogleNoiseProperties.single_qubit_gates() +TWO_QUBIT_GATES = GoogleNoiseProperties.two_qubit_gates() +SYMMETRIC_TWO_QUBIT_GATES = TWO_QUBIT_GATES + DEFAULT_GATE_NS: Dict[type, float] = { cirq.ZPowGate: 25.0, cirq.MeasurementGate: 4000.0, @@ -47,7 +45,7 @@ def sample_noise_properties( gate_times_ns=DEFAULT_GATE_NS, T1_ns={q: 1e5 for q in system_qubits}, Tphi_ns={q: 2e5 for q in system_qubits}, - ro_fidelities={q: np.array([0.001, 0.01]) for q in system_qubits}, + ro_fidelities={q: [0.001, 0.01] for q in system_qubits}, gate_pauli_errors={ **{OpIdentifier(g, q): 0.001 for g in SINGLE_QUBIT_GATES for q in system_qubits}, **{OpIdentifier(g, q0, q1): 0.01 for g in TWO_QUBIT_GATES for q0, q1 in qubit_pairs}, @@ -60,7 +58,81 @@ def sample_noise_properties( ) -# TODO: all the other tests +def test_str(): + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + assert str(props) == 'GoogleNoiseProperties' + + +def test_repr_evaluation(): + q0, q1 = cirq.LineQubit.range(2) + props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + print(repr(props)) + props_from_repr = eval(repr(props)) + assert props_from_repr == props + + +def test_json_serialization(): + q0, q1 = cirq.LineQubit.range(2) + props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) + props_json = cirq.to_json(props) + props_from_json = cirq.read_json(json_text=props_json) + assert props_from_json == props + + +def test_init_validation(): + q0, q1 = cirq.LineQubit.range(2) + with pytest.raises(ValueError, match='Keys specified for T1 and Tphi are not identical.'): + _ = GoogleNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={}, + ) + + with pytest.raises(ValueError, match='does not appear in the symmetric or asymmetric'): + _ = GoogleNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.ZPowGate, q0): 0.1, + OpIdentifier(cirq.CZPowGate, q0, q1): 0.1, + OpIdentifier(cirq.CZPowGate, q1, q0): 0.1, + }, + fsim_errors={ + OpIdentifier(cirq.CNOT, q0, q1): cirq.PhasedFSimGate(theta=0.1), + }, + ) + + # Errors are ignored if validation is disabled. + _ = GoogleNoiseProperties( + gate_times_ns=DEFAULT_GATE_NS, + T1_ns={q0: 1}, + Tphi_ns={q0: 1}, + ro_fidelities={q0: [0.1, 0.2]}, + gate_pauli_errors={ + OpIdentifier(cirq.ZPowGate, q0): 0.1, + OpIdentifier(cirq.CZPowGate, q0, q1): 0.1, + OpIdentifier(cirq.CZPowGate, q1, q0): 0.1, + }, + validate=False, + fsim_errors={ + OpIdentifier(cirq.CNOT, q0, q1): cirq.PhasedFSimGate(theta=0.1), + }, + ) + + +def test_depol_memoization(): + # Verify that depolarizing error is memoized. + q0 = cirq.LineQubit(0) + props = sample_noise_properties([q0], []) + depol_error_a = props.get_depolarizing_error() + depol_error_b = props.get_depolarizing_error() + assert depol_error_a == depol_error_b + assert depol_error_a is depol_error_b def test_zphase_gates(): diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py index f8be725057e..edca82e14a8 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py @@ -10,11 +10,13 @@ ) from google.protobuf.text_format import Merge import numpy as np +import pytest def test_noise_properties_from_calibration(): qubits = [cirq.GridQubit(0, 0), cirq.GridQubit(0, 1), cirq.GridQubit(1, 0)] pauli_error = [0.001, 0.002, 0.003] + incoherent_error = [0.0001, 0.0002, 0.0003] p00_error = [0.004, 0.005, 0.006] p11_error = [0.007, 0.008, 0.009] t1_micros = [10, 20, 30] @@ -40,6 +42,24 @@ def test_noise_properties_from_calibration(): values: [{{ double_val:{pauli_error[2]} }}] + }}, {{ + name: 'single_qubit_rb_incoherent_error_per_gate', + targets: ['0_0'], + values: [{{ + double_val: {incoherent_error[0]} + }}] + }}, {{ + name: 'single_qubit_rb_incoherent_error_per_gate', + targets: ['0_1'], + values: [{{ + double_val:{incoherent_error[1]} + }}] + }}, {{ + name: 'single_qubit_rb_incoherent_error_per_gate', + targets: ['1_0'], + values: [{{ + double_val:{incoherent_error[2]} + }}] }}, {{ name: 'single_qubit_p00_error', targets: ['0_0'], @@ -110,3 +130,102 @@ def test_noise_properties_from_calibration(): assert np.allclose(prop.ro_fidelities[q], np.array([p00_error[i], p11_error[i]])) assert np.isclose(prop.T1_ns[q], t1_micros[i] * 1000) # TODO: test Tphi + microwave_time_ns = 25.0 + tphi_err = incoherent_error[i] - microwave_time_ns / (3 * prop.T1_ns[q]) + if tphi_err > 0: + tphi_ns = microwave_time_ns / (3 * tphi_err) + else: + tphi_ns = 1e10 + assert prop.Tphi_ns[q] == tphi_ns + + +def test_incomplete_calibration(): + pauli_error = [0.001, 0.002, 0.003] + p00_error = [0.004, 0.005, 0.006] + p11_error = [0.007, 0.008, 0.009] + t1_micros = [10, 20, 30] + + _CALIBRATION_DATA = Merge( + f""" + timestamp_ms: 1579214873, + metrics: [{{ + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['0_0'], + values: [{{ + double_val: {pauli_error[0]} + }}] + }}, {{ + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['0_1'], + values: [{{ + double_val:{pauli_error[1]} + }}] + }}, {{ + name: 'single_qubit_rb_pauli_error_per_gate', + targets: ['1_0'], + values: [{{ + double_val:{pauli_error[2]} + }}] + }}, {{ + name: 'single_qubit_p00_error', + targets: ['0_0'], + values: [{{ + double_val: {p00_error[0]} + }}] + }}, {{ + name: 'single_qubit_p00_error', + targets: ['0_1'], + values: [{{ + double_val: {p00_error[1]} + }}] + }}, {{ + name: 'single_qubit_p00_error', + targets: ['1_0'], + values: [{{ + double_val: {p00_error[2]} + }}] + }}, {{ + name: 'single_qubit_p11_error', + targets: ['0_0'], + values: [{{ + double_val: {p11_error[0]} + }}] + }}, {{ + name: 'single_qubit_p11_error', + targets: ['0_1'], + values: [{{ + double_val: {p11_error[1]} + }}] + }}, {{ + name: 'single_qubit_p11_error', + targets: ['1_0'], + values: [{{ + double_val: {p11_error[2]} + }}] + }}, {{ + name: 'single_qubit_idle_t1_micros', + targets: ['0_0'], + values: [{{ + double_val: {t1_micros[0]} + }}] + }}, {{ + name: 'single_qubit_idle_t1_micros', + targets: ['0_1'], + values: [{{ + double_val: {t1_micros[1]} + }}] + }}, {{ + name: 'single_qubit_idle_t1_micros', + targets: ['1_0'], + values: [{{ + double_val: {t1_micros[2]} + }}] + }}] +""", + v2.metrics_pb2.MetricsSnapshot(), + ) + + # Create NoiseProperties object from Calibration + calibration = cirq_google.Calibration(_CALIBRATION_DATA) + with pytest.raises(ValueError, match='Keys specified for T1 and Tphi are not identical.'): + _ = noise_properties_from_calibration(calibration) diff --git a/cirq-google/cirq_google/json_resolver_cache.py b/cirq-google/cirq_google/json_resolver_cache.py index 29e6f90a359..699cc601612 100644 --- a/cirq-google/cirq_google/json_resolver_cache.py +++ b/cirq-google/cirq_google/json_resolver_cache.py @@ -34,6 +34,7 @@ def _class_resolver_dictionary() -> Dict[str, ObjectFactory]: 'GateTabulation': cirq_google.GateTabulation, 'PhysicalZTag': cirq_google.PhysicalZTag, 'FSimGateFamily': cirq_google.FSimGateFamily, + 'GoogleNoiseProperties': cirq_google.GoogleNoiseProperties, 'FloquetPhasedFSimCalibrationOptions': cirq_google.FloquetPhasedFSimCalibrationOptions, 'FloquetPhasedFSimCalibrationRequest': cirq_google.FloquetPhasedFSimCalibrationRequest, 'PhasedFSimCalibrationResult': cirq_google.PhasedFSimCalibrationResult, diff --git a/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json new file mode 100644 index 00000000000..4727906919b --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json @@ -0,0 +1,523 @@ +{ + "cirq_type": "GoogleNoiseProperties", + "gate_times_ns": [ + [ + { + "cirq_type": "CZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "FSimGate", + "theta": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "ISwapPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + 4000.0 + ], + [ + { + "cirq_type": "PhasedFSimGate", + "theta": 0.0, + "zeta": 0.0, + "chi": 0.0, + "gamma": 0.0, + "phi": 0.0 + }, + 32.0 + ], + [ + { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + 25.0 + ], + [ + { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + 250.0 + ], + [ + { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + 25.0 + ], + [ + { + "cirq_type": "SycamoreGate" + }, + 32.0 + ] + ], + "T1_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 100000.0 + ], + [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + 100000.0 + ] + ], + "Tphi_ns": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + 200000.0 + ], + [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + 200000.0 + ] + ], + "ro_fidelities": [ + [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + [ + 0.001, + 0.01 + ] + ], + [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + [ + 0.001, + 0.01 + ] + ] + ], + "gate_pauli_errors": [ + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "CZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.01 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "FSimGate", + "theta": 0.0, + "phi": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.01 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ISwapPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.01 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "PhasedFSimGate", + "theta": 0.0, + "zeta": 0.0, + "chi": 0.0, + "gamma": 0.0, + "phi": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.01 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "PhasedXZGate", + "axis_phase_exponent": 0, + "x_exponent": 0, + "z_exponent": 0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ResetChannel", + "dimension": 2 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "MeasurementGate", + "num_qubits": 1, + "key": "", + "invert_mask": [] + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "SycamoreGate" + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.01 + ] + ], + "validate": true, + "fsim_errors": [ + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "CZPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "FSimGate", + "theta": 0.0, + "phi": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "ISwapPowGate", + "exponent": 1.0, + "global_shift": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "PhasedFSimGate", + "theta": 0.0, + "zeta": 0.0, + "chi": 0.0, + "gamma": 0.0, + "phi": 0.0 + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "GateOperation", + "gate": { + "cirq_type": "SycamoreGate" + }, + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ] + ] +} \ No newline at end of file diff --git a/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.repr b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.repr new file mode 100644 index 00000000000..2c873684e0a --- /dev/null +++ b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.repr @@ -0,0 +1,57 @@ +cirq_google.GoogleNoiseProperties( + gate_times_ns={ + cirq.ops.common_gates.ZPowGate: 25.0, + cirq.ops.measurement_gate.MeasurementGate: 4000.0, + cirq.ops.common_channels.ResetChannel: 250.0, + cirq.ops.phased_x_z_gate.PhasedXZGate: 25.0, + cirq_google.ops.sycamore_gate.SycamoreGate: 32.0, + cirq.ops.fsim_gate.FSimGate: 32.0, + cirq.ops.fsim_gate.PhasedFSimGate: 32.0, + cirq.ops.swap_gates.ISwapPowGate: 32.0, + cirq.ops.common_gates.CZPowGate: 32.0 + }, + T1_ns={ + cirq.LineQubit(0): 100000.0, + cirq.LineQubit(1): 100000.0 + }, + Tphi_ns={ + cirq.LineQubit(0): 200000.0, + cirq.LineQubit(1): 200000.0 + }, + ro_fidelities={ + cirq.LineQubit(0): [0.001, 0.01], + cirq.LineQubit(1): [0.001, 0.01] + }, + gate_pauli_errors={ + cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.measurement_gate.MeasurementGate, *(cirq.LineQubit(1),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.phased_x_z_gate.PhasedXZGate, *(cirq.LineQubit(1),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, *(cirq.LineQubit(1),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(0),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, *(cirq.LineQubit(1),)): 0.001, + cirq.devices.noise_utils.OpIdentifier(cirq_google.ops.sycamore_gate.SycamoreGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq_google.ops.sycamore_gate.SycamoreGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.PhasedFSimGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.PhasedFSimGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.FSimGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.FSimGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.swap_gates.ISwapPowGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.swap_gates.ISwapPowGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.CZPowGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): 0.01, + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.CZPowGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): 0.01 + }, + fsim_errors={ + cirq.devices.noise_utils.OpIdentifier(cirq_google.ops.sycamore_gate.SycamoreGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq_google.ops.sycamore_gate.SycamoreGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.PhasedFSimGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.PhasedFSimGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.FSimGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.fsim_gate.FSimGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.swap_gates.ISwapPowGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.swap_gates.ISwapPowGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.CZPowGate, *(cirq.LineQubit(0), cirq.LineQubit(1))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02), + cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.CZPowGate, *(cirq.LineQubit(1), cirq.LineQubit(0))): cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02) + } +) diff --git a/cirq-google/cirq_google/json_test_data/spec.py b/cirq-google/cirq_google/json_test_data/spec.py index 1a7e08b94b0..52c377c7637 100644 --- a/cirq-google/cirq_google/json_test_data/spec.py +++ b/cirq-google/cirq_google/json_test_data/spec.py @@ -39,6 +39,7 @@ 'EngineProgram', 'FSimPhaseCorrections', 'NAMED_GATESETS', + 'NoiseModelFromGoogleNoiseProperties', 'ProtoVersion', 'GateOpSerializer', 'GateOpDeserializer', From 4f1990c6c6df360ca772a83943f0f987a1bcef6d Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Mon, 8 Nov 2021 16:17:10 -0800 Subject: [PATCH 09/13] All tests passing --- .../cirq/devices/insertion_noise_model.py | 12 +++ .../devices/insertion_noise_model_test.py | 87 ++++++++++++++++++- cirq-core/cirq/devices/noise_properties.py | 21 +++-- cirq-core/cirq/devices/noise_utils.py | 2 +- cirq-core/cirq/devices/noise_utils_test.py | 77 ++++++++++++---- .../cirq/devices/thermal_noise_model_test.py | 77 ++++++++++------ 6 files changed, 219 insertions(+), 57 deletions(-) diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index 872d2d5d94d..849831b0aaf 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -42,6 +42,18 @@ def noisy_moment( op_id = OpIdentifier(type(op.gate), *op.qubits) if op_id in self.ops_added: noise_ops.append(self.ops_added[op_id]) + continue + # Find the closest match, if one exists. + parent_id = OpIdentifier(object, *op.qubits) + for added_id in self.ops_added: + if added_id.qubits != parent_id.qubits: + continue + if not issubclass(op_id.gate_type, added_id.gate_type): + continue + if issubclass(added_id.gate_type, parent_id.gate_type): + parent_id = added_id + if parent_id.gate_type != object: + noise_ops.append(self.ops_added[parent_id]) if not noise_ops: return [moment] if self.prepend: diff --git a/cirq-core/cirq/devices/insertion_noise_model_test.py b/cirq-core/cirq/devices/insertion_noise_model_test.py index 25cfe4f42e6..8f8caa869a9 100644 --- a/cirq-core/cirq/devices/insertion_noise_model_test.py +++ b/cirq-core/cirq/devices/insertion_noise_model_test.py @@ -1,4 +1,89 @@ +import cirq +from cirq.devices.insertion_noise_model import InsertionNoiseModel +from cirq.devices.noise_utils import ( + PHYSICAL_GATE_TAG, + OpIdentifier, +) def test_insertion_noise(): - assert False + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.H(q1)}, require_physical_tag=False + ) + assert model.prepend == False + + moment_0 = cirq.Moment(cirq.X(q0), cirq.X(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + moment_0, + cirq.Moment(cirq.T(q0)), + ] + + moment_1 = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_1, system_qubits=[q0, q1]) == [ + moment_1, + cirq.Moment(cirq.H(q1)), + ] + + moment_2 = cirq.Moment(cirq.X(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_2, system_qubits=[q0, q1]) == [ + moment_2, + cirq.Moment(cirq.T(q0), cirq.H(q1)), + ] + + moment_3 = cirq.Moment(cirq.Z(q0), cirq.X(q1)) + assert model.noisy_moment(moment_3, system_qubits=[q0, q1]) == [moment_3] + + +def test_prepend(): + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.H(q1)}, prepend=True, require_physical_tag=False + ) + + moment_0 = cirq.Moment(cirq.X(q0), cirq.Z(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + cirq.Moment(cirq.T(q0), cirq.H(q1)), + moment_0, + ] + + +def test_require_physical_tag(): + q0, q1 = cirq.LineQubit.range(2) + op_id0 = OpIdentifier(cirq.XPowGate, q0) + op_id1 = OpIdentifier(cirq.ZPowGate, q1) + model = InsertionNoiseModel({op_id0: cirq.T(q0), op_id1: cirq.H(q1)}) + assert model.require_physical_tag == True + + moment_0 = cirq.Moment(cirq.X(q0).with_tags(PHYSICAL_GATE_TAG), cirq.Z(q1)) + assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ + moment_0, + cirq.Moment(cirq.T(q0)), + ] + + +def test_supertype_matching(): + # Demonstrate that the model applies the closest matching type + # if multiple types match a given gate. + q0 = cirq.LineQubit(0) + op_id0 = OpIdentifier(cirq.Gate, q0) + op_id1 = OpIdentifier(cirq.XPowGate, q0) + model = InsertionNoiseModel( + {op_id0: cirq.T(q0), op_id1: cirq.S(q0)}, require_physical_tag=False + ) + + moment_0 = cirq.Moment(cirq.Rx(rads=1).on(q0)) + assert model.noisy_moment(moment_0, system_qubits=[q0]) == [ + moment_0, + cirq.Moment(cirq.S(q0)), + ] + + moment_1 = cirq.Moment(cirq.Y(q0)) + assert model.noisy_moment(moment_1, system_qubits=[q0]) == [ + moment_1, + cirq.Moment(cirq.T(q0)), + ] diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 3dc2b6ac289..feb6931ca82 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -78,21 +78,20 @@ def _validate_symmetric_errors(self, field_name: str) -> None: f'Found gate {op_id.gate_type} with {len(op_id.qubits)} qubits. ' 'Symmetric errors can only apply to 2-qubit gates.' ) - continue - - if op_id.gate_type not in self.two_qubit_gates(): + elif op_id.gate_type not in self.two_qubit_gates(): raise ValueError( f'Found gate {op_id.gate_type} which does not appear in the ' 'symmetric or asymmetric gate sets.' ) - # TODO: this assumes op is symmetric. - # If asymmetric gates are added, we will need to update it. - op_id_swapped = op_id.swapped() - if op_id_swapped not in gate_error_dict: - raise ValueError( - f'Operation {op_id} of field {field_name} has ' - f'errors but its symmetric id {op_id_swapped} does not.' - ) + else: + # TODO: this assumes op is symmetric. + # If asymmetric gates are added, we will need to update it. + op_id_swapped = op_id.swapped() + if op_id_swapped not in gate_error_dict: + raise ValueError( + f'Operation {op_id} of field {field_name} has errors ' + f'but its symmetric id {op_id_swapped} does not.' + ) @property def qubits(self) -> List['cirq.Qid']: diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 77801ce47ff..595b06dbf9c 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -34,7 +34,7 @@ def __repr__(self) -> str: return f'cirq.devices.noise_utils.OpIdentifier({fullname}, {qubits})' -# TODO: expose all from top-level cirq +# TODO: expose all from top-level cirq? def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: """Calculates the XEB fidelity from the depolarization decay constant. diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py index 599b7258db6..5bfa6d0f873 100644 --- a/cirq-core/cirq/devices/noise_utils_test.py +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -99,21 +99,64 @@ def test_average_error(decay_constant, num_qubits, expected_output): assert val == expected_output -# TODO: finish decoherence_pauli_error and unitary_entanglement_fidelity tests -# @pytest.mark.parametrize( -# 'T1_ns,Tphi_ns,gate_time_ns,expected_output', -# [], -# ) -# def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns, expected_output): -# val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns) -# assert val == expected_output - - -# @pytest.mark.parametrize( -# 'U_actual,U_ideal,expected_output', -# [], -# ) -# def test_unitary_entanglement_fidelity(U_actual, U_ideal, expected_output): -# val = unitary_entanglement_fidelity(U_actual, U_ideal) -# assert val == expected_output +@pytest.mark.parametrize( + 'T1_ns,Tphi_ns,gate_time_ns', + [ + (1e4, 2e4, 25), + (1e5, 2e3, 25), + (1e4, 2e4, 4000), + ], +) +def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns): + val = decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns) + # Expected value is of the form: + # + # (1/4) * [1 - e^(-t/T1)] + (1/2) * [1 - e^(-t/(2*T1) - t/Tphi] + # + expected_output = 0.25 * (1 - np.exp(-gate_time_ns / T1_ns)) + 0.5 * ( + 1 - np.exp(-gate_time_ns * ((1 / (2 * T1_ns)) + 1 / Tphi_ns)) + ) + assert val == expected_output + +# Surface-level tests to ensure nothing breaks that shouldn't. +def test_unitary_entanglement_fidelity(): + # cirq.H(q0) * cirq.H(q1) + U_actual = ( + np.array([[[[1, 1], [1, -1]], [[1, 1], [1, -1]]], [[[1, 1], [1, -1]], [[-1, -1], [-1, 1]]]]) + / 2 + ) + # cirq.X(q0) + U_ideal = np.array([[0, 1], [1, 0]]) + fidelity = unitary_entanglement_fidelity(U_actual, U_ideal) + assert fidelity.shape == (2, 2) + + +def test_invalid_unitary_entanglement_fidelity(): + # 4x4 cannot broadcast to 2x2 + U_actual_1 = np.array( + [ + [1, 1, 1, 1], + [1, -1, 1, -1], + [1, 1, -1, -1], + [1, 1, -1, 1], + ] + ) + U_ideal_1 = np.array([[0, 1], [1, 0]]) + with pytest.raises(ValueError, match='Input arrays do not have matching shapes.'): + _ = unitary_entanglement_fidelity(U_actual_1, U_ideal_1) + + U_actual_2 = np.array( + [ + [[1, 2, 3], [4, 5, 6]], + [[1, 2, 3], [4, 5, 6]], + ] + ) + U_ideal_2 = np.array( + [ + [[1, 2, 3], [4, 5, 6]], + [[1, 2, 3], [4, 5, 6]], + ] + ) + with pytest.raises(ValueError, match='trailing dimensions must be equal'): + _ = unitary_entanglement_fidelity(U_actual_2, U_ideal_2) diff --git a/cirq-core/cirq/devices/thermal_noise_model_test.py b/cirq-core/cirq/devices/thermal_noise_model_test.py index 5e1ebeed129..bf1a9b1d352 100644 --- a/cirq-core/cirq/devices/thermal_noise_model_test.py +++ b/cirq-core/cirq/devices/thermal_noise_model_test.py @@ -1,4 +1,3 @@ -from cirq.ops import moment import numpy as np import pytest @@ -7,16 +6,40 @@ PHYSICAL_GATE_TAG, ) from cirq.devices.thermal_noise_model import ( - ThermalNoiseModel + _left_mul, + _right_mul, + _validate_rates, + ThermalNoiseModel, ) +def test_helper_method_errors(): + with pytest.raises(ValueError, match='_left_mul only accepts square matrices'): + _ = _left_mul(np.array([[1, 2, 3], [4, 5, 6]])) + + with pytest.raises(ValueError, match='_right_mul only accepts square matrices'): + _ = _right_mul(np.array([[1, 2, 3], [4, 5, 6]])) + + q0, q1 = cirq.LineQubit.range(2) + with pytest.raises(ValueError, match='qubits for rates inconsistent'): + _validate_rates({q0: 2, q1: 2}, {q0: np.array([[0.01, 0.1], [0.02, 0.2]])}) + + with pytest.raises(ValueError, match='qubits for rates inconsistent'): + _validate_rates( + {q0: 2}, + {q0: np.array([[0.01, 0.1], [0.02, 0.2]]), q1: np.array([[0.03, 0.3], [0.04, 0.4]])}, + ) + + with pytest.raises(ValueError, match='Invalid shape for rate matrix'): + _validate_rates({q0: 2}, {q0: np.array([[0.001, 0.01, 0.1], [0.002, 0.02, 0.2]])}) + + def test_create_thermal_noise_per_qubit(): q0, q1 = cirq.LineQubit.range(2) gate_durations = {cirq.PhasedXZGate: 25.0} - heat_rate_GHz={q0: 1e-5, q1: 2e-5} - cool_rate_GHz={q0: 1e-4, q1: 2e-4} - dephase_rate_GHz={q0: 3e-4, q1: 4e-4} + heat_rate_GHz = {q0: 1e-5, q1: 2e-5} + cool_rate_GHz = {q0: 1e-4, q1: 2e-4} + dephase_rate_GHz = {q0: 3e-4, q1: 4e-4} model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -34,9 +57,9 @@ def test_create_thermal_noise_per_qubit(): def test_create_thermal_noise_mixed_type(): q0, q1 = cirq.LineQubit.range(2) gate_durations = {cirq.PhasedXZGate: 25.0} - heat_rate_GHz=None - cool_rate_GHz={q0: 1e-4, q1: 2e-4} - dephase_rate_GHz=3e-4 + heat_rate_GHz = None + cool_rate_GHz = {q0: 1e-4, q1: 2e-4} + dephase_rate_GHz = 3e-4 model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -54,8 +77,8 @@ def test_create_thermal_noise_mixed_type(): def test_incomplete_rates(): q0, q1 = cirq.LineQubit.range(2) gate_durations = {cirq.PhasedXZGate: 25.0} - heat_rate_GHz={q1: 1e-5} - cool_rate_GHz={q0: 1e-4} + heat_rate_GHz = {q1: 1e-5} + cool_rate_GHz = {q0: 1e-4} model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -74,8 +97,8 @@ def test_noise_from_zero_duration(): # Verify that a moment with no duration has no noise. q0, q1 = cirq.LineQubit.range(2) gate_durations = {} - heat_rate_GHz={q1: 1e-5} - cool_rate_GHz={q0: 1e-4} + heat_rate_GHz = {q1: 1e-5} + cool_rate_GHz = {q0: 1e-4} model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -83,7 +106,7 @@ def test_noise_from_zero_duration(): cool_rate_GHz=cool_rate_GHz, dephase_rate_GHz=None, require_physical_tag=False, - skip_measurements=False + skip_measurements=False, ) moment = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] @@ -94,8 +117,8 @@ def test_noise_from_virtual_gates(): # require_physical_tag is True. q0, q1 = cirq.LineQubit.range(2) gate_durations = {cirq.ZPowGate: 25.0} - heat_rate_GHz={q1: 1e-5} - cool_rate_GHz={q0: 1e-4} + heat_rate_GHz = {q1: 1e-5} + cool_rate_GHz = {q0: 1e-4} model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -103,7 +126,7 @@ def test_noise_from_virtual_gates(): cool_rate_GHz=cool_rate_GHz, dephase_rate_GHz=None, require_physical_tag=True, - skip_measurements=False + skip_measurements=False, ) moment = cirq.Moment(cirq.Z(q0), cirq.Z(q1)) assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] @@ -123,8 +146,8 @@ def test_noise_from_measurement(): cirq.ZPowGate: 25.0, cirq.MeasurementGate: 4000.0, } - heat_rate_GHz={q1: 1e-5} - cool_rate_GHz={q0: 1e-4} + heat_rate_GHz = {q1: 1e-5} + cool_rate_GHz = {q0: 1e-4} model = ThermalNoiseModel( qubit_dims={q0: 2, q1: 2}, gate_durations_ns=gate_durations, @@ -132,7 +155,7 @@ def test_noise_from_measurement(): cool_rate_GHz=cool_rate_GHz, dephase_rate_GHz=None, require_physical_tag=False, - skip_measurements=True + skip_measurements=True, ) moment = cirq.Moment(cirq.measure(q0, q1, key='m')) assert model.noisy_moment(moment, system_qubits=[q0, q1]) == [moment] @@ -155,7 +178,7 @@ def test_noisy_moment_one_qubit(): heat_rate_GHz={q0: 1e-5, q1: 2e-5}, cool_rate_GHz={q0: 1e-4, q1: 2e-4}, dephase_rate_GHz={q0: 3e-4, q1: 4e-4}, - require_physical_tag=False + require_physical_tag=False, ) gate = cirq.PhasedXZGate(x_exponent=1, z_exponent=0.5, axis_phase_exponent=0.25) moment = cirq.Moment(gate.on(q0)) @@ -169,8 +192,8 @@ def test_noisy_moment_one_qubit(): [9.99750343e-01, 0, 0, 9.91164267e-01], [0, 2.49656565e-03, 0, 0], [0, 0, 2.49656565e-04, 0], - [9.91164267e-01, 0, 0, 9.97503434e-01] - ] + [9.91164267e-01, 0, 0, 9.97503434e-01], + ], ) @@ -185,7 +208,7 @@ def test_noisy_moment_two_qubit(): heat_rate_GHz={q0: 1e-5, q1: 2e-5}, cool_rate_GHz={q0: 1e-4, q1: 2e-4}, dephase_rate_GHz={q0: 3e-4, q1: 4e-4}, - require_physical_tag=False + require_physical_tag=False, ) gate = cirq.CZ ** 0.5 moment = cirq.Moment(gate.on(q0, q1)) @@ -199,8 +222,8 @@ def test_noisy_moment_two_qubit(): [9.99750343e-01, 0, 0, 9.91164267e-01], [0, 2.49656565e-03, 0, 0], [0, 0, 2.49656565e-04, 0], - [9.91164267e-01, 0, 0, 9.97503434e-01] - ] + [9.91164267e-01, 0, 0, 9.97503434e-01], + ], ) noisy_choi_1 = cirq.kraus_to_choi(cirq.kraus(noisy_moment[1].operations[1])) assert np.allclose( @@ -209,6 +232,6 @@ def test_noisy_moment_two_qubit(): [9.99501372e-01, 0, 0, 9.87330937e-01], [0, 4.98627517e-03, 0, 0], [0, 0, 4.98627517e-04, 0], - [9.87330937e-01, 0, 0, 9.95013725e-01] - ] + [9.87330937e-01, 0, 0, 9.95013725e-01], + ], ) From 2cca23825f23b5be93db653ff39c8d1cd7a3804c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 11 Nov 2021 07:48:29 -0800 Subject: [PATCH 10/13] Cleanup before splitting --- cirq-core/cirq/devices/noise_properties.py | 1 + .../devices/google_noise_properties_test.py | 13 +++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index feb6931ca82..1229b3f8d83 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -28,6 +28,7 @@ TWO_QUBIT_GATES = SYMMETRIC_TWO_QUBIT_GATES | ASYMMETRIC_TWO_QUBIT_GATES +# TODO: missing per-device defaults @dataclass class NoiseProperties: """Noise-defining properties for a quantum device. diff --git a/cirq-google/cirq_google/devices/google_noise_properties_test.py b/cirq-google/cirq_google/devices/google_noise_properties_test.py index 4dd89dbd425..03f9dd37606 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties_test.py +++ b/cirq-google/cirq_google/devices/google_noise_properties_test.py @@ -1,5 +1,4 @@ from typing import Dict, List, Tuple -from cirq.ops.fsim_gate import PhasedFSimGate import numpy as np import pytest import cirq, cirq_google @@ -67,7 +66,6 @@ def test_str(): def test_repr_evaluation(): q0, q1 = cirq.LineQubit.range(2) props = sample_noise_properties([q0, q1], [(q0, q1), (q1, q0)]) - print(repr(props)) props_from_repr = eval(repr(props)) assert props_from_repr == props @@ -213,9 +211,13 @@ def test_two_qubit_gates(op): assert len(noisy_circuit.moments[2].operations) == 1 fsim_op = noisy_circuit.moments[2].operations[0] assert isinstance(fsim_op.gate, cirq.PhasedFSimGate) - assert fsim_op == PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02).on( - q0, q1 - ) + assert fsim_op == cirq.PhasedFSimGate( + theta=0.01, + zeta=0.03, + chi=0.04, + gamma=0.05, + phi=0.02 + ).on(q0, q1) # Thermal noise assert len(noisy_circuit.moments[3].operations) == 2 @@ -258,7 +260,6 @@ def test_measure_gates(): op = cirq.measure(*qubits, key='m') circuit = cirq.Circuit(cirq.measure(*qubits, key='m')) noisy_circuit = circuit.with_noise(model) - print(noisy_circuit.moments) assert len(noisy_circuit.moments) == 2 # Amplitude damping before measurement From bac680ed83659d7691c35e5af53b01b8aaae0628 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 11 Nov 2021 08:35:21 -0800 Subject: [PATCH 11/13] Add copyright notices. --- .../cirq/devices/insertion_noise_model.py | 14 ++++++++++ .../devices/insertion_noise_model_test.py | 18 +++++++++++-- cirq-core/cirq/devices/noise_properties.py | 15 ++++++++++- .../cirq/devices/noise_properties_test.py | 15 ++++++++++- cirq-core/cirq/devices/noise_utils.py | 14 ++++++++++ cirq-core/cirq/devices/noise_utils_test.py | 14 ++++++++++ cirq-core/cirq/devices/thermal_noise_model.py | 14 ++++++++++ .../cirq/devices/thermal_noise_model_test.py | 26 ++++++++++++++----- .../devices/google_noise_properties.py | 14 ++++++++++ .../devices/google_noise_properties_test.py | 24 ++++++++++++----- .../calibration_to_noise_properties.py | 15 ++++++++++- .../calibration_to_noise_properties_test.py | 18 ++++++++++--- 12 files changed, 180 insertions(+), 21 deletions(-) diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index 849831b0aaf..6fc3d44b1cc 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -1,3 +1,17 @@ +# 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 dataclasses import dataclass, field from typing import TYPE_CHECKING, Dict, List, Sequence diff --git a/cirq-core/cirq/devices/insertion_noise_model_test.py b/cirq-core/cirq/devices/insertion_noise_model_test.py index 8f8caa869a9..15216def58c 100644 --- a/cirq-core/cirq/devices/insertion_noise_model_test.py +++ b/cirq-core/cirq/devices/insertion_noise_model_test.py @@ -1,3 +1,17 @@ +# 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. + import cirq from cirq.devices.insertion_noise_model import InsertionNoiseModel from cirq.devices.noise_utils import ( @@ -13,7 +27,7 @@ def test_insertion_noise(): model = InsertionNoiseModel( {op_id0: cirq.T(q0), op_id1: cirq.H(q1)}, require_physical_tag=False ) - assert model.prepend == False + assert not model.prepend moment_0 = cirq.Moment(cirq.X(q0), cirq.X(q1)) assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ @@ -57,7 +71,7 @@ def test_require_physical_tag(): op_id0 = OpIdentifier(cirq.XPowGate, q0) op_id1 = OpIdentifier(cirq.ZPowGate, q1) model = InsertionNoiseModel({op_id0: cirq.T(q0), op_id1: cirq.H(q1)}) - assert model.require_physical_tag == True + assert model.require_physical_tag moment_0 = cirq.Moment(cirq.X(q0).with_tags(PHYSICAL_GATE_TAG), cirq.Z(q1)) assert model.noisy_moment(moment_0, system_qubits=[q0, q1]) == [ diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index 1229b3f8d83..e3da04b4346 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -1,4 +1,17 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice +# 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 dataclasses import dataclass, field from typing import Dict, Iterable, Sequence, TYPE_CHECKING, List, Set diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index fa56e34c58a..27927fc2c61 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -1,4 +1,17 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice +# 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, List, Tuple import numpy as np import cirq diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 595b06dbf9c..9d21e36254b 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -1,3 +1,17 @@ +# 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 dataclasses import dataclass from typing import TYPE_CHECKING, Sequence, Tuple import warnings diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py index 5bfa6d0f873..bfb619cddd2 100644 --- a/cirq-core/cirq/devices/noise_utils_test.py +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -1,3 +1,17 @@ +# 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. + import numpy as np import pytest diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py index 016d4286f8a..0bdc135d703 100644 --- a/cirq-core/cirq/devices/thermal_noise_model.py +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -1,3 +1,17 @@ +# 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 dataclasses import dataclass from typing import TYPE_CHECKING, Dict, List, Optional, Sequence, Union from scipy.linalg import expm diff --git a/cirq-core/cirq/devices/thermal_noise_model_test.py b/cirq-core/cirq/devices/thermal_noise_model_test.py index bf1a9b1d352..cbb273e55cd 100644 --- a/cirq-core/cirq/devices/thermal_noise_model_test.py +++ b/cirq-core/cirq/devices/thermal_noise_model_test.py @@ -1,3 +1,17 @@ +# 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. + import numpy as np import pytest @@ -48,8 +62,8 @@ def test_create_thermal_noise_per_qubit(): dephase_rate_GHz=dephase_rate_GHz, ) assert model.gate_durations_ns == gate_durations - assert model.require_physical_tag == True - assert model.skip_measurements == True + assert model.require_physical_tag + assert model.skip_measurements assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [1e-5, 3e-4]])) assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 2e-4], [2e-5, 4e-4]])) @@ -68,8 +82,8 @@ def test_create_thermal_noise_mixed_type(): dephase_rate_GHz=dephase_rate_GHz, ) assert model.gate_durations_ns == gate_durations - assert model.require_physical_tag == True - assert model.skip_measurements == True + assert model.require_physical_tag + assert model.skip_measurements assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [0, 3e-4]])) assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 2e-4], [0, 3e-4]])) @@ -87,8 +101,8 @@ def test_incomplete_rates(): dephase_rate_GHz=None, ) assert model.gate_durations_ns == gate_durations - assert model.require_physical_tag == True - assert model.skip_measurements == True + assert model.require_physical_tag + assert model.skip_measurements assert np.allclose(model.rate_matrix_GHz[q0], np.array([[0, 1e-4], [0, 0]])) assert np.allclose(model.rate_matrix_GHz[q1], np.array([[0, 0], [1e-5, 0]])) diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index 58755b76656..117d77ea841 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -1,3 +1,17 @@ +# 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 dataclasses import dataclass, field from typing import Dict, List, Set import numpy as np diff --git a/cirq-google/cirq_google/devices/google_noise_properties_test.py b/cirq-google/cirq_google/devices/google_noise_properties_test.py index 03f9dd37606..fb77b172444 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties_test.py +++ b/cirq-google/cirq_google/devices/google_noise_properties_test.py @@ -1,3 +1,17 @@ +# 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, List, Tuple import numpy as np import pytest @@ -211,13 +225,9 @@ def test_two_qubit_gates(op): assert len(noisy_circuit.moments[2].operations) == 1 fsim_op = noisy_circuit.moments[2].operations[0] assert isinstance(fsim_op.gate, cirq.PhasedFSimGate) - assert fsim_op == cirq.PhasedFSimGate( - theta=0.01, - zeta=0.03, - chi=0.04, - gamma=0.05, - phi=0.02 - ).on(q0, q1) + assert fsim_op == cirq.PhasedFSimGate(theta=0.01, zeta=0.03, chi=0.04, gamma=0.05, phi=0.02).on( + q0, q1 + ) # Thermal noise assert len(noisy_circuit.moments[3].operations) == 2 diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py index 727013bb84b..0e27d51772c 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties.py @@ -1,4 +1,17 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice +# 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 diff --git a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py index edca82e14a8..3e1b94afa8d 100644 --- a/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py +++ b/cirq-google/cirq_google/experimental/noise_models/calibration_to_noise_properties_test.py @@ -1,6 +1,18 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice -import cirq -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. + +import cirq, cirq_google from cirq_google.api import v2 from cirq_google.experimental.noise_models.calibration_to_noise_properties import ( noise_properties_from_calibration, From 73eb1b36c43597c61b1f94ee580892432532740b Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Fri, 12 Nov 2021 09:37:27 -0800 Subject: [PATCH 12/13] Remove redundant unitary_entanglement_fidelity --- cirq-core/cirq/devices/__init__.py | 1 - cirq-core/cirq/devices/noise_utils.py | 44 ------------------- cirq-core/cirq/devices/noise_utils_test.py | 44 ------------------- .../devices/google_noise_properties.py | 4 +- 4 files changed, 3 insertions(+), 90 deletions(-) diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index fbf5eab8f2b..2bb51e4371c 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -70,5 +70,4 @@ pauli_error_from_depolarization, average_error, decoherence_pauli_error, - unitary_entanglement_fidelity, ) diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 9d21e36254b..21bd79303c7 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -153,47 +153,3 @@ def decoherence_pauli_error(T1_ns: float, Tphi_ns: float, gate_time_ns: float) - py = px pz = 0.5 * (1 - exp2) - px return px + py + pz - - -def unitary_entanglement_fidelity(U_actual: np.ndarray, U_ideal: np.ndarray) -> np.ndarray: - """Entanglement fidelity between two unitaries. - - For unitary matrices, this is related to average unitary fidelity F by: - - :math:`F = \frac{F_e d + 1}{d + 1}` - - where d is the matrix dimension. - - Args: - U_actual : Matrix whose fidelity to U_ideal will be computed. This may - be a non-unitary matrix, i.e. the projection of a larger unitary - matrix into the computational subspace. - U_ideal : Unitary matrix to which U_actual will be compared. - - Both arguments may be vectorized, in that their shapes may be of the form - (...,M,M) (as long as both shapes can be broadcast together). - - Raises: - ValueError: if input arrays cannot be broadcast or have mismatching - trailing dimensions. - - Returns: - The entanglement fidelity between the two unitaries. For inputs with - shape (...,M,M), the output has shape (...). - """ - - def shapes_broadcastable(shape_0: Tuple[int, ...], shape_1: Tuple[int, ...]) -> bool: - return all((m == n) or (m == 1) or (n == 1) for m, n in zip(shape_0[::-1], shape_1[::-1])) - - U_actual = np.asarray(U_actual) - U_ideal = np.asarray(U_ideal) - if not shapes_broadcastable(U_actual.shape, U_ideal.shape): - raise ValueError('Input arrays do not have matching shapes.') - if U_actual.shape[-1] != U_actual.shape[-2]: - raise ValueError("Inputs' trailing dimensions must be equal (square).") - - dim = U_ideal.shape[-1] - - prod_trace = np.einsum('...ba,...ba->...', U_actual.conj(), U_ideal) - - return np.real((np.abs(prod_trace)) / dim) ** 2 diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py index bfb619cddd2..294acf222f9 100644 --- a/cirq-core/cirq/devices/noise_utils_test.py +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -24,7 +24,6 @@ pauli_error_from_depolarization, average_error, decoherence_pauli_error, - unitary_entanglement_fidelity, ) @@ -131,46 +130,3 @@ def test_decoherence_pauli_error(T1_ns, Tphi_ns, gate_time_ns): 1 - np.exp(-gate_time_ns * ((1 / (2 * T1_ns)) + 1 / Tphi_ns)) ) assert val == expected_output - - -# Surface-level tests to ensure nothing breaks that shouldn't. -def test_unitary_entanglement_fidelity(): - # cirq.H(q0) * cirq.H(q1) - U_actual = ( - np.array([[[[1, 1], [1, -1]], [[1, 1], [1, -1]]], [[[1, 1], [1, -1]], [[-1, -1], [-1, 1]]]]) - / 2 - ) - # cirq.X(q0) - U_ideal = np.array([[0, 1], [1, 0]]) - fidelity = unitary_entanglement_fidelity(U_actual, U_ideal) - assert fidelity.shape == (2, 2) - - -def test_invalid_unitary_entanglement_fidelity(): - # 4x4 cannot broadcast to 2x2 - U_actual_1 = np.array( - [ - [1, 1, 1, 1], - [1, -1, 1, -1], - [1, 1, -1, -1], - [1, 1, -1, 1], - ] - ) - U_ideal_1 = np.array([[0, 1], [1, 0]]) - with pytest.raises(ValueError, match='Input arrays do not have matching shapes.'): - _ = unitary_entanglement_fidelity(U_actual_1, U_ideal_1) - - U_actual_2 = np.array( - [ - [[1, 2, 3], [4, 5, 6]], - [[1, 2, 3], [4, 5, 6]], - ] - ) - U_ideal_2 = np.array( - [ - [[1, 2, 3], [4, 5, 6]], - [[1, 2, 3], [4, 5, 6]], - ] - ) - with pytest.raises(ValueError, match='trailing dimensions must be equal'): - _ = unitary_entanglement_fidelity(U_actual_2, U_ideal_2) diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index 117d77ea841..edea8c9e107 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -20,7 +20,9 @@ from cirq import devices from cirq.devices.noise_utils import ( OpIdentifier, - unitary_entanglement_fidelity, +) +from cirq_google.optimizers.two_qubit_gates.math_utils import ( + unitary_entanglement_fidelity ) From f85e27ae571f1eb67f543e36429eeb744a4ef2d6 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Tue, 7 Dec 2021 09:41:16 -0800 Subject: [PATCH 13/13] Convert to using GateFamily --- cirq-core/cirq/__init__.py | 1 + cirq-core/cirq/devices/__init__.py | 1 + .../cirq/devices/insertion_noise_model.py | 24 +- cirq-core/cirq/devices/noise_properties.py | 44 +- .../cirq/devices/noise_properties_test.py | 1 + cirq-core/cirq/devices/noise_utils.py | 69 ++- cirq-core/cirq/devices/noise_utils_test.py | 8 + cirq-core/cirq/json_resolver_cache.py | 1 + .../json_test_data/NoiseProperties.json | 87 +--- .../json_test_data/NoiseProperties.repr | 2 +- .../json_test_data/OpIdentifier.json | 10 + .../json_test_data/OpIdentifier.repr | 4 + .../devices/google_noise_properties.py | 41 +- .../json_test_data/GoogleNoiseProperties.json | 429 +++++++++++------- 14 files changed, 391 insertions(+), 331 deletions(-) create mode 100644 cirq-core/cirq/protocols/json_test_data/OpIdentifier.json create mode 100644 cirq-core/cirq/protocols/json_test_data/OpIdentifier.repr diff --git a/cirq-core/cirq/__init__.py b/cirq-core/cirq/__init__.py index ec5a173a6ab..4a2d0f355ce 100644 --- a/cirq-core/cirq/__init__.py +++ b/cirq-core/cirq/__init__.py @@ -90,6 +90,7 @@ NoiseModel, NoiseProperties, NoiseModelFromNoiseProperties, + OpIdentifier, SymmetricalQidPair, UNCONSTRAINED_DEVICE, NamedTopology, diff --git a/cirq-core/cirq/devices/__init__.py b/cirq-core/cirq/devices/__init__.py index 2bb51e4371c..7c303725331 100644 --- a/cirq-core/cirq/devices/__init__.py +++ b/cirq-core/cirq/devices/__init__.py @@ -62,6 +62,7 @@ ) from cirq.devices.noise_utils import ( + OpIdentifier, decay_constant_to_xeb_fidelity, decay_constant_to_pauli_error, pauli_error_to_decay_constant, diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index 6fc3d44b1cc..ed8409c3ca6 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -13,7 +13,7 @@ # limitations under the License. from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Dict, List, Sequence +from typing import TYPE_CHECKING, Dict, List, Optional, Sequence from cirq import devices, ops from cirq.devices.noise_utils import ( @@ -53,21 +53,17 @@ def noisy_moment( if self.require_physical_tag and PHYSICAL_GATE_TAG not in op.tags: # Only non-virtual gates get noise applied. continue - op_id = OpIdentifier(type(op.gate), *op.qubits) - if op_id in self.ops_added: - noise_ops.append(self.ops_added[op_id]) - continue - # Find the closest match, if one exists. - parent_id = OpIdentifier(object, *op.qubits) - for added_id in self.ops_added: - if added_id.qubits != parent_id.qubits: + match_id: Optional[OpIdentifier] = None + for op_id in self.ops_added: + if op not in op_id: continue - if not issubclass(op_id.gate_type, added_id.gate_type): + elif match_id is None: + match_id = op_id continue - if issubclass(added_id.gate_type, parent_id.gate_type): - parent_id = added_id - if parent_id.gate_type != object: - noise_ops.append(self.ops_added[parent_id]) + elif op_id.is_proper_subtype_of(match_id): + match_id = op_id + if match_id is not None: + noise_ops.append(self.ops_added[match_id]) if not noise_ops: return [moment] if self.prepend: diff --git a/cirq-core/cirq/devices/noise_properties.py b/cirq-core/cirq/devices/noise_properties.py index e3da04b4346..3e472feb657 100644 --- a/cirq-core/cirq/devices/noise_properties.py +++ b/cirq-core/cirq/devices/noise_properties.py @@ -126,31 +126,6 @@ def two_qubit_gates(cls) -> Set[type]: def expected_gates(cls) -> Set[type]: return cls.single_qubit_gates() | cls.two_qubit_gates() - @classmethod - def canonical_gates(cls) -> Dict[type, 'cirq.Gate']: - return { - ops.ZPowGate: ops.ZPowGate(), - ops.PhasedXZGate: ops.PhasedXZGate(x_exponent=0, z_exponent=0, axis_phase_exponent=0), - ops.MeasurementGate: ops.MeasurementGate(num_qubits=1), - ops.ResetChannel: ops.ResetChannel(), - ops.FSimGate: ops.FSimGate(theta=0, phi=0), - ops.PhasedFSimGate: ops.PhasedFSimGate(theta=0), - ops.ISwapPowGate: ops.ISwapPowGate(), - ops.CZPowGate: ops.CZPowGate(), - } - - @classmethod - def get_canonical_gate(cls, gate_type: type) -> 'cirq.Gate': - return cls.canonical_gates()[gate_type] - - @classmethod - def _identifier_to_op(cls, op_id: OpIdentifier) -> 'cirq.Operation': - return cls.get_canonical_gate(op_id.gate_type).on(*op_id.qubits) - - @classmethod - def _op_to_identifier(cls, op: 'cirq.Operation'): - return OpIdentifier(type(op.gate), *op.qubits) - def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: """Returns the portion of Pauli error from depolarization. @@ -268,20 +243,16 @@ def __repr__(self) -> str: def _json_dict_(self): storage_gate_times = { - self.get_canonical_gate(key): val for key, val in self.gate_times_ns.items() - } - storage_pauli_errors = { - self._identifier_to_op(op_id): val for op_id, val in self.gate_pauli_errors.items() + protocols.json_cirq_type(key): val for key, val in self.gate_times_ns.items() } return { - 'cirq_type': 'NoiseProperties', # JSON requires mappings to have keys of basic types. # Pairs must be sorted to ensure consistent serialization. 'gate_times_ns': sorted(storage_gate_times.items(), key=str), 'T1_ns': sorted(self.T1_ns.items()), 'Tphi_ns': sorted(self.Tphi_ns.items()), 'ro_fidelities': sorted(self.ro_fidelities.items()), - 'gate_pauli_errors': sorted(storage_pauli_errors.items(), key=str), + 'gate_pauli_errors': sorted(self.gate_pauli_errors.items(), key=str), 'validate': self.validate, } @@ -289,14 +260,13 @@ def _json_dict_(self): def _from_json_dict_( cls, gate_times_ns, T1_ns, Tphi_ns, ro_fidelities, gate_pauli_errors, validate, **kwargs ): - gate_type_times = {type(gate): val for gate, val in gate_times_ns} - op_id_pauli_errors = {cls._op_to_identifier(op): val for op, val in gate_pauli_errors} + gate_type_times = {protocols.cirq_type_from_json(gate): val for gate, val in gate_times_ns} return NoiseProperties( gate_times_ns=gate_type_times, - T1_ns={k: v for k, v in T1_ns}, - Tphi_ns={k: v for k, v in Tphi_ns}, - ro_fidelities={k: v for k, v in ro_fidelities}, - gate_pauli_errors=op_id_pauli_errors, + T1_ns=dict(T1_ns), + Tphi_ns=dict(Tphi_ns), + ro_fidelities=dict(ro_fidelities), + gate_pauli_errors=dict(gate_pauli_errors), validate=validate, ) diff --git a/cirq-core/cirq/devices/noise_properties_test.py b/cirq-core/cirq/devices/noise_properties_test.py index 27927fc2c61..a4b801ea927 100644 --- a/cirq-core/cirq/devices/noise_properties_test.py +++ b/cirq-core/cirq/devices/noise_properties_test.py @@ -77,6 +77,7 @@ def test_json_serialization(): q0 = cirq.LineQubit(0) props = sample_noise_properties([q0], []) props_json = cirq.to_json(props) + print(props_json) props_from_json = cirq.read_json(json_text=props_json) assert props_from_json == props diff --git a/cirq-core/cirq/devices/noise_utils.py b/cirq-core/cirq/devices/noise_utils.py index 21bd79303c7..c3b5f346681 100644 --- a/cirq-core/cirq/devices/noise_utils.py +++ b/cirq-core/cirq/devices/noise_utils.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from dataclasses import dataclass -from typing import TYPE_CHECKING, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union import warnings import numpy as np +from cirq import ops, protocols, value + if TYPE_CHECKING: import cirq @@ -25,20 +26,57 @@ PHYSICAL_GATE_TAG = 'physical_gate' -@dataclass(frozen=True) +@value.value_equality(distinct_child_types=True) class OpIdentifier: """Identifies an operation by gate and (optionally) target qubits.""" - gate_type: type - qubits: Sequence['cirq.Qid'] + def __init__(self, gate_type: Type['cirq.Gate'], *qubits: 'cirq.Qid'): + self._gate_type = gate_type + self._gate_family = ops.GateFamily(gate_type) + self._qubits: Tuple['cirq.Qid', ...] = tuple(qubits) + + @property + def gate_type(self) -> Type['cirq.Gate']: + # set to a type during initialization, never modified + return self._gate_type - def __init__(self, gate_type: type, *qubits: 'cirq.Qid'): - object.__setattr__(self, 'gate_type', gate_type) - object.__setattr__(self, 'qubits', qubits) + @property + def qubits(self) -> Tuple['cirq.Qid', ...]: + return self._qubits + + def _predicate(self, *args, **kwargs): + return self._gate_family._predicate(*args, **kwargs) def swapped(self): return OpIdentifier(self.gate_type, *self.qubits[::-1]) + def is_proper_subtype_of(self, op_id: 'OpIdentifier'): + """Returns true if this is contained within op_id, but not equal to it. + + If this returns true, (x in self) implies (x in op_id), but the reverse + implication does not hold. op_id must be more general than self (either + by accepting any qubits or having a more general gate type) for this + to return true. + """ + more_specific_qubits = self.qubits and not op_id.qubits + more_specific_gate = self.gate_type != op_id.gate_type and issubclass( + self.gate_type, op_id.gate_type + ) + return ( + (more_specific_qubits or more_specific_gate) + and (more_specific_qubits or self.qubits == op_id.qubits) + and (more_specific_gate or self.gate_type == op_id.gate_type) + ) + + def __contains__(self, item: Union[ops.Gate, ops.Operation]) -> bool: + if isinstance(item, ops.Gate): + return (not self._qubits) and self._predicate(item) + return ( + (not self.qubits or (item.qubits == self._qubits)) + and item.gate is not None + and self._predicate(item.gate) + ) + def __str__(self): return f'{self.gate_type}{self.qubits}' @@ -47,6 +85,21 @@ def __repr__(self) -> str: qubits = ', '.join(map(repr, self.qubits)) return f'cirq.devices.noise_utils.OpIdentifier({fullname}, {qubits})' + def _value_equality_values_(self) -> Any: + return (self.gate_type, self.qubits) + + def _json_dict_(self) -> Dict[str, Any]: + gate_json = protocols.json_cirq_type(self._gate_type) + return { + 'gate_type': gate_json, + 'qubits': self._qubits, + } + + @classmethod + def _from_json_dict_(cls, gate_type, qubits, **kwargs) -> 'OpIdentifier': + gate_type = protocols.cirq_type_from_json(gate_type) + return cls(gate_type, *qubits) + # TODO: expose all from top-level cirq? def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float: diff --git a/cirq-core/cirq/devices/noise_utils_test.py b/cirq-core/cirq/devices/noise_utils_test.py index 294acf222f9..61b5852ced4 100644 --- a/cirq-core/cirq/devices/noise_utils_test.py +++ b/cirq-core/cirq/devices/noise_utils_test.py @@ -15,7 +15,9 @@ import numpy as np import pytest +import cirq from cirq.devices.noise_utils import ( + OpIdentifier, decay_constant_to_xeb_fidelity, decay_constant_to_pauli_error, pauli_error_to_decay_constant, @@ -27,6 +29,12 @@ ) +def test_op_id(): + op_id = OpIdentifier(cirq.XPowGate) + assert cirq.X(cirq.LineQubit(1)) in op_id + assert cirq.Rx(rads=1) in op_id + + @pytest.mark.parametrize( 'decay_constant,num_qubits,expected_output', [ diff --git a/cirq-core/cirq/json_resolver_cache.py b/cirq-core/cirq/json_resolver_cache.py index 6c532f641b4..12c2a0fa357 100644 --- a/cirq-core/cirq/json_resolver_cache.py +++ b/cirq-core/cirq/json_resolver_cache.py @@ -115,6 +115,7 @@ def _parallel_gate_op(gate, qubits): 'NamedQid': cirq.NamedQid, 'NoIdentifierQubit': cirq.testing.NoIdentifierQubit, 'NoiseProperties': cirq.NoiseProperties, + 'OpIdentifier': cirq.OpIdentifier, '_PauliX': cirq.ops.pauli_gates._PauliX, '_PauliY': cirq.ops.pauli_gates._PauliY, '_PauliZ': cirq.ops.pauli_gates._PauliZ, diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json index c825fa1598d..83a5c47893a 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.json @@ -2,71 +2,35 @@ "cirq_type": "NoiseProperties", "gate_times_ns": [ [ - { - "cirq_type": "CZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "CZPowGate", 32.0 ], [ - { - "cirq_type": "FSimGate", - "theta": 0.0, - "phi": 0.0 - }, + "FSimGate", 32.0 ], [ - { - "cirq_type": "ISwapPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "ISwapPowGate", 32.0 ], [ - { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, + "MeasurementGate", 4000.0 ], [ - { - "cirq_type": "PhasedFSimGate", - "theta": 0.0, - "zeta": 0.0, - "chi": 0.0, - "gamma": 0.0, - "phi": 0.0 - }, + "PhasedFSimGate", 32.0 ], [ - { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, + "PhasedXZGate", 25.0 ], [ - { - "cirq_type": "ResetChannel", - "dimension": 2 - }, + "ResetChannel", 250.0 ], [ - { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "ZPowGate", 25.0 ] ], @@ -103,13 +67,8 @@ "gate_pauli_errors": [ [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "ResetChannel", "qubits": [ { "cirq_type": "LineQubit", @@ -121,11 +80,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ResetChannel", - "dimension": 2 - }, + "cirq_type": "OpIdentifier", + "gate_type": "ZPowGate", "qubits": [ { "cirq_type": "LineQubit", @@ -137,12 +93,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "MeasurementGate", "qubits": [ { "cirq_type": "LineQubit", @@ -154,13 +106,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, + "cirq_type": "OpIdentifier", + "gate_type": "PhasedXZGate", "qubits": [ { "cirq_type": "LineQubit", @@ -172,4 +119,4 @@ ] ], "validate": true -} +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr index e17be7d33d1..42c51a7eedc 100644 --- a/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr +++ b/cirq-core/cirq/protocols/json_test_data/NoiseProperties.repr @@ -18,4 +18,4 @@ cirq.NoiseProperties( cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_gates.ZPowGate, cirq.LineQubit(0)): 0.001, cirq.devices.noise_utils.OpIdentifier(cirq.ops.common_channels.ResetChannel, cirq.LineQubit(0)): 0.001 } -) +) \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/OpIdentifier.json b/cirq-core/cirq/protocols/json_test_data/OpIdentifier.json new file mode 100644 index 00000000000..d33b909367d --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/OpIdentifier.json @@ -0,0 +1,10 @@ +{ + "cirq_type": "OpIdentifier", + "gate_type": "XPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] +} \ No newline at end of file diff --git a/cirq-core/cirq/protocols/json_test_data/OpIdentifier.repr b/cirq-core/cirq/protocols/json_test_data/OpIdentifier.repr new file mode 100644 index 00000000000..6b991bb0b2c --- /dev/null +++ b/cirq-core/cirq/protocols/json_test_data/OpIdentifier.repr @@ -0,0 +1,4 @@ +cirq.devices.noise_utils.OpIdentifier( + cirq.ops.common_gates.XPowGate, + cirq.LineQubit(1) +) \ No newline at end of file diff --git a/cirq-google/cirq_google/devices/google_noise_properties.py b/cirq-google/cirq_google/devices/google_noise_properties.py index edea8c9e107..e4b63ff221e 100644 --- a/cirq-google/cirq_google/devices/google_noise_properties.py +++ b/cirq-google/cirq_google/devices/google_noise_properties.py @@ -14,6 +14,7 @@ from dataclasses import dataclass, field from typing import Dict, List, Set +from cirq import protocols import numpy as np import cirq, cirq_google @@ -21,9 +22,7 @@ from cirq.devices.noise_utils import ( OpIdentifier, ) -from cirq_google.optimizers.two_qubit_gates.math_utils import ( - unitary_entanglement_fidelity -) +from cirq_google.optimizers.two_qubit_gates.math_utils import unitary_entanglement_fidelity @dataclass @@ -68,13 +67,6 @@ def __post_init__(self): def two_qubit_gates(cls) -> Set[type]: return super().two_qubit_gates() | {cirq_google.SycamoreGate} - @classmethod - def canonical_gates(cls) -> Dict[type, 'cirq.Gate']: - return { - **super().canonical_gates(), - cirq_google.SycamoreGate: cirq_google.SycamoreGate(), - } - def get_depolarizing_error(self) -> Dict[OpIdentifier, float]: if self._depolarizing_error: return self._depolarizing_error @@ -125,12 +117,7 @@ def __repr__(self) -> str: def _json_dict_(self): json_dict = super()._json_dict_() - json_dict['cirq_type'] = 'GoogleNoiseProperties' - storage_fsim_errors = { - self.get_canonical_gate(op_id.gate_type).on(*op_id.qubits): val - for op_id, val in self.fsim_errors.items() - } - json_dict['fsim_errors'] = sorted(storage_fsim_errors.items(), key=str) + json_dict['fsim_errors'] = sorted(self.fsim_errors.items(), key=str) return json_dict @classmethod @@ -146,25 +133,15 @@ def _from_json_dict_( **kwargs, ): # TODO: overlaps op_ids with same qubits in different order - gate_type_times = {type(gate): val for gate, val in gate_times_ns} - op_id_pauli_errors = {} - for op, val in gate_pauli_errors: - op_id = cls._op_to_identifier(op) - op_id_pauli_errors[op_id] = val - op_id_pauli_errors[op_id.swapped()] = val - op_id_fsim_errors = {} - for op, val in fsim_errors: - op_id = cls._op_to_identifier(op) - op_id_fsim_errors[op_id] = val - op_id_fsim_errors[op_id.swapped()] = val + gate_type_times = {protocols.cirq_type_from_json(gate): val for gate, val in gate_times_ns} return GoogleNoiseProperties( gate_times_ns=gate_type_times, - T1_ns={k: v for k, v in T1_ns}, - Tphi_ns={k: v for k, v in Tphi_ns}, - ro_fidelities={k: v for k, v in ro_fidelities}, - gate_pauli_errors=op_id_pauli_errors, + T1_ns=dict(T1_ns), + Tphi_ns=dict(Tphi_ns), + ro_fidelities=dict(ro_fidelities), + gate_pauli_errors=dict(gate_pauli_errors), validate=validate, - fsim_errors=op_id_fsim_errors, + fsim_errors=dict(fsim_errors), ) diff --git a/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json index 4727906919b..e71869651a7 100644 --- a/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json +++ b/cirq-google/cirq_google/json_test_data/GoogleNoiseProperties.json @@ -2,78 +2,40 @@ "cirq_type": "GoogleNoiseProperties", "gate_times_ns": [ [ - { - "cirq_type": "CZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "CZPowGate", 32.0 ], [ - { - "cirq_type": "FSimGate", - "theta": 0.0, - "phi": 0.0 - }, + "FSimGate", 32.0 ], [ - { - "cirq_type": "ISwapPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "ISwapPowGate", 32.0 ], [ - { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, + "MeasurementGate", 4000.0 ], [ - { - "cirq_type": "PhasedFSimGate", - "theta": 0.0, - "zeta": 0.0, - "chi": 0.0, - "gamma": 0.0, - "phi": 0.0 - }, + "PhasedFSimGate", 32.0 ], [ - { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, + "PhasedXZGate", 25.0 ], [ - { - "cirq_type": "ResetChannel", - "dimension": 2 - }, + "ResetChannel", 250.0 ], [ - { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, - 25.0 + "SycamoreGate", + 32.0 ], [ - { - "cirq_type": "SycamoreGate" - }, - 32.0 + "ZPowGate", + 25.0 ] ], "T1_ns": [ @@ -133,33 +95,34 @@ "gate_pauli_errors": [ [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "CZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "ResetChannel", "qubits": [ { "cirq_type": "LineQubit", "x": 0 - }, + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "ResetChannel", + "qubits": [ { "cirq_type": "LineQubit", "x": 1 } ] }, - 0.01 + 0.001 ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "FSimGate", - "theta": 0.0, - "phi": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "CZPowGate", "qubits": [ { "cirq_type": "LineQubit", @@ -175,20 +138,16 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ISwapPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "CZPowGate", "qubits": [ { "cirq_type": "LineQubit", - "x": 0 + "x": 1 }, { "cirq_type": "LineQubit", - "x": 1 + "x": 0 } ] }, @@ -196,15 +155,34 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedFSimGate", - "theta": 0.0, - "zeta": 0.0, - "chi": 0.0, - "gamma": 0.0, - "phi": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "ZPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "ZPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + } + ] + }, + 0.001 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "FSimGate", "qubits": [ { "cirq_type": "LineQubit", @@ -220,47 +198,59 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "FSimGate", "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, { "cirq_type": "LineQubit", "x": 0 } ] }, - 0.001 + 0.01 ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedXZGate", - "axis_phase_exponent": 0, - "x_exponent": 0, - "z_exponent": 0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "PhasedFSimGate", "qubits": [ + { + "cirq_type": "LineQubit", + "x": 0 + }, { "cirq_type": "LineQubit", "x": 1 } ] }, - 0.001 + 0.01 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "PhasedFSimGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.01 ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ResetChannel", - "dimension": 2 - }, + "cirq_type": "OpIdentifier", + "gate_type": "MeasurementGate", "qubits": [ { "cirq_type": "LineQubit", @@ -272,11 +262,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ResetChannel", - "dimension": 2 - }, + "cirq_type": "OpIdentifier", + "gate_type": "MeasurementGate", "qubits": [ { "cirq_type": "LineQubit", @@ -288,12 +275,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "PhasedXZGate", "qubits": [ { "cirq_type": "LineQubit", @@ -305,12 +288,8 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "PhasedXZGate", "qubits": [ { "cirq_type": "LineQubit", @@ -322,46 +301,42 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, + "cirq_type": "OpIdentifier", + "gate_type": "ISwapPowGate", "qubits": [ { "cirq_type": "LineQubit", "x": 0 + }, + { + "cirq_type": "LineQubit", + "x": 1 } ] }, - 0.001 + 0.01 ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "MeasurementGate", - "num_qubits": 1, - "key": "", - "invert_mask": [] - }, + "cirq_type": "OpIdentifier", + "gate_type": "ISwapPowGate", "qubits": [ { "cirq_type": "LineQubit", "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 } ] }, - 0.001 + 0.01 ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "SycamoreGate" - }, + "cirq_type": "OpIdentifier", + "gate_type": "SycamoreGate", "qubits": [ { "cirq_type": "LineQubit", @@ -374,18 +349,31 @@ ] }, 0.01 + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "SycamoreGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + 0.01 ] ], "validate": true, "fsim_errors": [ [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "CZPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "CZPowGate", "qubits": [ { "cirq_type": "LineQubit", @@ -408,12 +396,32 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "FSimGate", - "theta": 0.0, - "phi": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "CZPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "FSimGate", "qubits": [ { "cirq_type": "LineQubit", @@ -436,12 +444,32 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "ISwapPowGate", - "exponent": 1.0, - "global_shift": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "FSimGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "PhasedFSimGate", "qubits": [ { "cirq_type": "LineQubit", @@ -464,15 +492,32 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "PhasedFSimGate", - "theta": 0.0, - "zeta": 0.0, - "chi": 0.0, - "gamma": 0.0, - "phi": 0.0 - }, + "cirq_type": "OpIdentifier", + "gate_type": "PhasedFSimGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "ISwapPowGate", "qubits": [ { "cirq_type": "LineQubit", @@ -495,10 +540,32 @@ ], [ { - "cirq_type": "GateOperation", - "gate": { - "cirq_type": "SycamoreGate" - }, + "cirq_type": "OpIdentifier", + "gate_type": "ISwapPowGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "SycamoreGate", "qubits": [ { "cirq_type": "LineQubit", @@ -518,6 +585,30 @@ "gamma": 0.05, "phi": 0.02 } + ], + [ + { + "cirq_type": "OpIdentifier", + "gate_type": "SycamoreGate", + "qubits": [ + { + "cirq_type": "LineQubit", + "x": 1 + }, + { + "cirq_type": "LineQubit", + "x": 0 + } + ] + }, + { + "cirq_type": "PhasedFSimGate", + "theta": 0.01, + "zeta": 0.03, + "chi": 0.04, + "gamma": 0.05, + "phi": 0.02 + } ] ] } \ No newline at end of file