-
Notifications
You must be signed in to change notification settings - Fork 121
/
noise_model.py
397 lines (333 loc) · 15.6 KB
/
noise_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from __future__ import annotations
from collections import defaultdict
from dataclasses import dataclass
from typing import Optional
from braket.circuits.circuit import Circuit
from braket.circuits.gate import Gate
from braket.circuits.instruction import Instruction
from braket.circuits.noise import Noise
from braket.circuits.noise_model.circuit_instruction_criteria import CircuitInstructionCriteria
from braket.circuits.noise_model.criteria import Criteria, CriteriaKey, CriteriaKeyResult
from braket.circuits.noise_model.initialization_criteria import InitializationCriteria
from braket.circuits.noise_model.result_type_criteria import ResultTypeCriteria
from braket.circuits.result_types import ObservableResultType
from braket.registers.qubit_set import QubitSetInput
@dataclass
class NoiseModelInstruction:
"""Represents a single instruction for a Noise Model."""
noise: Noise
criteria: Criteria
def __init__(self, noise: Noise, criteria: Criteria):
if not isinstance(noise, Noise):
raise ValueError(f"{noise} must be a Noise type.")
if not isinstance(criteria, Criteria):
raise ValueError(f"{criteria} must be a Criteria type.")
self.noise = noise
self.criteria = criteria
def __repr__(self):
return f"NoiseModelInstruction(noise={self.noise}, criteria={self.criteria})"
def __str__(self):
return f"{self.noise}, {self.criteria}"
def to_dict(self) -> dict:
"""Converts this object to a dictionary."""
return {"noise": self.noise.to_dict(), "criteria": self.criteria.to_dict()}
@classmethod
def from_dict(cls, noise_model_item: dict) -> NoiseModelInstruction:
"""Converts a dictionary representing an object of this class into an instance of
this class.
Args:
noise_model_item (dict): A dictionary representation of an object of this class.
Returns:
NoiseModelInstruction: An object of this class that corresponds
to the passed in dictionary.
"""
return NoiseModelInstruction(
noise=Noise.from_dict(noise_model_item["noise"]),
criteria=Criteria.from_dict(noise_model_item["criteria"]),
)
@dataclass
class NoiseModelInstructions:
"""Represents the instructions in a noise model, separated by type."""
initialization_noise: list[NoiseModelInstruction]
gate_noise: list[NoiseModelInstruction]
readout_noise: list[NoiseModelInstruction]
class NoiseModel:
"""A Noise Model can be thought of as a set of Noise objects, and a corresponding set of
criteria for how each Noise object should be applied to a circuit. For example, a noise model
may represent that every H gate that acts on qubit 0 has a 10% probability of a bit flip, and
every X gate that acts on qubit 0 has a 20% probability of a bit flip, and 5% probability of
a phase flip.
"""
def __init__(self, instructions: list[NoiseModelInstruction] = None):
self._instructions = instructions or []
def __repr__(self):
return str({"instructions": list(self._instructions)})
def __str__(self):
instructions = self.get_instructions_by_type()
all_strings = []
all_strings.extend(
NoiseModel._items_to_string("Initialization Noise:", instructions.initialization_noise)
)
all_strings.extend(NoiseModel._items_to_string("Gate Noise:", instructions.gate_noise))
all_strings.extend(
NoiseModel._items_to_string("Readout Noise:", instructions.readout_noise)
)
return "\n".join(all_strings)
@property
def instructions(self) -> list[NoiseModelInstruction]:
"""List all the noise in the NoiseModel.
Returns:
list[NoiseModelInstruction]: The noise model instructions.
"""
return self._instructions
def add_noise(self, noise: Noise, criteria: Criteria) -> NoiseModel:
"""Modifies the NoiseModel to add noise with a given Criteria.
Args:
noise (Noise): The noise to add.
criteria (Criteria): The criteria that determines when the noise will be applied.
Returns:
NoiseModel: This NoiseModel object.
"""
return self._add_instruction(NoiseModelInstruction(noise, criteria))
def insert_noise(self, index: int, noise: Noise, criteria: Criteria) -> NoiseModel:
"""Modifies the NoiseModel to insert a noise with a given Criteria at particular position.
Args:
index (int): The index at which to insert.
noise (Noise): The noise to insert.
criteria (Criteria): The criteria that determines when the noise will be applied.
Returns:
NoiseModel: This NoiseModel object.
"""
self._instructions.insert(index, NoiseModelInstruction(noise, criteria))
return self
def _add_instruction(self, instruction: NoiseModelInstruction) -> NoiseModel:
"""Modifies the NoiseModel to add noise with a given Criteria.
Args:
instruction (NoiseModelInstruction): The noise model instruction to add.
Returns:
NoiseModel: This NoiseModel object.
"""
self._instructions.append(instruction)
return self
def remove_noise(self, index: int) -> NoiseModel:
"""Removes the noise and corresponding criteria from the NoiseModel at the given index.
Args:
index (int): The index of the instruction to remove.
Returns:
NoiseModel: This NoiseModel object.
Throws:
IndexError: If the provided index is not less than the number of noise (as returned
from items()).
"""
self._instructions.pop(index)
return self
def get_instructions_by_type(self) -> NoiseModelInstructions:
"""Returns the noise in this model by noise type.
Returns:
NoiseModelInstructions: The noise model instructions.
"""
init_noise = []
gate_noise = []
readout_noise = []
for item in self._instructions:
if isinstance(item.criteria, InitializationCriteria):
init_noise.append(item)
elif isinstance(item.criteria, CircuitInstructionCriteria):
gate_noise.append(item)
elif isinstance(item.criteria, ResultTypeCriteria):
readout_noise.append(item)
return NoiseModelInstructions(
initialization_noise=init_noise,
gate_noise=gate_noise,
readout_noise=readout_noise,
)
def from_filter(
self,
qubit: Optional[QubitSetInput] = None,
gate: Optional[Gate] = None,
noise: Optional[type[Noise]] = None,
) -> NoiseModel:
"""Returns a new NoiseModel from this NoiseModel using a given filter. If no filters are
specified, the returned NoiseModel will be the same as this one.
Args:
qubit (Optional[QubitSetInput]): The qubit to filter. Default is None.
If not None, the returned NoiseModel will only have Noise that might be applicable
to the passed qubit (or qubit list, if the criteria acts on a multi-qubit Gate).
gate (Optional[Gate]): The gate to filter. Default is None. If not None,
the returned NoiseModel will only have Noise that might be applicable
to the passed Gate.
noise (Optional[type[Noise]]): The noise class to filter. Default is None.
If not None, the returned NoiseModel will only have noise that is of the same
class as the given noise class.
Returns:
NoiseModel: A noise model containing Noise and Criteria that are applicable for
the given filter.
"""
new_model = NoiseModel()
for item in self._instructions:
if noise is not None and not isinstance(item.noise, noise):
continue
if gate is not None:
gate_keys = item.criteria.get_keys(CriteriaKey.GATE)
if gate_keys != CriteriaKeyResult.ALL and gate not in gate_keys:
continue
if qubit is not None:
qubit_keys = item.criteria.get_keys(CriteriaKey.QUBIT)
if qubit_keys != CriteriaKeyResult.ALL and qubit not in qubit_keys:
continue
new_model.add_noise(item.noise, item.criteria)
return new_model
def apply(self, circuit: Circuit) -> Circuit:
"""Applies this noise model to a circuit, and returns a new circuit that's the `noisy`
version of the given circuit. If multiple noise will act on the same instruction,
they will be applied in the order they are added to the noise model.
Args:
circuit (Circuit): a circuit to apply `noise` to.
Returns:
Circuit: A new circuit that's a `noisy` version of the passed in circuit.
"""
instructions = self.get_instructions_by_type()
new_circuit = NoiseModel._apply_gate_noise(circuit, instructions.gate_noise)
new_circuit = NoiseModel._apply_init_noise(new_circuit, instructions.initialization_noise)
return NoiseModel._apply_readout_noise(new_circuit, instructions.readout_noise)
def to_dict(self) -> dict:
"""Converts this object to a dictionary."""
return {"instructions": [item.to_dict() for item in self._instructions]}
@classmethod
def _apply_gate_noise(
cls,
circuit: Circuit,
gate_noise_instructions: list[NoiseModelInstruction],
) -> Circuit:
"""Applies the gate noise to return a new circuit that's the `noisy` version of the given
circuit.
Args:
circuit (Circuit): a circuit to apply `noise` to.
gate_noise_instructions (list[NoiseModelInstruction]): a list of gate noise
instructions to apply to the circuit.
Returns:
Circuit: A new circuit that's a `noisy` version of the passed in circuit. The targets
set will be populated with the list of targets in the new circuit.
"""
new_circuit = Circuit()
for circuit_instruction in circuit.instructions:
new_circuit.add_instruction(circuit_instruction)
target_qubits = list(circuit_instruction.target)
for item in gate_noise_instructions:
if item.criteria.instruction_matches(circuit_instruction):
if item.noise.fixed_qubit_count() == len(target_qubits):
new_circuit.add_instruction(Instruction(item.noise, target_qubits))
else:
for qubit in target_qubits:
new_circuit.add_instruction(Instruction(item.noise, qubit))
for result_type in circuit.result_types:
new_circuit.add_result_type(result_type)
return new_circuit
@classmethod
def _apply_init_noise(
cls,
circuit: Circuit,
init_noise_instructions: list[NoiseModelInstruction],
) -> Circuit:
"""Applies the initialization noise of this noise model to a circuit and returns
the circuit.
Args:
circuit (Circuit): A circuit to apply `noise` to.
init_noise_instructions (list[NoiseModelInstruction]): A list of initialization noise
model instructions.
Returns:
Circuit: The passed in circuit, with the initialization noise applied.
"""
if not init_noise_instructions:
return circuit
for item in init_noise_instructions:
qubits = item.criteria.qubit_intersection(circuit.qubits)
if len(qubits) > 0:
circuit.apply_initialization_noise(item.noise, list(qubits))
return circuit
@classmethod
def _apply_readout_noise(
cls,
circuit: Circuit,
readout_noise_instructions: list[NoiseModelInstruction],
) -> Circuit:
"""Applies the readout noise of this noise model to a circuit and returns the circuit.
Args:
circuit (Circuit): A circuit to apply `noise` to.
readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise
to apply.
Returns:
Circuit: The passed in circuit, with the readout noise applied.
"""
if not readout_noise_instructions or not circuit.result_types:
return circuit
return _apply_noise_on_observable_result_types(circuit, readout_noise_instructions)
@classmethod
def _items_to_string(
cls, instructions_title: str, instructions: list[NoiseModelInstruction]
) -> list[str]:
"""Creates a string representation of a list of instructions.
Args:
instructions_title (str): The title for this list of instructions.
instructions (list[NoiseModelInstruction]): A list of instructions.
Returns:
list[str]: A list of string representations of the passed instructions.
"""
results = []
if instructions:
results.append(instructions_title)
results.extend(f" {item}" for item in instructions)
return results
@classmethod
def from_dict(cls, noise_dict: dict) -> NoiseModel:
"""Converts a dictionary representing an object of this class into an instance
of this class.
Args:
noise_dict (dict): A dictionary representation of an object of this class.
Returns:
NoiseModel: An object of this class that corresponds to the passed in dictionary.
"""
model = NoiseModel()
all(
model._add_instruction(NoiseModelInstruction.from_dict(item))
for item in noise_dict["instructions"]
)
return model
def _apply_noise_on_observable_result_types(
circuit: Circuit, readout_noise_instructions: list[NoiseModelInstruction]
) -> Circuit:
"""Applies readout noise based on the observable result types in the circuit. Each applicable
Noise will be applied only once to a target in the ObservableResultType.
Args:
circuit (Circuit): The circuit to apply the readout noise to.
readout_noise_instructions (list[NoiseModelInstruction]): The list of readout noise
to apply.
Returns:
Circuit: The passed in circuit, with the readout noise applied.
"""
result_types = circuit.result_types
noise_to_apply = defaultdict(set)
for result_type in result_types:
if isinstance(result_type, ObservableResultType):
target_qubits = list(result_type.target)
for item_index, item in enumerate(readout_noise_instructions):
if item.criteria.result_type_matches(result_type):
for target_qubit in target_qubits:
noise_to_apply[target_qubit].add(item_index)
for qubit in noise_to_apply:
for noise_item_index in noise_to_apply[qubit]:
item = readout_noise_instructions[noise_item_index]
circuit.apply_readout_noise(item.noise, qubit)
return circuit