Skip to content

Commit 4bb3032

Browse files
Utilities for approximate hardware noise (quantumlib#4671)
* Remove files for subsequent PRs * Add coverage for OpId * Clarify if-case * snake_case_gamma Co-authored-by: Cirq Bot <[email protected]>
1 parent 01469ab commit 4bb3032

File tree

7 files changed

+431
-0
lines changed

7 files changed

+431
-0
lines changed

cirq/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
NO_NOISE,
8989
NOISE_MODEL_LIKE,
9090
NoiseModel,
91+
OpIdentifier,
9192
SymmetricalQidPair,
9293
UNCONSTRAINED_DEVICE,
9394
NamedTopology,

cirq/devices/__init__.py

+12
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,15 @@
4747
get_placements,
4848
draw_placements,
4949
)
50+
51+
from cirq.devices.noise_utils import (
52+
OpIdentifier,
53+
decay_constant_to_xeb_fidelity,
54+
decay_constant_to_pauli_error,
55+
pauli_error_to_decay_constant,
56+
xeb_fidelity_to_decay_constant,
57+
pauli_error_from_t1,
58+
pauli_error_from_depolarization,
59+
average_error,
60+
decoherence_pauli_error,
61+
)

cirq/devices/noise_utils.py

+233
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Copyright 2021 The Cirq Developers
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# https://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import TYPE_CHECKING, Any, Dict, Tuple, Type, Union
16+
import warnings
17+
import numpy as np
18+
19+
from cirq import ops, protocols, value
20+
21+
if TYPE_CHECKING:
22+
import cirq
23+
24+
25+
# Tag for gates to which noise must be applied.
26+
PHYSICAL_GATE_TAG = 'physical_gate'
27+
28+
29+
@value.value_equality(distinct_child_types=True)
30+
class OpIdentifier:
31+
"""Identifies an operation by gate and (optionally) target qubits."""
32+
33+
def __init__(self, gate_type: Type['cirq.Gate'], *qubits: 'cirq.Qid'):
34+
self._gate_type = gate_type
35+
self._gate_family = ops.GateFamily(gate_type)
36+
self._qubits: Tuple['cirq.Qid', ...] = tuple(qubits)
37+
38+
@property
39+
def gate_type(self) -> Type['cirq.Gate']:
40+
# set to a type during initialization, never modified
41+
return self._gate_type
42+
43+
@property
44+
def qubits(self) -> Tuple['cirq.Qid', ...]:
45+
return self._qubits
46+
47+
def _predicate(self, *args, **kwargs):
48+
return self._gate_family._predicate(*args, **kwargs)
49+
50+
def swapped(self):
51+
return OpIdentifier(self.gate_type, *self.qubits[::-1])
52+
53+
def is_proper_subtype_of(self, op_id: 'OpIdentifier'):
54+
"""Returns true if this is contained within op_id, but not equal to it.
55+
56+
If this returns true, (x in self) implies (x in op_id), but the reverse
57+
implication does not hold. op_id must be more general than self (either
58+
by accepting any qubits or having a more general gate type) for this
59+
to return true.
60+
"""
61+
more_specific_qubits = self.qubits and not op_id.qubits
62+
more_specific_gate = self.gate_type != op_id.gate_type and issubclass(
63+
self.gate_type, op_id.gate_type
64+
)
65+
if more_specific_qubits:
66+
return more_specific_gate or self.gate_type == op_id.gate_type
67+
elif more_specific_gate:
68+
return more_specific_qubits or self.qubits == op_id.qubits
69+
else:
70+
return False
71+
72+
def __contains__(self, item: Union[ops.Gate, ops.Operation]) -> bool:
73+
if isinstance(item, ops.Gate):
74+
return (not self._qubits) and self._predicate(item)
75+
return (
76+
(not self.qubits or (item.qubits == self._qubits))
77+
and item.gate is not None
78+
and self._predicate(item.gate)
79+
)
80+
81+
def __str__(self):
82+
return f'{self.gate_type}{self.qubits}'
83+
84+
def __repr__(self) -> str:
85+
fullname = f'{self.gate_type.__module__}.{self.gate_type.__qualname__}'
86+
qubits = ', '.join(map(repr, self.qubits))
87+
return f'cirq.devices.noise_utils.OpIdentifier({fullname}, {qubits})'
88+
89+
def _value_equality_values_(self) -> Any:
90+
return (self.gate_type, self.qubits)
91+
92+
def _json_dict_(self) -> Dict[str, Any]:
93+
gate_json = protocols.json_cirq_type(self._gate_type)
94+
return {
95+
'gate_type': gate_json,
96+
'qubits': self._qubits,
97+
}
98+
99+
@classmethod
100+
def _from_json_dict_(cls, gate_type, qubits, **kwargs) -> 'OpIdentifier':
101+
gate_type = protocols.cirq_type_from_json(gate_type)
102+
return cls(gate_type, *qubits)
103+
104+
105+
# TODO: expose all from top-level cirq?
106+
def decay_constant_to_xeb_fidelity(decay_constant: float, num_qubits: int = 2) -> float:
107+
"""Calculates the XEB fidelity from the depolarization decay constant.
108+
109+
Args:
110+
decay_constant: Depolarization decay constant.
111+
num_qubits: Number of qubits.
112+
113+
Returns:
114+
Calculated XEB fidelity.
115+
"""
116+
N = 2 ** num_qubits
117+
return 1 - ((1 - decay_constant) * (1 - 1 / N))
118+
119+
120+
def decay_constant_to_pauli_error(decay_constant: float, num_qubits: int = 1) -> float:
121+
"""Calculates pauli error from the depolarization decay constant.
122+
123+
Args:
124+
decay_constant: Depolarization decay constant.
125+
num_qubits: Number of qubits.
126+
127+
Returns:
128+
Calculated Pauli error.
129+
"""
130+
N = 2 ** num_qubits
131+
return (1 - decay_constant) * (1 - 1 / N / N)
132+
133+
134+
def pauli_error_to_decay_constant(pauli_error: float, num_qubits: int = 1) -> float:
135+
"""Calculates depolarization decay constant from pauli error.
136+
137+
Args:
138+
pauli_error: The pauli error.
139+
num_qubits: Number of qubits.
140+
141+
Returns:
142+
Calculated depolarization decay constant.
143+
"""
144+
N = 2 ** num_qubits
145+
return 1 - (pauli_error / (1 - 1 / N / N))
146+
147+
148+
def xeb_fidelity_to_decay_constant(xeb_fidelity: float, num_qubits: int = 2) -> float:
149+
"""Calculates the depolarization decay constant from XEB fidelity.
150+
151+
Args:
152+
xeb_fidelity: The XEB fidelity.
153+
num_qubits: Number of qubits.
154+
155+
Returns:
156+
Calculated depolarization decay constant.
157+
"""
158+
N = 2 ** num_qubits
159+
return 1 - (1 - xeb_fidelity) / (1 - 1 / N)
160+
161+
162+
def pauli_error_from_t1(t_ns: float, t1_ns: float) -> float:
163+
"""Calculates the pauli error from T1 decay constant.
164+
165+
This computes error for a specific duration, `t`.
166+
167+
Args:
168+
t_ns: The duration of the gate in ns.
169+
t1_ns: The T1 decay constant in ns.
170+
171+
Returns:
172+
Calculated Pauli error resulting from T1 decay.
173+
"""
174+
t2 = 2 * t1_ns
175+
return (1 - np.exp(-t_ns / t2)) / 2 + (1 - np.exp(-t_ns / t1_ns)) / 4
176+
177+
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+
201+
def average_error(decay_constant: float, num_qubits: int = 1) -> float:
202+
"""Calculates the average error from the depolarization decay constant.
203+
204+
Args:
205+
decay_constant: Depolarization decay constant.
206+
num_qubits: Number of qubits.
207+
208+
Returns:
209+
Calculated average error.
210+
"""
211+
N = 2 ** num_qubits
212+
return (1 - decay_constant) * (1 - 1 / N)
213+
214+
215+
def decoherence_pauli_error(t1_ns: float, tphi_ns: float, gate_time_ns: float) -> float:
216+
"""The component of Pauli error caused by decoherence.
217+
218+
Args:
219+
t1_ns: T1 time in nanoseconds.
220+
tphi_ns: Tphi time in nanoseconds.
221+
gate_time_ns: Duration in nanoseconds of the gate affected by this error.
222+
223+
Returns:
224+
Calculated Pauli error resulting from decoherence.
225+
"""
226+
gamma_2 = (1 / (2 * t1_ns)) + 1 / tphi_ns
227+
228+
exp1 = np.exp(-gate_time_ns / t1_ns)
229+
exp2 = np.exp(-gate_time_ns * gamma_2)
230+
px = 0.25 * (1 - exp1)
231+
py = px
232+
pz = 0.5 * (1 - exp2) - px
233+
return px + py + pz

0 commit comments

Comments
 (0)