Skip to content

Commit 4db45dc

Browse files
95-martin-orionCirqBot
authored andcommitted
Add SuperconductingQubitsNoiseProperties (quantumlib#4964)
* Add SuperconductingQubitsNoiseProperties. * Address review comments. * Recover from breakages * linter wrangling * use cached_property * nits nits nits Co-authored-by: Cirq Bot <craiggidney+github+cirqbot@google.com>
1 parent f4d1a50 commit 4db45dc

File tree

4 files changed

+563
-0
lines changed

4 files changed

+563
-0
lines changed

cirq-core/cirq/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
NoiseModelFromNoiseProperties,
9797
NoiseProperties,
9898
OpIdentifier,
99+
SuperconductingQubitsNoiseProperties,
99100
SymmetricalQidPair,
100101
UNCONSTRAINED_DEVICE,
101102
NamedTopology,

cirq-core/cirq/devices/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@
6767
NoiseProperties,
6868
)
6969

70+
from cirq.devices.superconducting_qubits_noise_properties import (
71+
SuperconductingQubitsNoiseProperties,
72+
)
73+
7074
from cirq.devices.noise_utils import (
7175
OpIdentifier,
7276
decay_constant_to_xeb_fidelity,
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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+
16+
"""Class for representing noise on a superconducting qubit device."""
17+
18+
import abc
19+
from dataclasses import dataclass, field
20+
from typing import Dict, TYPE_CHECKING, List, Set, Type
21+
22+
from cirq import _compat, ops, devices
23+
from cirq.devices import noise_utils
24+
25+
if TYPE_CHECKING:
26+
import cirq
27+
28+
29+
# TODO: missing per-device defaults
30+
# Type-ignored because mypy cannot handle abstract dataclasses:
31+
# https://github.com/python/mypy/issues/5374
32+
@dataclass # type: ignore
33+
class SuperconductingQubitsNoiseProperties(devices.NoiseProperties, abc.ABC):
34+
"""Noise-defining properties for a superconducting-qubit-based device.
35+
36+
Args:
37+
gate_times_ns: Dict[type, float] of gate types to their duration on
38+
quantum hardware.
39+
t1_ns: Dict[cirq.Qid, float] of qubits to their T_1 time, in ns.
40+
tphi_ns: Dict[cirq.Qid, float] of qubits to their T_phi time, in ns.
41+
readout_errors: Dict[cirq.Qid, np.ndarray] of qubits to their readout
42+
errors in matrix form: [P(read |1> from |0>), P(read |0> from |1>)].
43+
gate_pauli_errors: dict of noise_utils.OpIdentifiers (a gate and the qubits it
44+
targets) to the Pauli error for that operation. Keys in this dict
45+
must have defined qubits.
46+
validate: If True, verifies that t1 and tphi qubits sets match, and
47+
that all symmetric two-qubit gates have errors which are
48+
symmetric over the qubits they affect. Defaults to True.
49+
"""
50+
51+
gate_times_ns: Dict[type, float]
52+
t1_ns: Dict['cirq.Qid', float]
53+
tphi_ns: Dict['cirq.Qid', float]
54+
readout_errors: Dict['cirq.Qid', List[float]]
55+
gate_pauli_errors: Dict[noise_utils.OpIdentifier, float]
56+
57+
validate: bool = True
58+
_qubits: List['cirq.Qid'] = field(init=False, default_factory=list)
59+
60+
def __post_init__(self):
61+
if not self.validate:
62+
return
63+
t1_qubits = set(self.t1_ns)
64+
tphi_qubits = set(self.tphi_ns)
65+
66+
if t1_qubits != tphi_qubits:
67+
raise ValueError('Keys specified for T1 and Tphi are not identical.')
68+
69+
# validate two qubit gate errors.
70+
self._validate_symmetric_errors('gate_pauli_errors')
71+
72+
def _validate_symmetric_errors(self, field_name: str) -> None:
73+
gate_error_dict = getattr(self, field_name)
74+
for op_id in gate_error_dict:
75+
if len(op_id.qubits) != 2:
76+
# single qubit op_ids also present, or generic values are
77+
# specified. Skip these cases
78+
if len(op_id.qubits) > 2:
79+
raise ValueError(
80+
f'Found gate {op_id.gate_type} with {len(op_id.qubits)} qubits. '
81+
'Symmetric errors can only apply to 2-qubit gates.'
82+
)
83+
elif op_id.gate_type in self.symmetric_two_qubit_gates():
84+
op_id_swapped = noise_utils.OpIdentifier(op_id.gate_type, *op_id.qubits[::-1])
85+
if op_id_swapped not in gate_error_dict:
86+
raise ValueError(
87+
f'Operation {op_id} of field {field_name} has errors '
88+
f'but its symmetric id {op_id_swapped} does not.'
89+
)
90+
elif op_id.gate_type not in self.asymmetric_two_qubit_gates():
91+
# Asymmetric gates do not require validation.
92+
raise ValueError(
93+
f'Found gate {op_id.gate_type} which does not appear in the '
94+
'symmetric or asymmetric gate sets.'
95+
)
96+
97+
@property
98+
def qubits(self) -> List['cirq.Qid']:
99+
"""Qubits for which we have data"""
100+
if not self._qubits:
101+
self._qubits = sorted(self.t1_ns)
102+
return self._qubits
103+
104+
@classmethod
105+
@abc.abstractmethod
106+
def single_qubit_gates(cls) -> Set[Type[ops.Gate]]:
107+
"""Returns the set of single-qubit gates this class supports."""
108+
109+
@classmethod
110+
@abc.abstractmethod
111+
def symmetric_two_qubit_gates(cls) -> Set[Type[ops.Gate]]:
112+
"""Returns the set of symmetric two-qubit gates this class supports."""
113+
114+
@classmethod
115+
@abc.abstractmethod
116+
def asymmetric_two_qubit_gates(cls) -> Set[Type[ops.Gate]]:
117+
"""Returns the set of asymmetric two-qubit gates this class supports."""
118+
119+
@classmethod
120+
def two_qubit_gates(cls) -> Set[Type[ops.Gate]]:
121+
"""Returns the set of all two-qubit gates this class supports."""
122+
return cls.symmetric_two_qubit_gates() | cls.asymmetric_two_qubit_gates()
123+
124+
@classmethod
125+
def expected_gates(cls) -> Set[Type[ops.Gate]]:
126+
"""Returns the set of all gates this class supports."""
127+
return cls.single_qubit_gates() | cls.two_qubit_gates()
128+
129+
def _get_pauli_error(self, p_error: float, op_id: noise_utils.OpIdentifier):
130+
time_ns = float(self.gate_times_ns[op_id.gate_type])
131+
for q in op_id.qubits:
132+
p_error -= noise_utils.decoherence_pauli_error(self.t1_ns[q], self.tphi_ns[q], time_ns)
133+
return p_error
134+
135+
@_compat.cached_property
136+
def _depolarizing_error(self) -> Dict[noise_utils.OpIdentifier, float]:
137+
"""Returns the portion of Pauli error from depolarization."""
138+
depol_errors = {}
139+
for op_id, p_error in self.gate_pauli_errors.items():
140+
gate_type = op_id.gate_type
141+
if issubclass(gate_type, ops.MeasurementGate):
142+
# Non-measurement error can be ignored on measurement gates.
143+
continue
144+
expected_qubits = 1 if gate_type in self.single_qubit_gates() else 2
145+
if len(op_id.qubits) != expected_qubits:
146+
raise ValueError(
147+
f'Gate {gate_type} takes {expected_qubits} qubit(s), '
148+
f'but {op_id.qubits} were given.'
149+
)
150+
depol_errors[op_id] = self._get_pauli_error(p_error, op_id)
151+
return depol_errors
152+
153+
def build_noise_models(self) -> List['cirq.NoiseModel']:
154+
noise_models: List['cirq.NoiseModel'] = []
155+
156+
if self.t1_ns:
157+
noise_models.append(
158+
devices.ThermalNoiseModel(
159+
set(self.t1_ns.keys()),
160+
self.gate_times_ns,
161+
cool_rate_GHz={q: 1 / T1 for q, T1 in self.t1_ns.items()},
162+
dephase_rate_GHz={q: 1 / Tp for q, Tp in self.tphi_ns.items()},
163+
)
164+
)
165+
166+
depolarizing_error = self._depolarizing_error
167+
added_pauli_errors = {
168+
op_id: ops.depolarize(p_error, len(op_id.qubits)).on(*op_id.qubits)
169+
for op_id, p_error in depolarizing_error.items()
170+
if p_error > 0
171+
}
172+
173+
# This adds per-qubit pauli error after ops on those qubits.
174+
noise_models.append(devices.InsertionNoiseModel(ops_added=added_pauli_errors))
175+
176+
# This adds per-qubit measurement error BEFORE measurement on those qubits.
177+
if self.readout_errors:
178+
added_measure_errors: Dict[noise_utils.OpIdentifier, 'cirq.Operation'] = {}
179+
for qubit in self.readout_errors:
180+
p_00, p_11 = self.readout_errors[qubit]
181+
p = p_11 / (p_00 + p_11)
182+
gamma = p_11 / p
183+
added_measure_errors[
184+
noise_utils.OpIdentifier(ops.MeasurementGate, qubit)
185+
] = ops.generalized_amplitude_damp(p, gamma).on(qubit)
186+
187+
noise_models.append(
188+
devices.InsertionNoiseModel(ops_added=added_measure_errors, prepend=True)
189+
)
190+
191+
return noise_models

0 commit comments

Comments
 (0)