Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions cirq-core/cirq/contrib/noise_models/noise_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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


Expand All @@ -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


Expand Down
33 changes: 33 additions & 0 deletions cirq-core/cirq/contrib/noise_models/noise_models_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion cirq-core/cirq/devices/insertion_noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
20 changes: 18 additions & 2 deletions cirq-core/cirq/devices/noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,20 @@ 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.

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')
self.qubit_noise_gate = qubit_noise_gate
self._prepend = prepend

def _value_equality_values_(self) -> Any:
return self.qubit_noise_gate
Expand All @@ -227,12 +237,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'])
Expand All @@ -246,6 +257,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(
Expand Down
19 changes: 19 additions & 0 deletions cirq-core/cirq/devices/noise_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion cirq-core/cirq/devices/thermal_noise_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand All @@ -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']
Expand Down Expand Up @@ -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
30 changes: 30 additions & 0 deletions cirq-core/cirq/devices/thermal_noise_model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down