bloqade-circuit 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bloqade/cirq_utils/__init__.py +2 -0
- bloqade/cirq_utils/noise/__init__.py +11 -0
- bloqade/cirq_utils/noise/_two_zone_utils.py +531 -0
- bloqade/cirq_utils/noise/conflict_graph.py +166 -0
- bloqade/cirq_utils/noise/model.py +544 -0
- bloqade/cirq_utils/noise/transform.py +57 -0
- bloqade/cirq_utils/parallelize.py +4 -2
- bloqade/qasm2/_qasm_loading.py +4 -1
- bloqade/qasm2/parse/lowering.py +11 -3
- bloqade/squin/cirq/lowering.py +53 -5
- bloqade/squin/lowering.py +29 -2
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/METADATA +1 -1
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/RECORD +15 -10
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/licenses/LICENSE +0 -0
bloqade/cirq_utils/__init__.py
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# TODO: check if cirq is installed before importing stuff
|
|
2
|
+
|
|
3
|
+
from .model import (
|
|
4
|
+
GeminiOneZoneNoiseModel as GeminiOneZoneNoiseModel,
|
|
5
|
+
GeminiTwoZoneNoiseModel as GeminiTwoZoneNoiseModel,
|
|
6
|
+
GeminiOneZoneNoiseModelABC as GeminiOneZoneNoiseModelABC,
|
|
7
|
+
GeminiOneZoneNoiseModelCorrelated as GeminiOneZoneNoiseModelCorrelated,
|
|
8
|
+
GeminiOneZoneNoiseModelConflictGraphMoves as GeminiOneZoneNoiseModelConflictGraphMoves,
|
|
9
|
+
)
|
|
10
|
+
from .transform import transform_circuit as transform_circuit
|
|
11
|
+
from .conflict_graph import OneZoneConflictGraph as OneZoneConflictGraph
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from typing import List, Tuple, Optional, Sequence, cast
|
|
3
|
+
from collections import deque
|
|
4
|
+
|
|
5
|
+
import cirq
|
|
6
|
+
import numpy as np
|
|
7
|
+
from cirq.circuits.qasm_output import QasmUGate
|
|
8
|
+
|
|
9
|
+
Slot = Tuple[int, int] # (tuple index, position inside tuple)
|
|
10
|
+
Swap = Tuple[Slot, Slot]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
##We make a distinction between gate and move errors. Gate errors intrinsically depend on the gate type (two qubit gates, local single qubit gates, global single qubit gates)
|
|
14
|
+
##Move errors are independent of the gate type, and depend on the qubit spatial layout. For our purposes, an upper bound for the move error is enough per atom move, for a given layout.
|
|
15
|
+
def get_qargs_from_moment(moment: cirq.Moment):
|
|
16
|
+
"""Returns a list of qubit arguments (qargs) from all operations in a Cirq moment.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
moment: A cirq.Moment object.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
A list of tuples, where each tuple contains the qubits acted on by a gate in the moment.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
list_qubs = [op.qubits for op in moment.operations]
|
|
26
|
+
|
|
27
|
+
return list_qubs
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def flatten_qargs(list_qubs: Sequence[Tuple[cirq.Qid, ...]]) -> List[cirq.Qid]:
|
|
31
|
+
"""Flattens a list of lists of qargs
|
|
32
|
+
Args:
|
|
33
|
+
list_qubs: A list of tuples of cirq.Qid objects.
|
|
34
|
+
Returns:
|
|
35
|
+
A flattened list of cirq.Qid objects.
|
|
36
|
+
"""
|
|
37
|
+
return [item for tup in list_qubs for item in tup]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def qargs_to_qidxs(qargs: List[Tuple[cirq.LineQubit, ...]]) -> List[Tuple[int, ...]]:
|
|
41
|
+
"""
|
|
42
|
+
Transforms list of qargs (tuples of cirq.LineQubit objects) into a list of tuples of integers.
|
|
43
|
+
Each integer corresponds to the index of the qubit in the tuple.
|
|
44
|
+
Args:
|
|
45
|
+
qargs: A list of tuples of cirq.LineQubit objects.
|
|
46
|
+
Returns:
|
|
47
|
+
A list of tuples of integers, where each integer is the index of the qubit in the tuple.
|
|
48
|
+
"""
|
|
49
|
+
return [tuple(x.x for x in tup) for tup in qargs]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_map_named_to_line_qubits(named_qubits: Sequence[cirq.NamedQubit]) -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Maps cirq.NamedQubit('q_i') objects to cirq.LineQubit(i) objects.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
named_qubits: A list of cirq.NamedQubit objects.
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
A dictionary mapping cirq.NamedQubit to cirq.LineQubit.
|
|
61
|
+
"""
|
|
62
|
+
mapping = {}
|
|
63
|
+
for named_qubit in named_qubits:
|
|
64
|
+
# Extract the integer index from the NamedQubit name
|
|
65
|
+
index = int(named_qubit.name.split("_")[1]) # Assumes format 'q_i'
|
|
66
|
+
mapping[named_qubit] = cirq.LineQubit(index)
|
|
67
|
+
return mapping
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def numpy_complement(subset: np.ndarray, full: np.ndarray) -> np.ndarray:
|
|
71
|
+
"""Returns the elements in `full` that are not in `subset`.
|
|
72
|
+
Args:
|
|
73
|
+
subset: A numpy array of elements to exclude.
|
|
74
|
+
full: A numpy array of elements from which to exclude the subset.
|
|
75
|
+
Returns:
|
|
76
|
+
A numpy array containing elements from `full` that are not in `subset`.
|
|
77
|
+
"""
|
|
78
|
+
mask = ~np.isin(full, subset)
|
|
79
|
+
return full[mask]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def intersect_by_structure(
|
|
83
|
+
reference: List[Tuple[int, ...]], target: List[Tuple[int, ...]]
|
|
84
|
+
) -> List[Tuple[int, ...]]:
|
|
85
|
+
target_set = set(val for t in target for val in t)
|
|
86
|
+
result = []
|
|
87
|
+
|
|
88
|
+
for tup in reference:
|
|
89
|
+
filtered = tuple(val for val in tup if val in target_set)
|
|
90
|
+
result.append(filtered)
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def expand(
|
|
96
|
+
data: Sequence[Tuple[Optional[int], ...]], capacity: int = 2
|
|
97
|
+
) -> List[List[Optional[int]]]:
|
|
98
|
+
# Pad each tuple to have exactly `capacity` slots, using None
|
|
99
|
+
return [list(t) + [None] * (capacity - len(t)) for t in data]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def flatten_with_slots(
|
|
103
|
+
data: List[List[Optional[int]]],
|
|
104
|
+
) -> List[Tuple[Optional[int], Slot]]:
|
|
105
|
+
# Create list of (value, (tuple_index, slot_index))
|
|
106
|
+
return [(val, (i, j)) for i, row in enumerate(data) for j, val in enumerate(row)]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def regroup(data: List[List[Optional[int]]]) -> List[Tuple[int, ...]]:
|
|
110
|
+
# Turn 2D structure back into list of sorted tuples, ignoring Nones
|
|
111
|
+
return [tuple(sorted([v for v in row if v is not None])) for row in data]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def canonical_form(tuples: List[Tuple[int, ...]]) -> Tuple[Tuple[int, ...], ...]:
|
|
115
|
+
# Normalize by sorting tuples and sorting the list of tuples
|
|
116
|
+
return tuple(sorted(tuple(sorted(t)) for t in tuples))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def apply_swap(data: List[List[Optional[int]]], swap: Swap):
|
|
120
|
+
# Swap values between two slots
|
|
121
|
+
(i1, j1), (i2, j2) = swap
|
|
122
|
+
data[i1][j1], data[i2][j2] = data[i2][j2], data[i1][j1]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_equivalent_swaps(
|
|
126
|
+
source: List[Tuple[int, ...]], target: List[Tuple[int, ...]]
|
|
127
|
+
) -> List[Swap]:
|
|
128
|
+
src = expand(source)
|
|
129
|
+
tgt = expand(target)
|
|
130
|
+
|
|
131
|
+
target_form = canonical_form(regroup(tgt))
|
|
132
|
+
|
|
133
|
+
def config_key(state):
|
|
134
|
+
return canonical_form(regroup(state))
|
|
135
|
+
|
|
136
|
+
initial_key = config_key(src)
|
|
137
|
+
visited = {initial_key}
|
|
138
|
+
queue = deque([(copy.deepcopy(src), [])]) # (current state, list of swaps made)
|
|
139
|
+
|
|
140
|
+
while queue:
|
|
141
|
+
state, swaps = queue.popleft()
|
|
142
|
+
|
|
143
|
+
if config_key(state) == target_form:
|
|
144
|
+
return swaps
|
|
145
|
+
|
|
146
|
+
flat = flatten_with_slots(state)
|
|
147
|
+
|
|
148
|
+
for i in range(len(flat)):
|
|
149
|
+
for j in range(i + 1, len(flat)):
|
|
150
|
+
(_, slot_i), (_, slot_j) = flat[i], flat[j]
|
|
151
|
+
apply_swap(state, (slot_i, slot_j))
|
|
152
|
+
key = config_key(state)
|
|
153
|
+
|
|
154
|
+
if key not in visited:
|
|
155
|
+
visited.add(key)
|
|
156
|
+
queue.append((copy.deepcopy(state), swaps + [(slot_i, slot_j)]))
|
|
157
|
+
|
|
158
|
+
apply_swap(state, (slot_i, slot_j)) # Undo swap
|
|
159
|
+
|
|
160
|
+
return [] # Should not happen if input is valid
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def greedy_unique_packing(data: List[int]) -> List[List[int]]:
|
|
164
|
+
|
|
165
|
+
remaining = deque(data)
|
|
166
|
+
result = []
|
|
167
|
+
|
|
168
|
+
while remaining:
|
|
169
|
+
used = set()
|
|
170
|
+
group = []
|
|
171
|
+
i = 0
|
|
172
|
+
length = len(remaining)
|
|
173
|
+
|
|
174
|
+
while i < length:
|
|
175
|
+
item = remaining.popleft()
|
|
176
|
+
if item not in used:
|
|
177
|
+
group.append(item)
|
|
178
|
+
used.add(item)
|
|
179
|
+
else:
|
|
180
|
+
# Push it to the end for future groups
|
|
181
|
+
remaining.append(item)
|
|
182
|
+
i += 1
|
|
183
|
+
|
|
184
|
+
result.append(group)
|
|
185
|
+
|
|
186
|
+
return result
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_swap_move_qidxs(
|
|
190
|
+
swaps: List[Swap], init_qidxs: Sequence[Tuple[Optional[int], ...]]
|
|
191
|
+
) -> List[List[int]]:
|
|
192
|
+
# Convert tuples to mutable lists
|
|
193
|
+
swap_init_qidxs = expand(init_qidxs)
|
|
194
|
+
|
|
195
|
+
moved_qidxs = []
|
|
196
|
+
|
|
197
|
+
for (i1, j1), (i2, j2) in swaps:
|
|
198
|
+
first_idx = swap_init_qidxs[i1][j1]
|
|
199
|
+
sec_idx = swap_init_qidxs[i2][j2]
|
|
200
|
+
|
|
201
|
+
if first_idx is not None:
|
|
202
|
+
moved_qidxs.append(first_idx)
|
|
203
|
+
if sec_idx is not None:
|
|
204
|
+
moved_qidxs.append(sec_idx)
|
|
205
|
+
|
|
206
|
+
# Perform the swap
|
|
207
|
+
swap_init_qidxs[i1][j1], swap_init_qidxs[i2][j2] = sec_idx, first_idx
|
|
208
|
+
|
|
209
|
+
return greedy_unique_packing(moved_qidxs)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def pad_with_empty_tups(
|
|
213
|
+
target: List[Tuple[int, ...]], nqubs: int
|
|
214
|
+
) -> List[Tuple[int, ...]]:
|
|
215
|
+
|
|
216
|
+
while len(target) < nqubs:
|
|
217
|
+
target.append(())
|
|
218
|
+
|
|
219
|
+
return target
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def add_noise_to_swaps(
|
|
223
|
+
swaps: List[Swap],
|
|
224
|
+
init_qidxs: List[Tuple[int, ...]],
|
|
225
|
+
move_noise: cirq.AsymmetricDepolarizingChannel,
|
|
226
|
+
sitter_noise: cirq.AsymmetricDepolarizingChannel,
|
|
227
|
+
nqubs: int,
|
|
228
|
+
):
|
|
229
|
+
"""
|
|
230
|
+
Applies move noise to qubits that need to be swapped to reach a given configuration. This can be seen as the noise added to "pair-up"
|
|
231
|
+
or separate qubits to reach a target configuration before the application of gates
|
|
232
|
+
Args:
|
|
233
|
+
swaps, array of swaps
|
|
234
|
+
init_qidxs, qargs that reach the target configuration once the swaps are applied on it
|
|
235
|
+
move_noise, Pauli noise channel for moves
|
|
236
|
+
sitter_noise, Pauli channel for sitter noise
|
|
237
|
+
nqubs, the circuit width
|
|
238
|
+
"""
|
|
239
|
+
built_circuit = cirq.Circuit()
|
|
240
|
+
nqubs_idxs = np.arange(nqubs)
|
|
241
|
+
|
|
242
|
+
batches_move_qidxs = get_swap_move_qidxs(swaps, init_qidxs)
|
|
243
|
+
|
|
244
|
+
for batch in batches_move_qidxs:
|
|
245
|
+
built_moment = cirq.Moment()
|
|
246
|
+
non_mov_qidxs = numpy_complement(np.array(batch), nqubs_idxs)
|
|
247
|
+
|
|
248
|
+
for i in range(len(batch)):
|
|
249
|
+
built_moment += move_noise(cirq.LineQubit(batch[i]))
|
|
250
|
+
for j in range(len(non_mov_qidxs)):
|
|
251
|
+
built_moment += sitter_noise(cirq.LineQubit(non_mov_qidxs[j]))
|
|
252
|
+
|
|
253
|
+
built_circuit.append(built_moment)
|
|
254
|
+
|
|
255
|
+
# built_circuit.append(built_moment)
|
|
256
|
+
|
|
257
|
+
return built_circuit
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
#############################################
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def get_gate_error_channel(
|
|
264
|
+
moment: cirq.Moment,
|
|
265
|
+
sq_loc_rates: np.ndarray,
|
|
266
|
+
sq_glob_rates: np.ndarray,
|
|
267
|
+
cz_rates: np.ndarray,
|
|
268
|
+
unp_cz_rates: np.ndarray,
|
|
269
|
+
):
|
|
270
|
+
"""Applies gate errors to the circuit
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
moment: A cirq.Moment object.
|
|
274
|
+
sq_loc_rates: single local qubit rotation Pauli noise channel parameters (px, py, pz)
|
|
275
|
+
sq_glob_rates: single global qubit rotation Pauli noise channel parameters (px,py,pz)
|
|
276
|
+
cz_rates: two-qubit rotation Pauli noise channel parameters (ctrl_px, ctrl_py,ctrl_pz,tar_px,tar_py,tar_pz)
|
|
277
|
+
unp_cz_rates: Pauli noise channel parameters for qubits in the gate zone and outside blockade radius
|
|
278
|
+
Returns:
|
|
279
|
+
A new cirq.Moment object with the gate errors applied.
|
|
280
|
+
"""
|
|
281
|
+
# Check for the moment (layer) layout: global single qubit gates, or mixture of single qubit gates and two qubit gates
|
|
282
|
+
|
|
283
|
+
gates_in_layer = extract_u3_and_cz_qargs(moment)
|
|
284
|
+
# new_moment = cirq.Moment()
|
|
285
|
+
new_moments = cirq.Circuit()
|
|
286
|
+
|
|
287
|
+
if gates_in_layer["cz"] == []:
|
|
288
|
+
|
|
289
|
+
if gates_in_layer["u3"] == []:
|
|
290
|
+
print(
|
|
291
|
+
"Warning: Assumed Only single qubit gates in the layer, but there are no single qubit gates"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
if all(
|
|
295
|
+
np.all(np.isclose(element, gates_in_layer["angles"][0]))
|
|
296
|
+
for element in gates_in_layer["angles"]
|
|
297
|
+
):
|
|
298
|
+
pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
299
|
+
p_x=sq_glob_rates[0], p_y=sq_glob_rates[1], p_z=sq_glob_rates[2]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
for qub in gates_in_layer["u3"]:
|
|
303
|
+
|
|
304
|
+
# new_moment = new_moment +pauli_channel(qub[0])
|
|
305
|
+
new_moments.append(pauli_channel(qub[0]))
|
|
306
|
+
else:
|
|
307
|
+
pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
308
|
+
p_x=sq_loc_rates[0], p_y=sq_loc_rates[1], p_z=sq_loc_rates[2]
|
|
309
|
+
)
|
|
310
|
+
for qub in gates_in_layer["u3"]:
|
|
311
|
+
|
|
312
|
+
# new_moment = new_moment + pauli_channel(qub[0])
|
|
313
|
+
new_moments.append(pauli_channel(qub[0]))
|
|
314
|
+
|
|
315
|
+
else:
|
|
316
|
+
# there is at least one CZ gate...
|
|
317
|
+
ctrl_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
318
|
+
p_x=cz_rates[0], p_y=cz_rates[1], p_z=cz_rates[2]
|
|
319
|
+
)
|
|
320
|
+
tar_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
321
|
+
p_x=cz_rates[3], p_y=cz_rates[4], p_z=cz_rates[5]
|
|
322
|
+
)
|
|
323
|
+
loc_rot_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
324
|
+
p_x=sq_loc_rates[0], p_y=sq_loc_rates[1], p_z=sq_loc_rates[2]
|
|
325
|
+
)
|
|
326
|
+
unp_cz_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
327
|
+
p_x=unp_cz_rates[0], p_y=unp_cz_rates[1], p_z=unp_cz_rates[2]
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
for qub in gates_in_layer["cz"]:
|
|
331
|
+
new_moments.append(ctrl_pauli_channel(qub[0]))
|
|
332
|
+
new_moments.append(tar_pauli_channel(qub[1]))
|
|
333
|
+
# new_moment=new_moment+ctrl_pauli_channel(qub[0])
|
|
334
|
+
# new_moment=new_moment+tar_pauli_channel(qub[1])
|
|
335
|
+
|
|
336
|
+
for qub in gates_in_layer["u3"]:
|
|
337
|
+
new_moments.append(
|
|
338
|
+
unp_cz_pauli_channel(qub[0])
|
|
339
|
+
) ###qubits in the gate zone get unpaired_cz error
|
|
340
|
+
new_moments.append(loc_rot_pauli_channel(qub[0]))
|
|
341
|
+
# new_moment = new_moment + loc_rot_pauli_channel(qub[0])
|
|
342
|
+
|
|
343
|
+
return new_moments
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def add_move_and_sitter_channels(
|
|
347
|
+
ref_qargs: Sequence[Tuple[cirq.Qid, ...]] | None,
|
|
348
|
+
tar_qargs: Sequence[Tuple[cirq.Qid, ...]],
|
|
349
|
+
built_moment: cirq.Moment,
|
|
350
|
+
qub_reg: Sequence[cirq.Qid],
|
|
351
|
+
sitter_pauli_channel: cirq.Gate,
|
|
352
|
+
move_pauli_channel: cirq.Gate,
|
|
353
|
+
):
|
|
354
|
+
"""
|
|
355
|
+
Adds move and sitter noise channels according to the following rule: all the qargs in ref_moment that
|
|
356
|
+
are absent in tar_moment get a move error and the rest of qubits in the qub_reg get a sitter error. It also returns a boolean variable
|
|
357
|
+
that determines whether move noise was added
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
ref_qargs: reference qargs
|
|
361
|
+
tar_qargs: moment to make the comparison with
|
|
362
|
+
built_moment: the moment to which we append the noise channels
|
|
363
|
+
qub_reg: the qubit register
|
|
364
|
+
sitter_pauli channel: the parameterized sitter channel
|
|
365
|
+
move_pauli_channel: the parameterized move_pauli channel
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
# Faltten ref_qargs and ref_qargs for purposes of identifying how to apply noise:
|
|
369
|
+
flat_tar_qargs = flatten_qargs(tar_qargs)
|
|
370
|
+
|
|
371
|
+
if ref_qargs is None: # we are adding noise to the beginning of circuit...
|
|
372
|
+
|
|
373
|
+
flat_ref_qargs = flatten_qargs(tar_qargs)
|
|
374
|
+
|
|
375
|
+
bool_list = [k not in flat_tar_qargs for k in flat_ref_qargs]
|
|
376
|
+
|
|
377
|
+
else:
|
|
378
|
+
flat_ref_qargs = flatten_qargs(ref_qargs)
|
|
379
|
+
|
|
380
|
+
bool_list = [k in flat_tar_qargs for k in flat_ref_qargs]
|
|
381
|
+
|
|
382
|
+
rem_qubs = []
|
|
383
|
+
|
|
384
|
+
for i in range(len(flat_ref_qargs)):
|
|
385
|
+
if not bool_list[i]:
|
|
386
|
+
rem_qubs.append(flat_ref_qargs[i])
|
|
387
|
+
built_moment += move_pauli_channel(flat_ref_qargs[i])
|
|
388
|
+
|
|
389
|
+
if len(rem_qubs) >= 1:
|
|
390
|
+
|
|
391
|
+
for k in range(len(qub_reg)):
|
|
392
|
+
if qub_reg[k] not in rem_qubs:
|
|
393
|
+
built_moment += sitter_pauli_channel(qub_reg[k])
|
|
394
|
+
|
|
395
|
+
return built_moment, True
|
|
396
|
+
else:
|
|
397
|
+
return built_moment, False
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def get_move_error_channel_two_zoned(
|
|
401
|
+
curr_moment: cirq.Moment,
|
|
402
|
+
prev_moment: cirq.Moment | None,
|
|
403
|
+
move_rates: np.ndarray,
|
|
404
|
+
sitter_rates: np.ndarray,
|
|
405
|
+
nqubs: int,
|
|
406
|
+
):
|
|
407
|
+
"""Applies move noise channels to a cirq moment (curr_moment), depending on the qargs of another one (prev_moment)
|
|
408
|
+
returns a circuit that contains the noisy moments
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
curr_moment: A cirq.Moment object.
|
|
412
|
+
prev_moment: A cirq.Moment object
|
|
413
|
+
move_rates: Pauli noise channel parameters for atom moves (px,py,pz)
|
|
414
|
+
sitter_rates: probailities of sitter noise (px,py,pz)
|
|
415
|
+
nqubs: total number of qubits (width of the circuit)
|
|
416
|
+
Returns:
|
|
417
|
+
A circuit with atom move channels appended
|
|
418
|
+
"""
|
|
419
|
+
|
|
420
|
+
curr_qargs = get_qargs_from_moment(curr_moment)
|
|
421
|
+
|
|
422
|
+
move_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
423
|
+
p_x=move_rates[0], p_y=move_rates[1], p_z=move_rates[2]
|
|
424
|
+
)
|
|
425
|
+
sitter_pauli_channel = cirq.AsymmetricDepolarizingChannel(
|
|
426
|
+
p_x=sitter_rates[0], p_y=sitter_rates[1], p_z=sitter_rates[2]
|
|
427
|
+
)
|
|
428
|
+
qub_reg = [cirq.LineQubit(i) for i in range(nqubs)]
|
|
429
|
+
|
|
430
|
+
if prev_moment is None:
|
|
431
|
+
###Initial layer of circuit, all qubits in the first layer get move error, rest get sitter
|
|
432
|
+
new_moment = cirq.Moment()
|
|
433
|
+
dumb_circ = cirq.Circuit()
|
|
434
|
+
new_moment, _ = add_move_and_sitter_channels(
|
|
435
|
+
prev_moment,
|
|
436
|
+
curr_qargs,
|
|
437
|
+
new_moment,
|
|
438
|
+
qub_reg,
|
|
439
|
+
sitter_pauli_channel,
|
|
440
|
+
move_pauli_channel,
|
|
441
|
+
)
|
|
442
|
+
dumb_circ.append(new_moment)
|
|
443
|
+
|
|
444
|
+
else:
|
|
445
|
+
prev_qargs = get_qargs_from_moment(prev_moment)
|
|
446
|
+
# We follow this convention: 1) all qargs in previous moment that need to be removed from gate zone
|
|
447
|
+
# get move error, the rest get sitter error. 2) after this, all qargs that need to be brought to
|
|
448
|
+
# gate zone get move error, the rest get sitter error.
|
|
449
|
+
new_moment = cirq.Moment()
|
|
450
|
+
dumb_circ = cirq.Circuit()
|
|
451
|
+
new_moment, first_move_added = add_move_and_sitter_channels(
|
|
452
|
+
prev_qargs,
|
|
453
|
+
curr_qargs,
|
|
454
|
+
new_moment,
|
|
455
|
+
qub_reg,
|
|
456
|
+
sitter_pauli_channel,
|
|
457
|
+
move_pauli_channel,
|
|
458
|
+
)
|
|
459
|
+
dumb_circ.append(new_moment)
|
|
460
|
+
|
|
461
|
+
new_moment = cirq.Moment()
|
|
462
|
+
new_moment, second_move_added = add_move_and_sitter_channels(
|
|
463
|
+
curr_qargs,
|
|
464
|
+
prev_qargs,
|
|
465
|
+
new_moment,
|
|
466
|
+
qub_reg,
|
|
467
|
+
sitter_pauli_channel,
|
|
468
|
+
move_pauli_channel,
|
|
469
|
+
)
|
|
470
|
+
dumb_circ.append(new_moment)
|
|
471
|
+
# Once the noise channels to have the target qargs in the gate zone are added, we include an additional move error
|
|
472
|
+
# to reconfigure
|
|
473
|
+
|
|
474
|
+
# Find the initial and final qarg configuration in the previous and current moments...
|
|
475
|
+
prev_qidxs = qargs_to_qidxs(prev_qargs)
|
|
476
|
+
curr_qidxs = qargs_to_qidxs(curr_qargs)
|
|
477
|
+
|
|
478
|
+
intsc_rev = intersect_by_structure(prev_qidxs, curr_qidxs)
|
|
479
|
+
intsc_fow = intersect_by_structure(curr_qidxs, prev_qidxs)
|
|
480
|
+
|
|
481
|
+
# get swaps that render previous configuration to current...
|
|
482
|
+
swaps = get_equivalent_swaps(
|
|
483
|
+
pad_with_empty_tups(intsc_rev, nqubs), pad_with_empty_tups(intsc_fow, nqubs)
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# apply noise channels...
|
|
487
|
+
|
|
488
|
+
swap_noise_circ = add_noise_to_swaps(
|
|
489
|
+
swaps, intsc_rev, move_pauli_channel, sitter_pauli_channel, nqubs
|
|
490
|
+
)
|
|
491
|
+
dumb_circ.append(swap_noise_circ)
|
|
492
|
+
|
|
493
|
+
return dumb_circ
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def extract_u3_and_cz_qargs(moment: cirq.Moment):
|
|
497
|
+
"""
|
|
498
|
+
Extracts the qubit arguments (qargs) for u3 and CZ gates from a Cirq moment,
|
|
499
|
+
and the angle parameters of the u3 gates.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
moment: A cirq.Moment object containing only u3 and CZ gates.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
A dictionary with keys 'u3', 'cz', and 'angles', where:
|
|
506
|
+
- 'u3' maps to a list of qargs (tuples of qubits) for u3 OR PhXZ gates.
|
|
507
|
+
- 'cz' maps to a list of qargs (tuples of qubits) for CZ gates.
|
|
508
|
+
- 'angles' maps to a list of angle parameters (tuples) for the u3 gates.
|
|
509
|
+
"""
|
|
510
|
+
result = {"u3": [], "cz": [], "angles": []}
|
|
511
|
+
|
|
512
|
+
for op in moment.operations:
|
|
513
|
+
|
|
514
|
+
if isinstance(op.gate, QasmUGate): # u3 gate in Cirq
|
|
515
|
+
result["u3"].append(op.qubits)
|
|
516
|
+
# Extract angle parameters (x_exponent, z_exponent, axis_phase_exponent)
|
|
517
|
+
gate = cast(QasmUGate, op.gate)
|
|
518
|
+
angles = (gate.theta, gate.phi, gate.lmda)
|
|
519
|
+
result["angles"].append(angles)
|
|
520
|
+
elif isinstance(op.gate, cirq.PhasedXZGate): # CZ gate in Cirq
|
|
521
|
+
result["u3"].append(op.qubits)
|
|
522
|
+
# Extract angle parameters (x_exponent, z_exponent, axis_phase_exponent)
|
|
523
|
+
gate = cast(cirq.PhasedXZGate, op.gate)
|
|
524
|
+
angles = (gate.x_exponent, gate.z_exponent, gate.axis_phase_exponent)
|
|
525
|
+
|
|
526
|
+
result["angles"].append(angles)
|
|
527
|
+
elif isinstance(op.gate, cirq.CZPowGate): # CZ gate in Cirq
|
|
528
|
+
|
|
529
|
+
result["cz"].append(op.qubits)
|
|
530
|
+
|
|
531
|
+
return result
|