From 3c3a425951051c2241e7b8b31c0cdbccf5bc8920 Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 26 May 2022 13:52:35 -0700 Subject: [PATCH 1/2] Support prepending noise --- .../cirq/contrib/noise_models/noise_models.py | 21 ++++++++---- .../contrib/noise_models/noise_models_test.py | 33 +++++++++++++++++++ .../cirq/devices/insertion_noise_model.py | 2 +- cirq-core/cirq/devices/noise_model.py | 17 ++++++++-- cirq-core/cirq/devices/noise_model_test.py | 19 +++++++++++ cirq-core/cirq/devices/thermal_noise_model.py | 6 +++- .../cirq/devices/thermal_noise_model_test.py | 30 +++++++++++++++++ 7 files changed, 118 insertions(+), 10 deletions(-) diff --git a/cirq-core/cirq/contrib/noise_models/noise_models.py b/cirq-core/cirq/contrib/noise_models/noise_models.py index c7a6f0afb05..8d179d2348c 100644 --- a/cirq-core/cirq/contrib/noise_models/noise_models.py +++ b/cirq-core/cirq/contrib/noise_models/noise_models.py @@ -29,26 +29,29 @@ class DepolarizingNoiseModel(devices.NoiseModel): also contain gates. """ - def __init__(self, depol_prob: float): + def __init__(self, depol_prob: float, prepend: bool = False): """A depolarizing noise model Args: depol_prob: Depolarizing probability. + prepend: If True, put noise before affected gates. Default: False. """ value.validate_probability(depol_prob, 'depol prob') self.qubit_noise_gate = ops.DepolarizingChannel(depol_prob) + self._prepend = prepend def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']): if validate_all_measurements(moment) or self.is_virtual_moment(moment): # coverage: ignore return moment - return [ + output = [ moment, circuits.Moment( self.qubit_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits ), ] + return output[::-1] if self._prepend else output class ReadoutNoiseModel(devices.NoiseModel): @@ -63,25 +66,28 @@ class ReadoutNoiseModel(devices.NoiseModel): also contain gates. """ - def __init__(self, bitflip_prob: float): + def __init__(self, bitflip_prob: float, prepend: bool = True): """A noise model with readout error. Args: bitflip_prob: Probability of a bit-flip during measurement. + prepend: If True, put noise before affected gates. Default: True. """ value.validate_probability(bitflip_prob, 'bitflip prob') self.readout_noise_gate = ops.BitFlipChannel(bitflip_prob) + self._prepend = prepend def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']): if self.is_virtual_moment(moment): return moment if validate_all_measurements(moment): - return [ + output = [ circuits.Moment( self.readout_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits ), moment, ] + return output if self._prepend else output[::-1] return moment @@ -97,25 +103,28 @@ class DampedReadoutNoiseModel(devices.NoiseModel): also contain gates. """ - def __init__(self, decay_prob: float): + def __init__(self, decay_prob: float, prepend: bool = True): """A depolarizing noise model with damped readout error. Args: decay_prob: Probability of T1 decay during measurement. + prepend: If True, put noise before affected gates. Default: True. """ value.validate_probability(decay_prob, 'decay_prob') self.readout_decay_gate = ops.AmplitudeDampingChannel(decay_prob) + self._prepend = prepend def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']): if self.is_virtual_moment(moment): return moment if validate_all_measurements(moment): - return [ + output = [ circuits.Moment( self.readout_decay_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits ), moment, ] + return output if self._prepend else output[::-1] return moment diff --git a/cirq-core/cirq/contrib/noise_models/noise_models_test.py b/cirq-core/cirq/contrib/noise_models/noise_models_test.py index df9e6fbf724..3c21ef951e8 100644 --- a/cirq-core/cirq/contrib/noise_models/noise_models_test.py +++ b/cirq-core/cirq/contrib/noise_models/noise_models_test.py @@ -29,6 +29,17 @@ def test_depol_noise(): assert isinstance(g.gate, cirq.DepolarizingChannel) +def test_depol_noise_prepend(): + noise_model = ccn.DepolarizingNoiseModel(depol_prob=0.005, prepend=True) + qubits = cirq.LineQubit.range(2) + moment = cirq.Moment([cirq.X(qubits[0]), cirq.Y(qubits[1])]) + noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits) + assert len(noisy_mom) == 2 + assert noisy_mom[1] == moment + for g in noisy_mom[0]: + assert isinstance(g.gate, cirq.DepolarizingChannel) + + # Composes depolarization noise with readout noise. def test_readout_noise_after_moment(): program = cirq.Circuit() @@ -81,6 +92,17 @@ def test_readout_noise_after_moment(): assert_equivalent_op_tree(true_noisy_program, noisy_circuit) +def test_readout_noise_no_prepend(): + noise_model = ccn.ReadoutNoiseModel(bitflip_prob=0.005, prepend=False) + qubits = cirq.LineQubit.range(2) + moment = cirq.Moment([cirq.measure(*qubits, key="meas")]) + noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits) + assert len(noisy_mom) == 2 + assert noisy_mom[0] == moment + for g in noisy_mom[1]: + assert isinstance(g.gate, cirq.BitFlipChannel) + + # Composes depolarization, damping, and readout noise (in that order). def test_decay_noise_after_moment(): program = cirq.Circuit() @@ -138,6 +160,17 @@ def test_decay_noise_after_moment(): assert_equivalent_op_tree(true_noisy_program, noisy_circuit) +def test_damped_readout_noise_no_prepend(): + noise_model = ccn.DampedReadoutNoiseModel(decay_prob=0.005, prepend=False) + qubits = cirq.LineQubit.range(2) + moment = cirq.Moment([cirq.measure(*qubits, key="meas")]) + noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits) + assert len(noisy_mom) == 2 + assert noisy_mom[0] == moment + for g in noisy_mom[1]: + assert isinstance(g.gate, cirq.AmplitudeDampingChannel) + + # Test the aggregate noise models. def test_aggregate_readout_noise_after_moment(): program = cirq.Circuit() diff --git a/cirq-core/cirq/devices/insertion_noise_model.py b/cirq-core/cirq/devices/insertion_noise_model.py index 33553216de5..06682fd0ba7 100644 --- a/cirq-core/cirq/devices/insertion_noise_model.py +++ b/cirq-core/cirq/devices/insertion_noise_model.py @@ -37,7 +37,7 @@ class InsertionNoiseModel(devices.NoiseModel): type is more specific (e.g. A is a subtype of B, but B defines qubits and A does not) then the first one appering in this dict will match. - prepend: whether to add the new moment before the current one. + prepend: If True, put noise before affected gates. Default: False. require_physical_tag: whether to only apply noise to operations tagged with PHYSICAL_GATE_TAG. """ diff --git a/cirq-core/cirq/devices/noise_model.py b/cirq-core/cirq/devices/noise_model.py index 8300cde7003..729ed3eeb58 100644 --- a/cirq-core/cirq/devices/noise_model.py +++ b/cirq-core/cirq/devices/noise_model.py @@ -212,10 +212,17 @@ class ConstantQubitNoiseModel(NoiseModel): operation is given as "the noise to use" for a `NOISE_MODEL_LIKE` parameter. """ - def __init__(self, qubit_noise_gate: 'cirq.Gate'): + def __init__(self, qubit_noise_gate: 'cirq.Gate', prepend: bool = False): + """Noise model which applies a specific gate as noise to all gates. + + Args: + qubit_noise_gate: The "noise" gate to use. + prepend: If True, put noise before affected gates. Default: False. + """ if qubit_noise_gate.num_qubits() != 1: raise ValueError('noise.num_qubits() != 1') self.qubit_noise_gate = qubit_noise_gate + self._prepend = prepend def _value_equality_values_(self) -> Any: return self.qubit_noise_gate @@ -227,12 +234,13 @@ def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid' # Noise should not be appended to previously-added noise. if self.is_virtual_moment(moment): return moment - return [ + output = [ moment, moment_module.Moment( [self.qubit_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits] ), ] + return output[::-1] if self._prepend else output def _json_dict_(self): return protocols.obj_to_dict_helper(self, ['qubit_noise_gate']) @@ -246,6 +254,11 @@ def _has_mixture_(self): class GateSubstitutionNoiseModel(NoiseModel): def __init__(self, substitution_func: Callable[['cirq.Operation'], 'cirq.Operation']): + """Noise model which replaces operations using a substitution function. + + Args: + substitution_func: a function for replacing operations. + """ self.substitution_func = substitution_func def noisy_moment( diff --git a/cirq-core/cirq/devices/noise_model_test.py b/cirq-core/cirq/devices/noise_model_test.py index 0e66579f981..46e94dd81c6 100644 --- a/cirq-core/cirq/devices/noise_model_test.py +++ b/cirq-core/cirq/devices/noise_model_test.py @@ -124,6 +124,25 @@ def test_constant_qubit_noise(): _ = cirq.ConstantQubitNoiseModel(cirq.CNOT**0.01) +def test_constant_qubit_noise_prepend(): + a, b, c = cirq.LineQubit.range(3) + damp = cirq.amplitude_damp(0.5) + damp_all = cirq.ConstantQubitNoiseModel(damp, prepend=True) + actual = damp_all.noisy_moments([cirq.Moment([cirq.X(a)]), cirq.Moment()], [a, b, c]) + expected = [ + [ + cirq.Moment(d.with_tags(ops.VirtualTag()) for d in [damp(a), damp(b), damp(c)]), + cirq.Moment([cirq.X(a)]), + ], + [ + cirq.Moment(d.with_tags(ops.VirtualTag()) for d in [damp(a), damp(b), damp(c)]), + cirq.Moment(), + ], + ] + assert actual == expected + cirq.testing.assert_equivalent_repr(damp_all) + + def test_noise_composition(): # Verify that noise models can be composed without regard to ordering, as # long as the noise operators commute with one another. diff --git a/cirq-core/cirq/devices/thermal_noise_model.py b/cirq-core/cirq/devices/thermal_noise_model.py index bf610973965..c3038fa05eb 100644 --- a/cirq-core/cirq/devices/thermal_noise_model.py +++ b/cirq-core/cirq/devices/thermal_noise_model.py @@ -175,6 +175,7 @@ def __init__( dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None, require_physical_tag: bool = True, skip_measurements: bool = True, + prepend: bool = False, ): """Construct a ThermalNoiseModel data object. @@ -203,6 +204,7 @@ def __init__( require_physical_tag: whether to only apply noise to operations tagged with PHYSICAL_GATE_TAG. skip_measurements: whether to skip applying noise to measurements. + prepend: If True, put noise before affected gates. Default: False. Returns: The ThermalNoiseModel with specified parameters. @@ -225,6 +227,7 @@ def __init__( 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 + self._prepend = prepend def noisy_moment( self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'] @@ -277,4 +280,5 @@ def noisy_moment( noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit)) if not noise_ops: return [moment] - return [moment, moment_module.Moment(noise_ops)] + output = [moment, moment_module.Moment(noise_ops)] + return output[::-1] if self._prepend else output diff --git a/cirq-core/cirq/devices/thermal_noise_model_test.py b/cirq-core/cirq/devices/thermal_noise_model_test.py index 855cbd53e2f..293ec035ddc 100644 --- a/cirq-core/cirq/devices/thermal_noise_model_test.py +++ b/cirq-core/cirq/devices/thermal_noise_model_test.py @@ -225,6 +225,36 @@ def test_noisy_moment_one_qubit(): ) +def test_noisy_moment_one_qubit_prepend(): + q0, q1 = cirq.LineQubit.range(2) + model = ThermalNoiseModel( + qubits={q0, q1}, + 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, + prepend=True, + ) + 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]) + # Noise applies to both qubits, even if only one is acted upon. + assert len(noisy_moment[0]) == 2 + noisy_choi = cirq.kraus_to_choi(cirq.kraus(noisy_moment[0].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], + ], + ) + # Prepend puts noise before the original moment. + assert noisy_moment[1] == moment + + def test_noise_from_wait(): # Verify that wait-gate noise is duration-dependent. q0 = cirq.LineQubit(0) From dfcfff0561ee7f3612e4b41933a068a67d046a4c Mon Sep 17 00:00:00 2001 From: Orion Martin <40585662+95-martin-orion@users.noreply.github.com> Date: Thu, 26 May 2022 13:58:39 -0700 Subject: [PATCH 2/2] pylint --- cirq-core/cirq/devices/noise_model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cirq-core/cirq/devices/noise_model.py b/cirq-core/cirq/devices/noise_model.py index 729ed3eeb58..7769032285a 100644 --- a/cirq-core/cirq/devices/noise_model.py +++ b/cirq-core/cirq/devices/noise_model.py @@ -218,6 +218,9 @@ def __init__(self, qubit_noise_gate: 'cirq.Gate', prepend: bool = False): Args: qubit_noise_gate: The "noise" gate to use. prepend: If True, put noise before affected gates. Default: False. + + Raises: + ValueError: if qubit_noise_gate is not a single-qubit gate. """ if qubit_noise_gate.num_qubits() != 1: raise ValueError('noise.num_qubits() != 1')