diff --git a/cirq/__init__.py b/cirq/__init__.py index 9f27af266c7..6bc4262f8c6 100644 --- a/cirq/__init__.py +++ b/cirq/__init__.py @@ -93,6 +93,8 @@ NO_NOISE, NOISE_MODEL_LIKE, NoiseModel, + NoiseModelFromNoiseProperties, + NoiseProperties, OpIdentifier, SymmetricalQidPair, UNCONSTRAINED_DEVICE, diff --git a/cirq/devices/__init__.py b/cirq/devices/__init__.py index 1af4f58c8a7..49b0708a5f0 100644 --- a/cirq/devices/__init__.py +++ b/cirq/devices/__init__.py @@ -62,6 +62,11 @@ ThermalNoiseModel, ) +from cirq.devices.noise_properties import ( + NoiseModelFromNoiseProperties, + NoiseProperties, +) + from cirq.devices.noise_utils import ( OpIdentifier, decay_constant_to_xeb_fidelity, diff --git a/cirq/devices/noise_properties.py b/cirq/devices/noise_properties.py index b708a24eea6..a2ee49ff0fd 100644 --- a/cirq/devices/noise_properties.py +++ b/cirq/devices/noise_properties.py @@ -1,227 +1,44 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice -import warnings -from typing import Sequence, TYPE_CHECKING, List -from itertools import product -from cirq import circuits, ops, protocols, devices -import numpy as np +# 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. + +"""Classes for representing device noise. + +NoiseProperties is an abstract class for capturing metrics of a device that can +be translated into noise models. NoiseModelFromNoiseProperties consumes those +noise models to produce a single noise model which replicates device noise. +""" + +import abc +from typing import Iterable, Sequence, TYPE_CHECKING, List + +from cirq import _import, ops, protocols, devices +from cirq.devices.noise_utils import ( + PHYSICAL_GATE_TAG, +) + +circuits = _import.LazyLoader("circuits", globals(), "cirq.circuits.circuit") if TYPE_CHECKING: - from typing import Iterable import cirq -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. +class NoiseProperties(abc.ABC): + """Noise-defining properties for a quantum device.""" - Only one of decay_constant, xeb_fidelity, and pauli_error should be specified. - - 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 - - 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') - - 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 - ): - raise ValueError( - 'Only one of xeb fidelity, pauli error, or decay constant should be defined' - ) - - 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) - - @property - def decay_constant(self): - return self._p - - @property - def p00(self): - return self._p00 - - @property - def p11(self): - return self._p11 - - @property - def pauli_error(self): - return self.decay_constant_to_pauli_error() - - @property - def t1_ns(self): - return self._t1_ns - - @property - def xeb(self): - return self.decay_constant_to_xeb_fidelity() - - 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 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 - - 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 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 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). - - 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(self, t: float): - """Calculates the amount of pauli error from depolarization. - Unlike the other methods, this computes a specific case (over time t). - - If pauli error from t1 decay is more than total pauli error, just return the pauli error. - - 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 - else: - warnings.warn( - "Pauli error from T1 decay is greater than total Pauli error", RuntimeWarning - ) - return self.pauli_error - - 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 - else: - p = p11 / (p00 + p11) - gamma = p11 / p - moments.append( - circuits.Moment( - ops.GeneralizedAmplitudeDampingChannel(p=p, gamma=gamma)(q) for q in measurement_qubits - ) - ) - - -def _apply_depol_noise(pauli_error, moments, system_qubits): - - _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(circuits.Moment(ops.depolarize(p_other)(q) for q in system_qubits)) - - -def _apply_amplitude_damp_noise(duration, t1, moments, system_qubits): - moments.append( - circuits.Moment(ops.amplitude_damp(1 - np.exp(-duration / t1)).on_each(system_qubits)) - ) + @abc.abstractmethod + def build_noise_models(self) -> List['cirq.NoiseModel']: + """Construct all NoiseModels associated with this NoiseProperties.""" class NoiseModelFromNoiseProperties(devices.NoiseModel): @@ -234,37 +51,78 @@ 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 noisy_moment( - self, moment: circuits.Moment, system_qubits: Sequence['cirq.Qid'] - ) -> 'cirq.OP_TREE': - moments: List[circuits.Moment] = [] + def virtual_predicate(self, op: 'cirq.Operation') -> bool: + """Returns True if an operation is virtual. - 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) + Device-specific subclasses should implement this method to mark any + operations which their device handles outside the quantum hardware. - 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 + Args: + op: an operation to check for virtual indicators. + + Returns: + True if `op` is virtual. + """ + return False + + 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(circuits.Moment(split_measure_ops)) + + # Append PHYSICAL_GATE_TAG to non-virtual ops in the input circuit, + # using `self.virtual_predicate` to determine virtuality. + new_moments = [] + for moment in split_measure_moments: + 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 + ] + # Both physical and virtual operations remain in the circuit, but + # only ops with PHYSICAL_GATE_TAG will receive noise. + if virtual_ops: + # Only subclasses will trigger this case. + new_moments.append(circuits.Moment(virtual_ops)) # coverage: ignore + if physical_ops: + new_moments.append(circuits.Moment(physical_ops)) + + split_measure_circuit = circuits.Circuit(new_moments) + + # Add noise from each noise model. The PHYSICAL_GATE_TAGs added + # previously allow noise models to distinguish physical gates from + # those added by other noise models. + 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(circuits.Moment(combined_measure_ops)) + return final_moments diff --git a/cirq/devices/noise_properties_test.py b/cirq/devices/noise_properties_test.py index eea1ed4d8d8..c848af659b3 100644 --- a/cirq/devices/noise_properties_test.py +++ b/cirq/devices/noise_properties_test.py @@ -1,298 +1,62 @@ -# pylint: disable=wrong-or-nonexistent-copyright-notice -import pytest +# 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 List, Tuple import cirq -from cirq.testing import assert_equivalent_op_tree + +from cirq.devices.insertion_noise_model import InsertionNoiseModel from cirq.devices.noise_properties import ( 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 - - -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) - - 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) +from cirq.devices.noise_utils import ( + OpIdentifier, + PHYSICAL_GATE_TAG, +) -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 +# These properties are for testing purposes only - they are not representative +# of device behavior for any existing hardware. +class SampleNoiseProperties(NoiseProperties): + def __init__(self, system_qubits: List[cirq.Qid], qubit_pairs: List[Tuple[cirq.Qid, cirq.Qid]]): + self.qubits = system_qubits + self.qubit_pairs = qubit_pairs - t1_ns = 2000.0 - p11 = 0.01 + def build_noise_models(self): + add_h = InsertionNoiseModel({OpIdentifier(cirq.Gate, q): cirq.H(q) for q in self.qubits}) + add_iswap = InsertionNoiseModel( + {OpIdentifier(cirq.Gate, *qs): cirq.ISWAP(*qs) for qs in self.qubit_pairs} + ) + return [add_h, add_iswap] - # 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)] +def test_sample_model(): + q0, q1 = cirq.LineQubit.range(2) + props = SampleNoiseProperties([q0, q1], [(q0, q1), (q1, q0)]) + model = NoiseModelFromNoiseProperties(props) 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')]), + cirq.X(q0), cirq.CNOT(q0, q1), cirq.Z(q1), cirq.measure(q0, q1, key='meas') ) - - # 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 + noisy_circuit = circuit.with_noise(model) 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) + cirq.Moment(cirq.X(q0).with_tags(PHYSICAL_GATE_TAG)), + cirq.Moment(cirq.H(q0)), + cirq.Moment(cirq.CNOT(q0, q1).with_tags(PHYSICAL_GATE_TAG)), + cirq.Moment(cirq.ISWAP(q0, q1)), + cirq.Moment(cirq.Z(q1).with_tags(PHYSICAL_GATE_TAG)), + cirq.Moment(cirq.H(q1)), + cirq.Moment(cirq.measure(q0, q1, key='meas')), + cirq.Moment(cirq.H(q0), cirq.H(q1)), + ) + assert noisy_circuit == expected_circuit diff --git a/cirq/protocols/json_test_data/spec.py b/cirq/protocols/json_test_data/spec.py index 38cf7e0b558..9df089bc156 100644 --- a/cirq/protocols/json_test_data/spec.py +++ b/cirq/protocols/json_test_data/spec.py @@ -179,6 +179,7 @@ 'ParamDictType', # utility: 'CliffordSimulator', + 'NoiseModelFromNoiseProperties', 'Simulator', 'StabilizerSampler', 'Unique',