Skip to content

Commit ac7994e

Browse files
Support prepending noise (#5410)
Fixes #2570. This is essentially a more friendly version of #5327 which maintains existing behavior but gives users the option to prepend noise (or append, in the case of readout error) on various noise models.
1 parent 90df56a commit ac7994e

File tree

7 files changed

+121
-10
lines changed

7 files changed

+121
-10
lines changed

cirq-core/cirq/contrib/noise_models/noise_models.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,29 @@ class DepolarizingNoiseModel(devices.NoiseModel):
2929
also contain gates.
3030
"""
3131

32-
def __init__(self, depol_prob: float):
32+
def __init__(self, depol_prob: float, prepend: bool = False):
3333
"""A depolarizing noise model
3434
3535
Args:
3636
depol_prob: Depolarizing probability.
37+
prepend: If True, put noise before affected gates. Default: False.
3738
"""
3839
value.validate_probability(depol_prob, 'depol prob')
3940
self.qubit_noise_gate = ops.DepolarizingChannel(depol_prob)
41+
self._prepend = prepend
4042

4143
def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']):
4244
if validate_all_measurements(moment) or self.is_virtual_moment(moment):
4345
# coverage: ignore
4446
return moment
4547

46-
return [
48+
output = [
4749
moment,
4850
circuits.Moment(
4951
self.qubit_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits
5052
),
5153
]
54+
return output[::-1] if self._prepend else output
5255

5356

5457
class ReadoutNoiseModel(devices.NoiseModel):
@@ -63,25 +66,28 @@ class ReadoutNoiseModel(devices.NoiseModel):
6366
also contain gates.
6467
"""
6568

66-
def __init__(self, bitflip_prob: float):
69+
def __init__(self, bitflip_prob: float, prepend: bool = True):
6770
"""A noise model with readout error.
6871
6972
Args:
7073
bitflip_prob: Probability of a bit-flip during measurement.
74+
prepend: If True, put noise before affected gates. Default: True.
7175
"""
7276
value.validate_probability(bitflip_prob, 'bitflip prob')
7377
self.readout_noise_gate = ops.BitFlipChannel(bitflip_prob)
78+
self._prepend = prepend
7479

7580
def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']):
7681
if self.is_virtual_moment(moment):
7782
return moment
7883
if validate_all_measurements(moment):
79-
return [
84+
output = [
8085
circuits.Moment(
8186
self.readout_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits
8287
),
8388
moment,
8489
]
90+
return output if self._prepend else output[::-1]
8591
return moment
8692

8793

@@ -97,25 +103,28 @@ class DampedReadoutNoiseModel(devices.NoiseModel):
97103
also contain gates.
98104
"""
99105

100-
def __init__(self, decay_prob: float):
106+
def __init__(self, decay_prob: float, prepend: bool = True):
101107
"""A depolarizing noise model with damped readout error.
102108
103109
Args:
104110
decay_prob: Probability of T1 decay during measurement.
111+
prepend: If True, put noise before affected gates. Default: True.
105112
"""
106113
value.validate_probability(decay_prob, 'decay_prob')
107114
self.readout_decay_gate = ops.AmplitudeDampingChannel(decay_prob)
115+
self._prepend = prepend
108116

109117
def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']):
110118
if self.is_virtual_moment(moment):
111119
return moment
112120
if validate_all_measurements(moment):
113-
return [
121+
output = [
114122
circuits.Moment(
115123
self.readout_decay_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits
116124
),
117125
moment,
118126
]
127+
return output if self._prepend else output[::-1]
119128
return moment
120129

121130

cirq-core/cirq/contrib/noise_models/noise_models_test.py

+33
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ def test_depol_noise():
2929
assert isinstance(g.gate, cirq.DepolarizingChannel)
3030

3131

32+
def test_depol_noise_prepend():
33+
noise_model = ccn.DepolarizingNoiseModel(depol_prob=0.005, prepend=True)
34+
qubits = cirq.LineQubit.range(2)
35+
moment = cirq.Moment([cirq.X(qubits[0]), cirq.Y(qubits[1])])
36+
noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits)
37+
assert len(noisy_mom) == 2
38+
assert noisy_mom[1] == moment
39+
for g in noisy_mom[0]:
40+
assert isinstance(g.gate, cirq.DepolarizingChannel)
41+
42+
3243
# Composes depolarization noise with readout noise.
3344
def test_readout_noise_after_moment():
3445
program = cirq.Circuit()
@@ -81,6 +92,17 @@ def test_readout_noise_after_moment():
8192
assert_equivalent_op_tree(true_noisy_program, noisy_circuit)
8293

8394

95+
def test_readout_noise_no_prepend():
96+
noise_model = ccn.ReadoutNoiseModel(bitflip_prob=0.005, prepend=False)
97+
qubits = cirq.LineQubit.range(2)
98+
moment = cirq.Moment([cirq.measure(*qubits, key="meas")])
99+
noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits)
100+
assert len(noisy_mom) == 2
101+
assert noisy_mom[0] == moment
102+
for g in noisy_mom[1]:
103+
assert isinstance(g.gate, cirq.BitFlipChannel)
104+
105+
84106
# Composes depolarization, damping, and readout noise (in that order).
85107
def test_decay_noise_after_moment():
86108
program = cirq.Circuit()
@@ -138,6 +160,17 @@ def test_decay_noise_after_moment():
138160
assert_equivalent_op_tree(true_noisy_program, noisy_circuit)
139161

140162

163+
def test_damped_readout_noise_no_prepend():
164+
noise_model = ccn.DampedReadoutNoiseModel(decay_prob=0.005, prepend=False)
165+
qubits = cirq.LineQubit.range(2)
166+
moment = cirq.Moment([cirq.measure(*qubits, key="meas")])
167+
noisy_mom = noise_model.noisy_moment(moment, system_qubits=qubits)
168+
assert len(noisy_mom) == 2
169+
assert noisy_mom[0] == moment
170+
for g in noisy_mom[1]:
171+
assert isinstance(g.gate, cirq.AmplitudeDampingChannel)
172+
173+
141174
# Test the aggregate noise models.
142175
def test_aggregate_readout_noise_after_moment():
143176
program = cirq.Circuit()

cirq-core/cirq/devices/insertion_noise_model.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class InsertionNoiseModel(devices.NoiseModel):
3737
type is more specific (e.g. A is a subtype of B, but B defines
3838
qubits and A does not) then the first one appering in this dict
3939
will match.
40-
prepend: whether to add the new moment before the current one.
40+
prepend: If True, put noise before affected gates. Default: False.
4141
require_physical_tag: whether to only apply noise to operations tagged
4242
with PHYSICAL_GATE_TAG.
4343
"""

cirq-core/cirq/devices/noise_model.py

+18-2
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,20 @@ class ConstantQubitNoiseModel(NoiseModel):
212212
operation is given as "the noise to use" for a `NOISE_MODEL_LIKE` parameter.
213213
"""
214214

215-
def __init__(self, qubit_noise_gate: 'cirq.Gate'):
215+
def __init__(self, qubit_noise_gate: 'cirq.Gate', prepend: bool = False):
216+
"""Noise model which applies a specific gate as noise to all gates.
217+
218+
Args:
219+
qubit_noise_gate: The "noise" gate to use.
220+
prepend: If True, put noise before affected gates. Default: False.
221+
222+
Raises:
223+
ValueError: if qubit_noise_gate is not a single-qubit gate.
224+
"""
216225
if qubit_noise_gate.num_qubits() != 1:
217226
raise ValueError('noise.num_qubits() != 1')
218227
self.qubit_noise_gate = qubit_noise_gate
228+
self._prepend = prepend
219229

220230
def _value_equality_values_(self) -> Any:
221231
return self.qubit_noise_gate
@@ -227,12 +237,13 @@ def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid'
227237
# Noise should not be appended to previously-added noise.
228238
if self.is_virtual_moment(moment):
229239
return moment
230-
return [
240+
output = [
231241
moment,
232242
moment_module.Moment(
233243
[self.qubit_noise_gate(q).with_tags(ops.VirtualTag()) for q in system_qubits]
234244
),
235245
]
246+
return output[::-1] if self._prepend else output
236247

237248
def _json_dict_(self):
238249
return protocols.obj_to_dict_helper(self, ['qubit_noise_gate'])
@@ -246,6 +257,11 @@ def _has_mixture_(self):
246257

247258
class GateSubstitutionNoiseModel(NoiseModel):
248259
def __init__(self, substitution_func: Callable[['cirq.Operation'], 'cirq.Operation']):
260+
"""Noise model which replaces operations using a substitution function.
261+
262+
Args:
263+
substitution_func: a function for replacing operations.
264+
"""
249265
self.substitution_func = substitution_func
250266

251267
def noisy_moment(

cirq-core/cirq/devices/noise_model_test.py

+19
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,25 @@ def test_constant_qubit_noise():
124124
_ = cirq.ConstantQubitNoiseModel(cirq.CNOT**0.01)
125125

126126

127+
def test_constant_qubit_noise_prepend():
128+
a, b, c = cirq.LineQubit.range(3)
129+
damp = cirq.amplitude_damp(0.5)
130+
damp_all = cirq.ConstantQubitNoiseModel(damp, prepend=True)
131+
actual = damp_all.noisy_moments([cirq.Moment([cirq.X(a)]), cirq.Moment()], [a, b, c])
132+
expected = [
133+
[
134+
cirq.Moment(d.with_tags(ops.VirtualTag()) for d in [damp(a), damp(b), damp(c)]),
135+
cirq.Moment([cirq.X(a)]),
136+
],
137+
[
138+
cirq.Moment(d.with_tags(ops.VirtualTag()) for d in [damp(a), damp(b), damp(c)]),
139+
cirq.Moment(),
140+
],
141+
]
142+
assert actual == expected
143+
cirq.testing.assert_equivalent_repr(damp_all)
144+
145+
127146
def test_noise_composition():
128147
# Verify that noise models can be composed without regard to ordering, as
129148
# long as the noise operators commute with one another.

cirq-core/cirq/devices/thermal_noise_model.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ def __init__(
175175
dephase_rate_GHz: Union[float, Dict['cirq.Qid', float], None] = None,
176176
require_physical_tag: bool = True,
177177
skip_measurements: bool = True,
178+
prepend: bool = False,
178179
):
179180
"""Construct a ThermalNoiseModel data object.
180181
@@ -203,6 +204,7 @@ def __init__(
203204
require_physical_tag: whether to only apply noise to operations
204205
tagged with PHYSICAL_GATE_TAG.
205206
skip_measurements: whether to skip applying noise to measurements.
207+
prepend: If True, put noise before affected gates. Default: False.
206208
207209
Returns:
208210
The ThermalNoiseModel with specified parameters.
@@ -225,6 +227,7 @@ def __init__(
225227
self.rate_matrix_GHz: Dict['cirq.Qid', np.ndarray] = rate_dict
226228
self.require_physical_tag: bool = require_physical_tag
227229
self.skip_measurements: bool = skip_measurements
230+
self._prepend = prepend
228231

229232
def noisy_moment(
230233
self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']
@@ -277,4 +280,5 @@ def noisy_moment(
277280
noise_ops.append(ops.KrausChannel(kraus_ops).on(qubit))
278281
if not noise_ops:
279282
return [moment]
280-
return [moment, moment_module.Moment(noise_ops)]
283+
output = [moment, moment_module.Moment(noise_ops)]
284+
return output[::-1] if self._prepend else output

cirq-core/cirq/devices/thermal_noise_model_test.py

+30
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,36 @@ def test_noisy_moment_one_qubit():
225225
)
226226

227227

228+
def test_noisy_moment_one_qubit_prepend():
229+
q0, q1 = cirq.LineQubit.range(2)
230+
model = ThermalNoiseModel(
231+
qubits={q0, q1},
232+
gate_durations_ns={cirq.PhasedXZGate: 25.0, cirq.CZPowGate: 25.0},
233+
heat_rate_GHz={q0: 1e-5, q1: 2e-5},
234+
cool_rate_GHz={q0: 1e-4, q1: 2e-4},
235+
dephase_rate_GHz={q0: 3e-4, q1: 4e-4},
236+
require_physical_tag=False,
237+
prepend=True,
238+
)
239+
gate = cirq.PhasedXZGate(x_exponent=1, z_exponent=0.5, axis_phase_exponent=0.25)
240+
moment = cirq.Moment(gate.on(q0))
241+
noisy_moment = model.noisy_moment(moment, system_qubits=[q0, q1])
242+
# Noise applies to both qubits, even if only one is acted upon.
243+
assert len(noisy_moment[0]) == 2
244+
noisy_choi = cirq.kraus_to_choi(cirq.kraus(noisy_moment[0].operations[0]))
245+
assert np.allclose(
246+
noisy_choi,
247+
[
248+
[9.99750343e-01, 0, 0, 9.91164267e-01],
249+
[0, 2.49656565e-03, 0, 0],
250+
[0, 0, 2.49656565e-04, 0],
251+
[9.91164267e-01, 0, 0, 9.97503434e-01],
252+
],
253+
)
254+
# Prepend puts noise before the original moment.
255+
assert noisy_moment[1] == moment
256+
257+
228258
def test_noise_from_wait():
229259
# Verify that wait-gate noise is duration-dependent.
230260
q0 = cirq.LineQubit(0)

0 commit comments

Comments
 (0)