Skip to content

Commit d6311f5

Browse files
Thermal noise model (quantumlib#4673)
* Remove files for subsequent PRs * Add coverage for OpId * Clarify if-case * snake_case_gamma * Add insertion noise model. * Update to use new match methods * Apply review cleanup * Introduce Thermal noise model. * Align with checks * remove ThermalChannel ref * Answer review comments * Document mutli-type behavior. * qubit_dims -> qubits * Extract methods, clean docstrings * Cache kraus_ops_from_rates * Document edge behaviors. * Handle other qubits nicely Co-authored-by: Cirq Bot <[email protected]>
1 parent bc2cdfb commit d6311f5

7 files changed

+612
-48
lines changed

cirq/devices/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,17 @@
5252
InsertionNoiseModel,
5353
)
5454

55+
from cirq.devices.thermal_noise_model import (
56+
ThermalNoiseModel,
57+
)
58+
5559
from cirq.devices.noise_utils import (
5660
OpIdentifier,
5761
decay_constant_to_xeb_fidelity,
5862
decay_constant_to_pauli_error,
5963
pauli_error_to_decay_constant,
6064
xeb_fidelity_to_decay_constant,
6165
pauli_error_from_t1,
62-
pauli_error_from_depolarization,
6366
average_error,
6467
decoherence_pauli_error,
6568
)

cirq/devices/insertion_noise_model.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import dataclasses
1616
from typing import TYPE_CHECKING, Dict, List, Optional, Sequence
1717

18-
from cirq import devices, ops
18+
from cirq import devices
1919
from cirq.devices import noise_utils
2020

2121
if TYPE_CHECKING:
@@ -32,7 +32,11 @@ class InsertionNoiseModel(devices.NoiseModel):
3232
3333
Args:
3434
ops_added: a map of gate types (and optionally, qubits they act on) to
35-
operations that should be added.
35+
operations that should be added. If two gate types provided apply
36+
to a target gate, the most specific type will match; if neither
37+
type is more specific (e.g. A is a subtype of B, but B defines
38+
qubits and A does not) then the first one appering in this dict
39+
will match.
3640
prepend: whether to add the new moment before the current one.
3741
require_physical_tag: whether to only apply noise to operations tagged
3842
with PHYSICAL_GATE_TAG.
@@ -63,6 +67,10 @@ def noisy_moment(
6367
noise_ops.append(self.ops_added[match_id])
6468
if not noise_ops:
6569
return [moment]
70+
71+
from cirq import circuits
72+
73+
noise_steps = circuits.Circuit(noise_ops)
6674
if self.prepend:
67-
return [ops.Moment(noise_ops), moment]
68-
return [moment, ops.Moment(noise_ops)]
75+
return [*noise_steps.moments, moment]
76+
return [moment, *noise_steps.moments]

cirq/devices/insertion_noise_model_test.py

+14
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,20 @@ def test_insertion_noise():
5151
assert model.noisy_moment(moment_3, system_qubits=[q0, q1]) == [moment_3]
5252

5353

54+
def test_colliding_noise_qubits():
55+
# Check that noise affecting other qubits doesn't cause issues.
56+
q0, q1, q2, q3 = cirq.LineQubit.range(4)
57+
op_id0 = OpIdentifier(cirq.CZPowGate)
58+
model = InsertionNoiseModel({op_id0: cirq.CNOT(q1, q2)}, require_physical_tag=False)
59+
60+
moment_0 = cirq.Moment(cirq.CZ(q0, q1), cirq.CZ(q2, q3))
61+
assert model.noisy_moment(moment_0, system_qubits=[q0, q1, q2, q3]) == [
62+
moment_0,
63+
cirq.Moment(cirq.CNOT(q1, q2)),
64+
cirq.Moment(cirq.CNOT(q1, q2)),
65+
]
66+
67+
5468
def test_prepend():
5569
q0, q1 = cirq.LineQubit.range(2)
5670
op_id0 = OpIdentifier(cirq.XPowGate, q0)

cirq/devices/noise_utils.py

+1-28
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
# limitations under the License.
1414

1515
from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union
16-
import warnings
1716
import numpy as np
1817

1918
from cirq import ops, protocols, value
@@ -47,9 +46,6 @@ def qubits(self) -> Tuple['cirq.Qid', ...]:
4746
def _predicate(self, *args, **kwargs):
4847
return self._gate_family._predicate(*args, **kwargs)
4948

50-
def swapped(self):
51-
return OpIdentifier(self.gate_type, *self.qubits[::-1])
52-
5349
def is_proper_subtype_of(self, op_id: 'OpIdentifier'):
5450
"""Returns true if this is contained within op_id, but not equal to it.
5551
@@ -175,29 +171,6 @@ def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float:
175171
return (1 - np.exp(-t_ns / t2)) / 2 + (1 - np.exp(-t_ns / t1_ns)) / 4
176172

177173

178-
def pauli_error_from_depolarization(t_ns: float, t1_ns: float, pauli_error: float = 0) -> float:
179-
"""Calculates the amount of pauli error from depolarization.
180-
181-
This computes non-T1 error for a specific duration, `t`. If pauli error
182-
from T1 decay is more than total pauli error, this returns zero; otherwise,
183-
it returns the portion of pauli error not attributable to T1 error.
184-
185-
Args:
186-
t_ns: The duration of the gate in ns.
187-
t1_ns: The T1 decay constant in ns.
188-
pauli_error: The total pauli error.
189-
190-
Returns:
191-
Calculated Pauli error resulting from depolarization.
192-
"""
193-
t1_pauli_error = pauli_error_from_t1(t_ns, t1_ns)
194-
if pauli_error >= t1_pauli_error:
195-
return pauli_error - t1_pauli_error
196-
197-
warnings.warn("Pauli error from T1 decay is greater than total Pauli error", RuntimeWarning)
198-
return 0
199-
200-
201174
def average_error(decay_constant: float, num_qubits: int = 1) -> float:
202175
"""Calculates the average error from the depolarization decay constant.
203176
@@ -213,7 +186,7 @@ def average_error(decay_constant: float, num_qubits: int = 1) -> float:
213186

214187

215188
def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -> float:
216-
"""The component of Pauli error caused by decoherence.
189+
"""The component of Pauli error caused by decoherence on a single qubit.
217190
218191
Args:
219192
t1_ns: T1 time in nanoseconds.

cirq/devices/noise_utils_test.py

+1-15
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
pauli_error_to_decay_constant,
2424
xeb_fidelity_to_decay_constant,
2525
pauli_error_from_t1,
26-
pauli_error_from_depolarization,
2726
average_error,
2827
decoherence_pauli_error,
2928
)
@@ -58,7 +57,7 @@ def test_op_id_str():
5857
def test_op_id_swap():
5958
q0, q1 = cirq.LineQubit.range(2)
6059
base_id = OpIdentifier(cirq.CZPowGate, q0, q1)
61-
swap_id = base_id.swapped()
60+
swap_id = OpIdentifier(base_id.gate_type, *base_id.qubits[::-1])
6261
assert cirq.CZ(q0, q1) in base_id
6362
assert cirq.CZ(q0, q1) not in swap_id
6463
assert cirq.CZ(q1, q0) not in base_id
@@ -125,19 +124,6 @@ def test_pauli_error_from_t1(t, t1_ns, expected_output):
125124
assert val == expected_output
126125

127126

128-
@pytest.mark.parametrize(
129-
't,t1_ns,pauli_error,expected_output',
130-
[
131-
(20, 1e5, 0.01, 0.01 - ((1 - np.exp(-20 / 2e5)) / 2 + (1 - np.exp(-20 / 1e5)) / 4)),
132-
# In this case, the formula produces a negative result.
133-
(4000, 1e4, 0.01, 0),
134-
],
135-
)
136-
def test_pauli_error_from_depolarization(t, t1_ns, pauli_error, expected_output):
137-
val = pauli_error_from_depolarization(t, t1_ns, pauli_error)
138-
assert val == expected_output
139-
140-
141127
@pytest.mark.parametrize(
142128
'decay_constant,num_qubits,expected_output',
143129
[

0 commit comments

Comments
 (0)