bloqade-circuit 0.2.3__py3-none-any.whl → 0.4.0__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.
Potentially problematic release.
This version of bloqade-circuit might be problematic. Click here for more details.
- bloqade/analysis/address/impls.py +3 -2
- bloqade/pyqrack/device.py +1 -3
- bloqade/pyqrack/noise/native.py +8 -8
- bloqade/pyqrack/qasm2/core.py +4 -1
- bloqade/pyqrack/squin/op.py +7 -0
- bloqade/pyqrack/squin/qubit.py +5 -27
- bloqade/pyqrack/squin/runtime.py +18 -0
- bloqade/pyqrack/squin/wire.py +4 -22
- bloqade/pyqrack/task.py +13 -5
- bloqade/qasm2/__init__.py +1 -0
- bloqade/qasm2/_qasm_loading.py +151 -0
- bloqade/qasm2/dialects/core/__init__.py +9 -1
- bloqade/qasm2/dialects/expr/__init__.py +18 -1
- bloqade/{noise/native → qasm2/dialects/noise}/__init__.py +1 -7
- bloqade/qasm2/dialects/noise/_dialect.py +3 -0
- bloqade/{noise → qasm2/dialects/noise}/fidelity.py +4 -4
- bloqade/qasm2/dialects/noise/model.py +278 -0
- bloqade/{noise/native → qasm2/dialects/noise}/stmts.py +1 -1
- bloqade/qasm2/dialects/uop/__init__.py +39 -3
- bloqade/qasm2/dialects/uop/schedule.py +1 -1
- bloqade/qasm2/emit/impls/__init__.py +1 -0
- bloqade/qasm2/emit/impls/noise.py +89 -0
- bloqade/qasm2/emit/main.py +23 -4
- bloqade/qasm2/emit/target.py +19 -4
- bloqade/qasm2/noise.py +67 -0
- bloqade/qasm2/parse/__init__.py +7 -4
- bloqade/qasm2/parse/lowering.py +20 -130
- bloqade/qasm2/parse/qasm2.lark +1 -1
- bloqade/qasm2/passes/__init__.py +1 -0
- bloqade/qasm2/passes/fold.py +6 -0
- bloqade/qasm2/passes/glob.py +12 -8
- bloqade/qasm2/passes/noise.py +27 -16
- bloqade/qasm2/passes/parallel.py +9 -0
- bloqade/qasm2/passes/unroll_if.py +25 -0
- bloqade/qasm2/rewrite/__init__.py +3 -0
- bloqade/qasm2/rewrite/desugar.py +3 -2
- bloqade/qasm2/rewrite/native_gates.py +67 -4
- bloqade/qasm2/rewrite/noise/__init__.py +0 -0
- bloqade/qasm2/rewrite/{heuristic_noise.py → noise/heuristic_noise.py} +32 -62
- bloqade/{noise/native/rewrite.py → qasm2/rewrite/noise/remove_noise.py} +2 -2
- bloqade/qasm2/rewrite/split_ifs.py +66 -0
- bloqade/qbraid/lowering.py +8 -8
- bloqade/squin/__init__.py +7 -1
- bloqade/squin/analysis/nsites/__init__.py +1 -0
- bloqade/squin/analysis/nsites/impls.py +16 -1
- bloqade/squin/groups.py +4 -4
- bloqade/squin/lowering.py +27 -0
- bloqade/squin/noise/__init__.py +7 -26
- bloqade/squin/noise/_wrapper.py +25 -0
- bloqade/squin/op/__init__.py +34 -159
- bloqade/squin/op/_wrapper.py +105 -0
- bloqade/squin/op/stdlib.py +62 -0
- bloqade/squin/op/stmts.py +10 -0
- bloqade/squin/passes/__init__.py +1 -0
- bloqade/squin/passes/stim.py +68 -0
- bloqade/squin/qubit.py +32 -37
- bloqade/squin/rewrite/__init__.py +11 -0
- bloqade/squin/rewrite/desugar.py +65 -0
- bloqade/squin/rewrite/qubit_to_stim.py +61 -0
- bloqade/squin/rewrite/squin_measure.py +73 -0
- bloqade/squin/rewrite/stim_rewrite_util.py +153 -0
- bloqade/squin/rewrite/wire_identity_elimination.py +24 -0
- bloqade/squin/rewrite/wire_to_stim.py +52 -0
- bloqade/squin/rewrite/wrap_analysis.py +72 -0
- bloqade/squin/wire.py +5 -22
- bloqade/stim/__init__.py +40 -5
- bloqade/stim/_wrappers.py +18 -12
- bloqade/stim/dialects/__init__.py +1 -5
- bloqade/stim/dialects/{aux → auxiliary}/__init__.py +13 -1
- bloqade/stim/dialects/{aux → auxiliary}/emit.py +18 -3
- bloqade/stim/dialects/{aux → auxiliary}/stmts/__init__.py +1 -0
- bloqade/stim/dialects/{aux → auxiliary}/stmts/annotate.py +8 -0
- bloqade/stim/dialects/collapse/__init__.py +13 -2
- bloqade/stim/dialects/collapse/{emit.py → emit_str.py} +4 -2
- bloqade/stim/dialects/collapse/stmts/pp_measure.py +1 -1
- bloqade/stim/dialects/gate/__init__.py +16 -1
- bloqade/stim/dialects/gate/emit.py +10 -3
- bloqade/stim/dialects/gate/stmts/base.py +1 -1
- bloqade/stim/dialects/gate/stmts/pp.py +1 -1
- bloqade/stim/dialects/noise/emit.py +33 -2
- bloqade/stim/dialects/noise/stmts.py +29 -0
- bloqade/stim/emit/__init__.py +1 -1
- bloqade/stim/groups.py +4 -2
- bloqade/stim/parse/__init__.py +1 -0
- bloqade/stim/parse/lowering.py +686 -0
- {bloqade_circuit-0.2.3.dist-info → bloqade_circuit-0.4.0.dist-info}/METADATA +5 -3
- {bloqade_circuit-0.2.3.dist-info → bloqade_circuit-0.4.0.dist-info}/RECORD +95 -77
- bloqade/noise/__init__.py +0 -2
- bloqade/noise/native/_dialect.py +0 -3
- bloqade/noise/native/_wrappers.py +0 -34
- bloqade/noise/native/model.py +0 -346
- bloqade/qasm2/dialects/noise.py +0 -16
- bloqade/squin/rewrite/measure_desugar.py +0 -33
- /bloqade/stim/dialects/{aux → auxiliary}/_dialect.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/interp.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/lowering.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/stmts/const.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/types.py +0 -0
- /bloqade/stim/emit/{stim.py → stim_str.py} +0 -0
- {bloqade_circuit-0.2.3.dist-info → bloqade_circuit-0.4.0.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.2.3.dist-info → bloqade_circuit-0.4.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from dataclasses import field, dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class MoveNoiseModelABC(abc.ABC):
|
|
7
|
+
"""Abstract base class for noise based on atom movement.
|
|
8
|
+
|
|
9
|
+
This class defines the interface for a noise model. The gate noise is calculated from the parameters
|
|
10
|
+
provided in this dataclass which can be updated when inheriting from this class. The move error is
|
|
11
|
+
calculated by implementing the parallel_cz_errors method which takes a set of ctrl and qarg qubits
|
|
12
|
+
and returns a noise model for all the qubits. The noise model is a dictionary with the keys being the
|
|
13
|
+
error rates for the qubits and the values being the list of qubits that the error rate applies to.
|
|
14
|
+
|
|
15
|
+
Once implemented the class can be used with the NoisePass to analyze a circuit and apply the noise
|
|
16
|
+
model to the circuit.
|
|
17
|
+
|
|
18
|
+
NOTE: This model is not guaranteed to be supported long-term in bloqade. We will be
|
|
19
|
+
moving towards a more general approach to noise modeling in the future.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# parameters for gate noise
|
|
24
|
+
|
|
25
|
+
local_px: float = field(default=4.102e-04, kw_only=True)
|
|
26
|
+
"""The error probability for a Pauli-X error during a local single qubit gate operation."""
|
|
27
|
+
local_py: float = field(default=4.102e-04, kw_only=True)
|
|
28
|
+
"""The error probability for a Pauli-Y error during a local single qubit gate operation."""
|
|
29
|
+
local_pz: float = field(default=4.112e-04, kw_only=True)
|
|
30
|
+
"""The error probability for a Pauli-Z error during a local single qubit gate operation."""
|
|
31
|
+
local_loss_prob: float = field(default=0.0, kw_only=True)
|
|
32
|
+
"""The error probability for a loss during a local single qubit gate operation."""
|
|
33
|
+
|
|
34
|
+
local_unaddressed_px: float = field(default=2.000e-07, kw_only=True)
|
|
35
|
+
"""The error probability for a Pauli-X error during a local single qubit gate operation when the qubit is not addressed."""
|
|
36
|
+
local_unaddressed_py: float = field(default=2.000e-07, kw_only=True)
|
|
37
|
+
"""The error probability for a Pauli-Y error during a local single qubit gate operation when the qubit is not addressed."""
|
|
38
|
+
local_unaddressed_pz: float = field(default=1.200e-06, kw_only=True)
|
|
39
|
+
"""The error probability for a Pauli-Z error during a local single qubit gate operation when the qubit is not addressed."""
|
|
40
|
+
local_unaddressed_loss_prob: float = field(default=0.0, kw_only=True)
|
|
41
|
+
"""The error probability for a loss during a local single qubit gate operation when the qubit is not addressed."""
|
|
42
|
+
|
|
43
|
+
global_px: float = field(default=6.500e-05, kw_only=True)
|
|
44
|
+
"""The error probability for a Pauli-X error during a global single qubit gate operation."""
|
|
45
|
+
global_py: float = field(default=6.500e-05, kw_only=True)
|
|
46
|
+
"""The error probability for a Pauli-Y error during a global single qubit gate operation."""
|
|
47
|
+
global_pz: float = field(default=6.500e-05, kw_only=True)
|
|
48
|
+
"""The error probability for a Pauli-Z error during a global single qubit gate operation."""
|
|
49
|
+
global_loss_prob: float = field(default=0.0, kw_only=True)
|
|
50
|
+
"""The error probability for a loss during a global single qubit gate operation."""
|
|
51
|
+
|
|
52
|
+
cz_paired_gate_px: float = field(default=6.549e-04, kw_only=True)
|
|
53
|
+
"""The error probability for a Pauli-X error during CZ gate operation when two qubits are within blockade radius."""
|
|
54
|
+
cz_paired_gate_py: float = field(default=6.549e-04, kw_only=True)
|
|
55
|
+
"""The error probability for a Pauli-Y error during CZ gate operation when two qubits are within blockade radius."""
|
|
56
|
+
cz_paired_gate_pz: float = field(default=3.184e-03, kw_only=True)
|
|
57
|
+
"""The error probability for a Pauli-Z error during CZ gate operation when two qubits are within blockade radius."""
|
|
58
|
+
cz_gate_loss_prob: float = field(default=0.0, kw_only=True)
|
|
59
|
+
"""The error probability for a loss during CZ gate operation when two qubits are within blockade radius."""
|
|
60
|
+
|
|
61
|
+
cz_unpaired_gate_px: float = field(default=5.149e-04, kw_only=True)
|
|
62
|
+
"""The error probability for Pauli-X error during CZ gate operation when another qubit is not within blockade radius."""
|
|
63
|
+
cz_unpaired_gate_py: float = field(default=5.149e-04, kw_only=True)
|
|
64
|
+
"""The error probability for Pauli-Y error during CZ gate operation when another qubit is not within blockade radius."""
|
|
65
|
+
cz_unpaired_gate_pz: float = field(default=2.185e-03, kw_only=True)
|
|
66
|
+
"""The error probability for Pauli-Z error during CZ gate operation when another qubit is not within blockade radius."""
|
|
67
|
+
cz_unpaired_loss_prob: float = field(default=0.0, kw_only=True)
|
|
68
|
+
"""The error probability for a loss during CZ gate operation when another qubit is not within blockade radius."""
|
|
69
|
+
|
|
70
|
+
# parameters for move noise
|
|
71
|
+
|
|
72
|
+
mover_px: float = field(default=8.060e-04, kw_only=True)
|
|
73
|
+
"""Probability of X error occurring on a moving qubit during a move operation"""
|
|
74
|
+
mover_py: float = field(default=8.060e-04, kw_only=True)
|
|
75
|
+
"""Probability of Y error occurring on a moving qubit during a move operation"""
|
|
76
|
+
mover_pz: float = field(default=2.458e-03, kw_only=True)
|
|
77
|
+
"""Probability of Z error occurring on a moving qubit during a move operation"""
|
|
78
|
+
move_loss_prob: float = field(default=0.0, kw_only=True)
|
|
79
|
+
"""Probability of loss occurring on a moving qubit during a move operation"""
|
|
80
|
+
|
|
81
|
+
sitter_px: float = field(default=3.066e-04, kw_only=True)
|
|
82
|
+
"""Probability of X error occurring on a stationary qubit during a move operation"""
|
|
83
|
+
sitter_py: float = field(default=3.066e-04, kw_only=True)
|
|
84
|
+
"""Probability of Y error occurring on a stationary qubit during a move operation"""
|
|
85
|
+
sitter_pz: float = field(default=4.639e-04, kw_only=True)
|
|
86
|
+
"""Probability of Z error occurring on a stationary qubit during a move operation"""
|
|
87
|
+
sit_loss_prob: float = field(default=0.0, kw_only=True)
|
|
88
|
+
"""Probability of loss occurring on a stationary qubit during a move operation"""
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def cz_paired_errors(
|
|
92
|
+
self,
|
|
93
|
+
) -> tuple[float, float, float, float]:
|
|
94
|
+
"""Returns the error rates for a CZ gate."""
|
|
95
|
+
return (
|
|
96
|
+
self.cz_paired_gate_px,
|
|
97
|
+
self.cz_paired_gate_py,
|
|
98
|
+
self.cz_paired_gate_pz,
|
|
99
|
+
self.cz_gate_loss_prob,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def cz_unpaired_errors(
|
|
104
|
+
self,
|
|
105
|
+
) -> tuple[float, float, float, float]:
|
|
106
|
+
"""Returns the error rates for a CZ gate."""
|
|
107
|
+
return (
|
|
108
|
+
self.cz_unpaired_gate_px,
|
|
109
|
+
self.cz_unpaired_gate_py,
|
|
110
|
+
self.cz_unpaired_gate_pz,
|
|
111
|
+
self.cz_unpaired_loss_prob,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def local_errors(
|
|
116
|
+
self,
|
|
117
|
+
) -> tuple[float, float, float, float]:
|
|
118
|
+
"""Returns the error rates for a local single qubit gate."""
|
|
119
|
+
return (
|
|
120
|
+
self.local_px,
|
|
121
|
+
self.local_py,
|
|
122
|
+
self.local_pz,
|
|
123
|
+
self.local_loss_prob,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def local_unaddressed_errors(
|
|
128
|
+
self,
|
|
129
|
+
) -> tuple[float, float, float, float]:
|
|
130
|
+
"""Returns the error rates for a local single qubit gate."""
|
|
131
|
+
return (
|
|
132
|
+
self.local_unaddressed_px,
|
|
133
|
+
self.local_unaddressed_py,
|
|
134
|
+
self.local_unaddressed_pz,
|
|
135
|
+
self.local_unaddressed_loss_prob,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def global_errors(
|
|
140
|
+
self,
|
|
141
|
+
) -> tuple[float, float, float, float]:
|
|
142
|
+
"""Returns the error rates for a global single qubit gate."""
|
|
143
|
+
return (
|
|
144
|
+
self.global_px,
|
|
145
|
+
self.global_py,
|
|
146
|
+
self.global_pz,
|
|
147
|
+
self.global_loss_prob,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def sitter_errors(
|
|
152
|
+
self,
|
|
153
|
+
) -> tuple[float, float, float, float]:
|
|
154
|
+
"""Returns the error rates for a move operation."""
|
|
155
|
+
return (
|
|
156
|
+
self.sitter_px,
|
|
157
|
+
self.sitter_py,
|
|
158
|
+
self.sitter_pz,
|
|
159
|
+
self.sit_loss_prob,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@abc.abstractmethod
|
|
163
|
+
def parallel_cz_errors(
|
|
164
|
+
self, ctrls: list[int], qargs: list[int], rest: list[int]
|
|
165
|
+
) -> dict[tuple[float, float, float, float], list[int]]:
|
|
166
|
+
"""Takes a set of ctrls and qargs and returns a noise model for all qubits."""
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def join_binary_probs(cls, p1: float, *args: float) -> float:
|
|
171
|
+
"""Merge the probabilities of an event happening if the event can only happen once.
|
|
172
|
+
|
|
173
|
+
For example, finding the effective probability of losing an atom from multiple sources, since
|
|
174
|
+
a qubit can only be lost once. This is done by using the formula:
|
|
175
|
+
|
|
176
|
+
p = p1 * (1 - p2) + p2 * (1 - p1)
|
|
177
|
+
|
|
178
|
+
applied recursively to all the probabilities in the list.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
p1 (float): The probability of the event happening.
|
|
182
|
+
arg (float): The probabilities of the event happening from other sources.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
float: The effective probability of the event happening.
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
if len(args) == 0:
|
|
189
|
+
return p1
|
|
190
|
+
else:
|
|
191
|
+
p2 = cls.join_binary_probs(*args)
|
|
192
|
+
return p1 * (1 - p2) + p2 * (1 - p1)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True)
|
|
196
|
+
class TwoRowZoneModel(MoveNoiseModelABC):
|
|
197
|
+
"""This model assumes that the qubits are arranged in a single storage row with a row corresponding to a gate zone below it.
|
|
198
|
+
|
|
199
|
+
The CZ gate noise is calculated using the following heuristic: The idle error is calculated by the total duration required
|
|
200
|
+
to do the move and entangle the qubits. Not every pair can be entangled at the same time, so we first deconflict the qargs
|
|
201
|
+
by finding subsets in which both the ctrl and the qarg qubits are in ascending order. This breaks the pairs into
|
|
202
|
+
groups that can be moved and entangled separately. We then take each group and assign each pair to a gate zone slot. The
|
|
203
|
+
slots are allocated by starting from the middle of the atoms and moving outwards making sure to keep the ctrl qubits in
|
|
204
|
+
ascending order. The time to move a group is calculated by finding the maximum travel distance of the qarg and ctrl qubits
|
|
205
|
+
and dviding by the move speed. The total move time is the sum of all the group move times. The error rate for all the qubits
|
|
206
|
+
is then calculated by using the poisson_pauli_prob function. An additional error for the pick operation is calculated by
|
|
207
|
+
joining the binary probabilities of the pick operation and the move operation.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def move_errors(
|
|
213
|
+
self,
|
|
214
|
+
) -> tuple[float, float, float, float]:
|
|
215
|
+
"""Returns the error rates for a move operation."""
|
|
216
|
+
return (
|
|
217
|
+
self.mover_px / 2,
|
|
218
|
+
self.mover_py / 2,
|
|
219
|
+
self.mover_pz / 2,
|
|
220
|
+
self.move_loss_prob / 2,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def deconflict(
|
|
224
|
+
self, ctrls: list[int], qargs: list[int]
|
|
225
|
+
) -> list[tuple[tuple[int, ...], tuple[int, ...]]]:
|
|
226
|
+
"""Return a list of groups of ctrl and qarg qubits that can be moved and entangled separately."""
|
|
227
|
+
# sort by ctrl qubit first to guarantee that they will be in ascending order
|
|
228
|
+
sorted_pairs = sorted(zip(ctrls, qargs))
|
|
229
|
+
|
|
230
|
+
groups: list[list[tuple[int, int]]] = []
|
|
231
|
+
# group by qarg only putting it in a group if the qarg is greater than the last qarg in the group
|
|
232
|
+
# thus ensuring that the qargs are in ascending order
|
|
233
|
+
while len(sorted_pairs) > 0:
|
|
234
|
+
ctrl, qarg = sorted_pairs.pop(0)
|
|
235
|
+
|
|
236
|
+
found = False
|
|
237
|
+
for group in groups:
|
|
238
|
+
if group[-1][1] < qarg:
|
|
239
|
+
group.append((ctrl, qarg))
|
|
240
|
+
found = True
|
|
241
|
+
break
|
|
242
|
+
if not found:
|
|
243
|
+
groups.append([(ctrl, qarg)])
|
|
244
|
+
|
|
245
|
+
new_groups: list[tuple[tuple[int, ...], tuple[int, ...]]] = []
|
|
246
|
+
|
|
247
|
+
for group in groups:
|
|
248
|
+
ctrl, qarg = zip(*group)
|
|
249
|
+
ctrl = tuple(ctrl)
|
|
250
|
+
qarg = tuple(qarg)
|
|
251
|
+
new_groups.append((ctrl, qarg))
|
|
252
|
+
|
|
253
|
+
return new_groups
|
|
254
|
+
|
|
255
|
+
def parallel_cz_errors(
|
|
256
|
+
self, ctrls: list[int], qargs: list[int], rest: list[int]
|
|
257
|
+
) -> dict[tuple[float, float, float, float], list[int]]:
|
|
258
|
+
"""Apply parallel gates by moving ctrl qubits to qarg qubits."""
|
|
259
|
+
groups = self.deconflict(ctrls, qargs)
|
|
260
|
+
movers = ctrls + qargs
|
|
261
|
+
num_moves = len(groups)
|
|
262
|
+
# ignore order O(p^2) errors since they are small
|
|
263
|
+
effective_move_errors = (
|
|
264
|
+
self.move_errors[0] + self.sitter_errors[0] * (num_moves - 1),
|
|
265
|
+
self.move_errors[1] + self.sitter_errors[1] * (num_moves - 1),
|
|
266
|
+
self.move_errors[2] + self.sitter_errors[2] * (num_moves - 1),
|
|
267
|
+
self.move_errors[3] + self.sitter_errors[3] * (num_moves - 1),
|
|
268
|
+
)
|
|
269
|
+
effective_sitter_errors = (
|
|
270
|
+
self.sitter_errors[0] * num_moves,
|
|
271
|
+
self.sitter_errors[1] * num_moves,
|
|
272
|
+
self.sitter_errors[2] * num_moves,
|
|
273
|
+
self.sitter_errors[3] * num_moves,
|
|
274
|
+
)
|
|
275
|
+
result = {effective_move_errors: list(movers)}
|
|
276
|
+
result.setdefault(effective_sitter_errors, []).extend(rest)
|
|
277
|
+
|
|
278
|
+
return result
|
|
@@ -1,4 +1,40 @@
|
|
|
1
|
-
from . import _emit as _emit, stmts as stmts
|
|
2
|
-
from .stmts import
|
|
1
|
+
from . import _emit as _emit, stmts as stmts, schedule as schedule
|
|
2
|
+
from .stmts import (
|
|
3
|
+
CH as CH,
|
|
4
|
+
CU as CU,
|
|
5
|
+
CX as CX,
|
|
6
|
+
CY as CY,
|
|
7
|
+
CZ as CZ,
|
|
8
|
+
RX as RX,
|
|
9
|
+
RY as RY,
|
|
10
|
+
RZ as RZ,
|
|
11
|
+
SX as SX,
|
|
12
|
+
U1 as U1,
|
|
13
|
+
U2 as U2,
|
|
14
|
+
CCX as CCX,
|
|
15
|
+
CRX as CRX,
|
|
16
|
+
CRY as CRY,
|
|
17
|
+
CRZ as CRZ,
|
|
18
|
+
CSX as CSX,
|
|
19
|
+
CU1 as CU1,
|
|
20
|
+
CU3 as CU3,
|
|
21
|
+
RXX as RXX,
|
|
22
|
+
RZZ as RZZ,
|
|
23
|
+
H as H,
|
|
24
|
+
S as S,
|
|
25
|
+
T as T,
|
|
26
|
+
X as X,
|
|
27
|
+
Y as Y,
|
|
28
|
+
Z as Z,
|
|
29
|
+
Id as Id,
|
|
30
|
+
Sdag as Sdag,
|
|
31
|
+
Swap as Swap,
|
|
32
|
+
Tdag as Tdag,
|
|
33
|
+
CSwap as CSwap,
|
|
34
|
+
SXdag as SXdag,
|
|
35
|
+
UGate as UGate,
|
|
36
|
+
Barrier as Barrier,
|
|
37
|
+
SingleQubitGate as SingleQubitGate,
|
|
38
|
+
TwoQubitCtrlGate as TwoQubitCtrlGate,
|
|
39
|
+
)
|
|
3
40
|
from ._dialect import dialect as dialect
|
|
4
|
-
from .schedule import * # noqa: F403
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .noise import NativeNoise as NativeNoise
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from kirin import interp
|
|
4
|
+
from kirin.dialects import ilist
|
|
5
|
+
|
|
6
|
+
from bloqade.qasm2.parse import ast
|
|
7
|
+
from bloqade.qasm2.dialects import noise
|
|
8
|
+
from bloqade.qasm2.emit.gate import EmitQASM2Gate, EmitQASM2Frame
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@noise.dialect.register(key="emit.qasm2.gate")
|
|
12
|
+
class NativeNoise(interp.MethodTable):
|
|
13
|
+
|
|
14
|
+
def _convert(self, node: ast.Bit | ast.Name) -> str:
|
|
15
|
+
if isinstance(node, ast.Bit):
|
|
16
|
+
return f"{node.name.id}[{node.addr}]"
|
|
17
|
+
else:
|
|
18
|
+
return f"{node.id}"
|
|
19
|
+
|
|
20
|
+
@interp.impl(noise.CZPauliChannel)
|
|
21
|
+
def emit_czp(
|
|
22
|
+
self,
|
|
23
|
+
emit: EmitQASM2Gate,
|
|
24
|
+
frame: EmitQASM2Frame,
|
|
25
|
+
stmt: noise.CZPauliChannel,
|
|
26
|
+
):
|
|
27
|
+
paired: bool = stmt.paired
|
|
28
|
+
px_ctrl: float = stmt.px_ctrl
|
|
29
|
+
py_ctrl: float = stmt.py_ctrl
|
|
30
|
+
pz_ctrl: float = stmt.pz_ctrl
|
|
31
|
+
px_qarg: float = stmt.pz_qarg
|
|
32
|
+
py_qarg: float = stmt.py_qarg
|
|
33
|
+
pz_qarg: float = stmt.pz_qarg
|
|
34
|
+
ctrls: ilist.IList[ast.Bit, Any] = frame.get(stmt.ctrls)
|
|
35
|
+
qargs: ilist.IList[ast.Bit, Any] = frame.get(stmt.qargs)
|
|
36
|
+
frame.body.append(
|
|
37
|
+
ast.Comment(
|
|
38
|
+
text=f"noise.CZPauliChannel(paired={paired}, p_ctrl=[x:{px_ctrl}, y:{py_ctrl}, z:{pz_ctrl}], p_qarg[x:{px_qarg}, y:{py_qarg}, z:{pz_qarg}])"
|
|
39
|
+
)
|
|
40
|
+
)
|
|
41
|
+
frame.body.append(
|
|
42
|
+
ast.Comment(
|
|
43
|
+
text=f" -: ctrls: {', '.join([self._convert(q) for q in ctrls])}"
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
frame.body.append(
|
|
47
|
+
ast.Comment(
|
|
48
|
+
text=f" -: qargs: {', '.join([self._convert(q) for q in qargs])}"
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
return ()
|
|
52
|
+
|
|
53
|
+
@interp.impl(noise.AtomLossChannel)
|
|
54
|
+
def emit_loss(
|
|
55
|
+
self,
|
|
56
|
+
emit: EmitQASM2Gate,
|
|
57
|
+
frame: EmitQASM2Frame,
|
|
58
|
+
stmt: noise.AtomLossChannel,
|
|
59
|
+
):
|
|
60
|
+
prob: float = stmt.prob
|
|
61
|
+
qargs: ilist.IList[ast.Bit, Any] = frame.get(stmt.qargs)
|
|
62
|
+
frame.body.append(ast.Comment(text=f"noise.Atomloss(p={prob})"))
|
|
63
|
+
frame.body.append(
|
|
64
|
+
ast.Comment(
|
|
65
|
+
text=f" -: qargs: {', '.join([self._convert(q) for q in qargs])}"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
return ()
|
|
69
|
+
|
|
70
|
+
@interp.impl(noise.PauliChannel)
|
|
71
|
+
def emit_pauli(
|
|
72
|
+
self,
|
|
73
|
+
emit: EmitQASM2Gate,
|
|
74
|
+
frame: EmitQASM2Frame,
|
|
75
|
+
stmt: noise.PauliChannel,
|
|
76
|
+
):
|
|
77
|
+
px: float = stmt.px
|
|
78
|
+
py: float = stmt.py
|
|
79
|
+
pz: float = stmt.pz
|
|
80
|
+
qargs: ilist.IList[ast.Bit, Any] = frame.get(stmt.qargs)
|
|
81
|
+
frame.body.append(
|
|
82
|
+
ast.Comment(text=f"noise.PauliChannel(px={px}, py={py}, pz={pz})")
|
|
83
|
+
)
|
|
84
|
+
frame.body.append(
|
|
85
|
+
ast.Comment(
|
|
86
|
+
text=f" -: qargs: {', '.join([self._convert(q) for q in qargs])}"
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
return ()
|
bloqade/qasm2/emit/main.py
CHANGED
|
@@ -5,8 +5,11 @@ from kirin.dialects import cf, scf, func
|
|
|
5
5
|
from kirin.ir.dialect import Dialect as Dialect
|
|
6
6
|
|
|
7
7
|
from bloqade.qasm2.parse import ast
|
|
8
|
+
from bloqade.qasm2.dialects.uop import SingleQubitGate, TwoQubitCtrlGate
|
|
9
|
+
from bloqade.qasm2.dialects.expr import GateFunction
|
|
8
10
|
|
|
9
11
|
from .base import EmitQASM2Base, EmitQASM2Frame
|
|
12
|
+
from ..dialects.core.stmts import Reset, Measure
|
|
10
13
|
|
|
11
14
|
|
|
12
15
|
@dataclass
|
|
@@ -22,12 +25,10 @@ class Func(interp.MethodTable):
|
|
|
22
25
|
def emit_func(
|
|
23
26
|
self, emit: EmitQASM2Main, frame: EmitQASM2Frame, stmt: func.Function
|
|
24
27
|
):
|
|
25
|
-
from bloqade.qasm2.dialects import glob,
|
|
28
|
+
from bloqade.qasm2.dialects import glob, parallel
|
|
26
29
|
|
|
27
30
|
emit.run_ssacfg_region(frame, stmt.body, ())
|
|
28
|
-
if emit.dialects.data.intersection(
|
|
29
|
-
(parallel.dialect, glob.dialect, noise.dialect)
|
|
30
|
-
):
|
|
31
|
+
if emit.dialects.data.intersection((parallel.dialect, glob.dialect)):
|
|
31
32
|
header = ast.Kirin([dialect.name for dialect in emit.dialects])
|
|
32
33
|
else:
|
|
33
34
|
header = ast.OPENQASM(ast.Version(2, 0))
|
|
@@ -94,6 +95,24 @@ class Scf(interp.MethodTable):
|
|
|
94
95
|
|
|
95
96
|
cond = emit.assert_node(ast.Cmp, frame.get(stmt.cond))
|
|
96
97
|
|
|
98
|
+
# NOTE: we need exactly one of those in the then body in order to emit valid QASM2
|
|
99
|
+
AllowedThenType = SingleQubitGate | TwoQubitCtrlGate | Measure | Reset
|
|
100
|
+
|
|
101
|
+
then_stmts = stmt.then_body.blocks[0].stmts
|
|
102
|
+
uop_stmts = 0
|
|
103
|
+
for s in then_stmts:
|
|
104
|
+
if isinstance(s, AllowedThenType):
|
|
105
|
+
uop_stmts += 1
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
if isinstance(s, func.Invoke):
|
|
109
|
+
uop_stmts += isinstance(s.callee.code, GateFunction)
|
|
110
|
+
|
|
111
|
+
if uop_stmts != 1:
|
|
112
|
+
raise interp.InterpreterError(
|
|
113
|
+
"Cannot lower if-statement: QASM2 only allows exactly one quantum operation in the body."
|
|
114
|
+
)
|
|
115
|
+
|
|
97
116
|
with emit.new_frame(stmt) as then_frame:
|
|
98
117
|
then_frame.entries.update(frame.entries)
|
|
99
118
|
emit.emit_block(then_frame, stmt.then_body.blocks[0])
|
bloqade/qasm2/emit/target.py
CHANGED
|
@@ -11,6 +11,7 @@ from bloqade.qasm2.passes.glob import GlobalToParallel
|
|
|
11
11
|
from bloqade.qasm2.passes.py2qasm import Py2QASM
|
|
12
12
|
from bloqade.qasm2.passes.parallel import ParallelToUOp
|
|
13
13
|
|
|
14
|
+
from . import impls as impls # register the tables
|
|
14
15
|
from .gate import EmitQASM2Gate
|
|
15
16
|
from .main import EmitQASM2Main
|
|
16
17
|
|
|
@@ -27,6 +28,8 @@ class QASM2:
|
|
|
27
28
|
allow_parallel: bool = False,
|
|
28
29
|
allow_global: bool = False,
|
|
29
30
|
custom_gate: bool = True,
|
|
31
|
+
unroll_ifs: bool = True,
|
|
32
|
+
allow_noise: bool = True,
|
|
30
33
|
) -> None:
|
|
31
34
|
"""Initialize the QASM2 target.
|
|
32
35
|
|
|
@@ -43,9 +46,14 @@ class QASM2:
|
|
|
43
46
|
qelib1 (bool):
|
|
44
47
|
Include the `include "qelib1.inc"` line in the resulting QASM2 AST that's
|
|
45
48
|
submitted to qBraid. Defaults to `True`.
|
|
49
|
+
|
|
46
50
|
custom_gate (bool):
|
|
47
51
|
Include the custom gate definitions in the resulting QASM2 AST. Defaults to `True`. If `False`, all the qasm2.gate will be inlined.
|
|
48
52
|
|
|
53
|
+
unroll_ifs (bool):
|
|
54
|
+
Unrolls if statements with multiple qasm2 statements in the body in order to produce valid qasm2 output, which only allows a single
|
|
55
|
+
operation in an if body. Defaults to `True`.
|
|
56
|
+
|
|
49
57
|
|
|
50
58
|
|
|
51
59
|
"""
|
|
@@ -58,6 +66,7 @@ class QASM2:
|
|
|
58
66
|
self.custom_gate = custom_gate
|
|
59
67
|
self.allow_parallel = allow_parallel
|
|
60
68
|
self.allow_global = allow_global
|
|
69
|
+
self.unroll_ifs = unroll_ifs
|
|
61
70
|
|
|
62
71
|
if allow_parallel:
|
|
63
72
|
self.main_target = self.main_target.add(qasm2.dialects.parallel)
|
|
@@ -67,7 +76,11 @@ class QASM2:
|
|
|
67
76
|
self.main_target = self.main_target.add(qasm2.dialects.glob)
|
|
68
77
|
self.gate_target = self.gate_target.add(qasm2.dialects.glob)
|
|
69
78
|
|
|
70
|
-
if
|
|
79
|
+
if allow_noise:
|
|
80
|
+
self.main_target = self.main_target.add(qasm2.dialects.noise)
|
|
81
|
+
self.gate_target = self.gate_target.add(qasm2.dialects.noise)
|
|
82
|
+
|
|
83
|
+
if allow_global or allow_parallel or allow_noise:
|
|
71
84
|
self.main_target = self.main_target.add(ilist)
|
|
72
85
|
self.gate_target = self.gate_target.add(ilist)
|
|
73
86
|
|
|
@@ -87,9 +100,11 @@ class QASM2:
|
|
|
87
100
|
|
|
88
101
|
# make a cloned instance of kernel
|
|
89
102
|
entry = entry.similar()
|
|
90
|
-
QASM2Fold(
|
|
91
|
-
entry
|
|
92
|
-
|
|
103
|
+
QASM2Fold(
|
|
104
|
+
entry.dialects,
|
|
105
|
+
inline_gate_subroutine=not self.custom_gate,
|
|
106
|
+
unroll_ifs=self.unroll_ifs,
|
|
107
|
+
).fixpoint(entry)
|
|
93
108
|
|
|
94
109
|
if not self.allow_global:
|
|
95
110
|
# rewrite global to parallel
|
bloqade/qasm2/noise.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from kirin.dialects import ilist
|
|
4
|
+
from kirin.lowering import wraps
|
|
5
|
+
|
|
6
|
+
from .types import Qubit
|
|
7
|
+
from .dialects import noise
|
|
8
|
+
from .dialects.noise import (
|
|
9
|
+
TwoRowZoneModel as TwoRowZoneModel,
|
|
10
|
+
MoveNoiseModelABC as MoveNoiseModelABC,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@wraps(noise.AtomLossChannel)
|
|
15
|
+
def atom_loss_channel(qargs: ilist.IList[Qubit, Any] | list, *, prob: float) -> None:
|
|
16
|
+
"""Apply an atom loss channel to a list of qubits.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
qargs (ilist.IList[Qubit, Any] | list): List of qubits to apply the noise to.
|
|
20
|
+
prob (float): The loss probability.
|
|
21
|
+
"""
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@wraps(noise.PauliChannel)
|
|
26
|
+
def pauli_channel(
|
|
27
|
+
qargs: ilist.IList[Qubit, Any] | list, *, px: float, py: float, pz: float
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Apply a Pauli channel to a list of qubits.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
qargs (ilist.IList[Qubit, Any] | list): List of qubits to apply the noise to.
|
|
33
|
+
px (float): Probability of X error.
|
|
34
|
+
py (float): Probability of Y error.
|
|
35
|
+
pz (float): Probability of Z error.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@wraps(noise.CZPauliChannel)
|
|
40
|
+
def cz_pauli_channel(
|
|
41
|
+
ctrls: ilist.IList[Qubit, Any] | list,
|
|
42
|
+
qargs: ilist.IList[Qubit, Any] | list,
|
|
43
|
+
*,
|
|
44
|
+
px_ctrl: float,
|
|
45
|
+
py_ctrl: float,
|
|
46
|
+
pz_ctrl: float,
|
|
47
|
+
px_qarg: float,
|
|
48
|
+
py_qarg: float,
|
|
49
|
+
pz_qarg: float,
|
|
50
|
+
paired: bool,
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Insert noise for a CZ gate with a Pauli channel on qubits.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
ctrls: List of control qubits.
|
|
56
|
+
qarg2: List of target qubits.
|
|
57
|
+
px_ctrl: Probability of X error on control qubits.
|
|
58
|
+
py_ctrl: Probability of Y error on control qubits.
|
|
59
|
+
pz_ctrl: Probability of Z error on control qubits.
|
|
60
|
+
px_qarg: Probability of X error on target qubits.
|
|
61
|
+
py_qarg: Probability of Y error on target qubits.
|
|
62
|
+
pz_qarg: Probability of Z error on target qubits.
|
|
63
|
+
paired: If True, the noise is applied to both control and target qubits
|
|
64
|
+
are not lost otherwise skip this error. If False Apply the noise on
|
|
65
|
+
the whatever qubit is not lost.
|
|
66
|
+
"""
|
|
67
|
+
...
|
bloqade/qasm2/parse/__init__.py
CHANGED
|
@@ -19,19 +19,22 @@ def loadfile(file: str | pathlib.Path):
|
|
|
19
19
|
return loads(f.read())
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def pprint(node: ast.Node, *, console: Console | None = None):
|
|
22
|
+
def pprint(node: ast.Node, *, console: Console | None = None, no_color: bool = False):
|
|
23
23
|
if console:
|
|
24
|
-
|
|
24
|
+
printer = Printer(console)
|
|
25
25
|
else:
|
|
26
|
-
Printer()
|
|
26
|
+
printer = Printer()
|
|
27
|
+
printer.console.no_color = no_color
|
|
28
|
+
printer.visit(node)
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
def spprint(node: ast.Node, *, console: Console | None = None):
|
|
31
|
+
def spprint(node: ast.Node, *, console: Console | None = None, no_color: bool = False):
|
|
30
32
|
if console:
|
|
31
33
|
printer = Printer(console)
|
|
32
34
|
else:
|
|
33
35
|
printer = Printer()
|
|
34
36
|
|
|
37
|
+
printer.console.no_color = no_color
|
|
35
38
|
with printer.string_io() as stream:
|
|
36
39
|
printer.visit(node)
|
|
37
40
|
return stream.getvalue()
|