bloqade-circuit 0.1.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.

Files changed (153) hide show
  1. bloqade/analysis/__init__.py +0 -0
  2. bloqade/analysis/address/__init__.py +11 -0
  3. bloqade/analysis/address/analysis.py +60 -0
  4. bloqade/analysis/address/impls.py +228 -0
  5. bloqade/analysis/address/lattice.py +85 -0
  6. bloqade/noise/__init__.py +1 -0
  7. bloqade/noise/native/__init__.py +20 -0
  8. bloqade/noise/native/_dialect.py +3 -0
  9. bloqade/noise/native/_wrappers.py +34 -0
  10. bloqade/noise/native/model.py +347 -0
  11. bloqade/noise/native/rewrite.py +35 -0
  12. bloqade/noise/native/stmts.py +46 -0
  13. bloqade/pyqrack/__init__.py +18 -0
  14. bloqade/pyqrack/base.py +131 -0
  15. bloqade/pyqrack/noise/__init__.py +0 -0
  16. bloqade/pyqrack/noise/native.py +100 -0
  17. bloqade/pyqrack/qasm2/__init__.py +0 -0
  18. bloqade/pyqrack/qasm2/core.py +79 -0
  19. bloqade/pyqrack/qasm2/parallel.py +46 -0
  20. bloqade/pyqrack/qasm2/uop.py +247 -0
  21. bloqade/pyqrack/reg.py +109 -0
  22. bloqade/pyqrack/target.py +112 -0
  23. bloqade/qasm2/__init__.py +19 -0
  24. bloqade/qasm2/_wrappers.py +674 -0
  25. bloqade/qasm2/dialects/__init__.py +10 -0
  26. bloqade/qasm2/dialects/core/__init__.py +3 -0
  27. bloqade/qasm2/dialects/core/_dialect.py +3 -0
  28. bloqade/qasm2/dialects/core/_emit.py +68 -0
  29. bloqade/qasm2/dialects/core/_typeinfer.py +23 -0
  30. bloqade/qasm2/dialects/core/address.py +38 -0
  31. bloqade/qasm2/dialects/core/stmts.py +94 -0
  32. bloqade/qasm2/dialects/expr/__init__.py +3 -0
  33. bloqade/qasm2/dialects/expr/_dialect.py +3 -0
  34. bloqade/qasm2/dialects/expr/_emit.py +103 -0
  35. bloqade/qasm2/dialects/expr/_from_python.py +86 -0
  36. bloqade/qasm2/dialects/expr/_interp.py +75 -0
  37. bloqade/qasm2/dialects/expr/stmts.py +262 -0
  38. bloqade/qasm2/dialects/glob.py +45 -0
  39. bloqade/qasm2/dialects/indexing.py +64 -0
  40. bloqade/qasm2/dialects/inline.py +76 -0
  41. bloqade/qasm2/dialects/noise.py +16 -0
  42. bloqade/qasm2/dialects/parallel.py +110 -0
  43. bloqade/qasm2/dialects/uop/__init__.py +4 -0
  44. bloqade/qasm2/dialects/uop/_dialect.py +3 -0
  45. bloqade/qasm2/dialects/uop/_emit.py +211 -0
  46. bloqade/qasm2/dialects/uop/schedule.py +89 -0
  47. bloqade/qasm2/dialects/uop/stmts.py +325 -0
  48. bloqade/qasm2/emit/__init__.py +1 -0
  49. bloqade/qasm2/emit/base.py +72 -0
  50. bloqade/qasm2/emit/gate.py +102 -0
  51. bloqade/qasm2/emit/main.py +106 -0
  52. bloqade/qasm2/emit/target.py +165 -0
  53. bloqade/qasm2/glob.py +24 -0
  54. bloqade/qasm2/groups.py +120 -0
  55. bloqade/qasm2/parallel.py +48 -0
  56. bloqade/qasm2/parse/__init__.py +37 -0
  57. bloqade/qasm2/parse/ast.py +235 -0
  58. bloqade/qasm2/parse/build.py +289 -0
  59. bloqade/qasm2/parse/lowering.py +553 -0
  60. bloqade/qasm2/parse/parser.py +5 -0
  61. bloqade/qasm2/parse/print.py +293 -0
  62. bloqade/qasm2/parse/qasm2.lark +75 -0
  63. bloqade/qasm2/parse/visitor.py +16 -0
  64. bloqade/qasm2/parse/visitor.pyi +39 -0
  65. bloqade/qasm2/passes/__init__.py +5 -0
  66. bloqade/qasm2/passes/fold.py +94 -0
  67. bloqade/qasm2/passes/glob.py +119 -0
  68. bloqade/qasm2/passes/noise.py +61 -0
  69. bloqade/qasm2/passes/parallel.py +176 -0
  70. bloqade/qasm2/passes/py2qasm.py +63 -0
  71. bloqade/qasm2/passes/qasm2py.py +61 -0
  72. bloqade/qasm2/rewrite/__init__.py +12 -0
  73. bloqade/qasm2/rewrite/desugar.py +28 -0
  74. bloqade/qasm2/rewrite/glob.py +103 -0
  75. bloqade/qasm2/rewrite/heuristic_noise.py +247 -0
  76. bloqade/qasm2/rewrite/native_gates.py +447 -0
  77. bloqade/qasm2/rewrite/parallel_to_uop.py +83 -0
  78. bloqade/qasm2/rewrite/register.py +45 -0
  79. bloqade/qasm2/rewrite/uop_to_parallel.py +395 -0
  80. bloqade/qasm2/types.py +39 -0
  81. bloqade/qbraid/__init__.py +2 -0
  82. bloqade/qbraid/lowering.py +324 -0
  83. bloqade/qbraid/schema.py +252 -0
  84. bloqade/qbraid/simulation_result.py +99 -0
  85. bloqade/qbraid/target.py +86 -0
  86. bloqade/squin/__init__.py +2 -0
  87. bloqade/squin/analysis/__init__.py +0 -0
  88. bloqade/squin/analysis/nsites/__init__.py +8 -0
  89. bloqade/squin/analysis/nsites/analysis.py +52 -0
  90. bloqade/squin/analysis/nsites/impls.py +69 -0
  91. bloqade/squin/analysis/nsites/lattice.py +49 -0
  92. bloqade/squin/analysis/schedule.py +244 -0
  93. bloqade/squin/groups.py +38 -0
  94. bloqade/squin/op/__init__.py +132 -0
  95. bloqade/squin/op/_dialect.py +3 -0
  96. bloqade/squin/op/complex.py +6 -0
  97. bloqade/squin/op/stmts.py +220 -0
  98. bloqade/squin/op/traits.py +43 -0
  99. bloqade/squin/op/types.py +10 -0
  100. bloqade/squin/qubit.py +118 -0
  101. bloqade/squin/wire.py +103 -0
  102. bloqade/stim/__init__.py +6 -0
  103. bloqade/stim/_wrappers.py +186 -0
  104. bloqade/stim/dialects/__init__.py +5 -0
  105. bloqade/stim/dialects/aux/__init__.py +11 -0
  106. bloqade/stim/dialects/aux/_dialect.py +3 -0
  107. bloqade/stim/dialects/aux/emit.py +102 -0
  108. bloqade/stim/dialects/aux/interp.py +39 -0
  109. bloqade/stim/dialects/aux/lowering.py +40 -0
  110. bloqade/stim/dialects/aux/stmts/__init__.py +14 -0
  111. bloqade/stim/dialects/aux/stmts/annotate.py +47 -0
  112. bloqade/stim/dialects/aux/stmts/const.py +95 -0
  113. bloqade/stim/dialects/aux/types.py +19 -0
  114. bloqade/stim/dialects/collapse/__init__.py +3 -0
  115. bloqade/stim/dialects/collapse/_dialect.py +3 -0
  116. bloqade/stim/dialects/collapse/emit.py +68 -0
  117. bloqade/stim/dialects/collapse/stmts/__init__.py +3 -0
  118. bloqade/stim/dialects/collapse/stmts/measure.py +45 -0
  119. bloqade/stim/dialects/collapse/stmts/pp_measure.py +14 -0
  120. bloqade/stim/dialects/collapse/stmts/reset.py +26 -0
  121. bloqade/stim/dialects/gate/__init__.py +3 -0
  122. bloqade/stim/dialects/gate/_dialect.py +3 -0
  123. bloqade/stim/dialects/gate/emit.py +87 -0
  124. bloqade/stim/dialects/gate/stmts/__init__.py +14 -0
  125. bloqade/stim/dialects/gate/stmts/base.py +31 -0
  126. bloqade/stim/dialects/gate/stmts/clifford_1q.py +53 -0
  127. bloqade/stim/dialects/gate/stmts/clifford_2q.py +11 -0
  128. bloqade/stim/dialects/gate/stmts/control_2q.py +21 -0
  129. bloqade/stim/dialects/gate/stmts/pp.py +15 -0
  130. bloqade/stim/dialects/noise/__init__.py +3 -0
  131. bloqade/stim/dialects/noise/_dialect.py +3 -0
  132. bloqade/stim/dialects/noise/emit.py +66 -0
  133. bloqade/stim/dialects/noise/stmts.py +77 -0
  134. bloqade/stim/emit/__init__.py +1 -0
  135. bloqade/stim/emit/stim.py +54 -0
  136. bloqade/stim/groups.py +26 -0
  137. bloqade/test_utils.py +35 -0
  138. bloqade/types.py +24 -0
  139. bloqade/visual/__init__.py +1 -0
  140. bloqade/visual/animation/__init__.py +0 -0
  141. bloqade/visual/animation/animate.py +267 -0
  142. bloqade/visual/animation/base.py +346 -0
  143. bloqade/visual/animation/gate_event.py +24 -0
  144. bloqade/visual/animation/runtime/__init__.py +0 -0
  145. bloqade/visual/animation/runtime/aod.py +36 -0
  146. bloqade/visual/animation/runtime/atoms.py +55 -0
  147. bloqade/visual/animation/runtime/ppoly.py +50 -0
  148. bloqade/visual/animation/runtime/qpustate.py +119 -0
  149. bloqade/visual/animation/runtime/utils.py +43 -0
  150. bloqade_circuit-0.1.0.dist-info/METADATA +70 -0
  151. bloqade_circuit-0.1.0.dist-info/RECORD +153 -0
  152. bloqade_circuit-0.1.0.dist-info/WHEEL +4 -0
  153. bloqade_circuit-0.1.0.dist-info/licenses/LICENSE +234 -0
@@ -0,0 +1,324 @@
1
+ from typing import Dict, List, Tuple, Sequence
2
+ from dataclasses import field, dataclass
3
+
4
+ from kirin import ir, types, passes
5
+ from kirin.dialects import func, ilist
6
+
7
+ from bloqade import noise, qasm2
8
+ from bloqade.qbraid import schema
9
+ from bloqade.qasm2.dialects import glob, parallel
10
+
11
+
12
+ @ir.dialect_group(
13
+ [func, qasm2.core, qasm2.uop, parallel, glob, qasm2.expr, noise.native, ilist]
14
+ )
15
+ def qbraid_noise(
16
+ self,
17
+ ):
18
+ fold_pass = passes.Fold(self)
19
+ typeinfer_pass = passes.TypeInfer(self)
20
+
21
+ def run_pass(
22
+ method: ir.Method,
23
+ *,
24
+ fold: bool = True,
25
+ ):
26
+ method.verify()
27
+
28
+ if fold:
29
+ fold_pass(method)
30
+
31
+ typeinfer_pass(method)
32
+ method.code.verify_type()
33
+
34
+ return run_pass
35
+
36
+
37
+ @dataclass
38
+ class Lowering:
39
+ qubit_list: List[ir.SSAValue] = field(init=False, default_factory=list)
40
+ qubit_id_map: Dict[int, ir.SSAValue] = field(init=False, default_factory=dict)
41
+ bit_id_map: Dict[int, ir.SSAValue] = field(init=False, default_factory=dict)
42
+ block_list: List[ir.Statement] = field(init=False, default_factory=list)
43
+
44
+ def lower(
45
+ self,
46
+ sym_name: str,
47
+ noise_model: schema.NoiseModel,
48
+ return_qreg: bool = False,
49
+ ) -> ir.Method:
50
+ """Lower the noise model to a method.
51
+
52
+ Args:
53
+ name (str): The name of the method to generate.
54
+ return_qreg (bool): Use the quantum register as the return value.
55
+
56
+ Returns:
57
+ Method: The generated kirin method.
58
+
59
+ """
60
+ self.process_noise_model(noise_model, return_qreg)
61
+ block = ir.Block(stmts=self.block_list)
62
+ ret_type = qasm2.types.QRegType if return_qreg else qasm2.types.CRegType
63
+ block.args.append_from(types.MethodType[[], ret_type], name=f"{sym_name}_self")
64
+ region = ir.Region(block)
65
+ func_stmt = func.Function(
66
+ sym_name=sym_name,
67
+ signature=func.Signature(inputs=(), output=qasm2.types.QRegType),
68
+ body=region,
69
+ )
70
+
71
+ mt = ir.Method(
72
+ mod=None,
73
+ py_func=None,
74
+ sym_name=sym_name,
75
+ dialects=qbraid_noise,
76
+ code=func_stmt,
77
+ arg_names=[],
78
+ )
79
+ qbraid_noise.run_pass(mt) # type: ignore
80
+ return mt
81
+
82
+ def process_noise_model(self, noise_model: schema.NoiseModel, return_qreg: bool):
83
+ num_qubits = self.lower_number(noise_model.num_qubits)
84
+
85
+ reg = qasm2.core.QRegNew(num_qubits)
86
+ creg = qasm2.core.CRegNew(num_qubits)
87
+ self.block_list.append(reg)
88
+ self.block_list.append(creg)
89
+
90
+ for idx_value, qubit in enumerate(noise_model.all_qubits):
91
+ idx = self.lower_number(idx_value)
92
+ self.block_list.append(qubit_stmt := qasm2.core.QRegGet(reg.result, idx))
93
+ self.block_list.append(bit_stmt := qasm2.core.CRegGet(creg.result, idx))
94
+
95
+ self.qubit_id_map[qubit] = qubit_stmt.result
96
+ self.bit_id_map[qubit] = bit_stmt.result
97
+ self.qubit_list.append(qubit_stmt.result)
98
+
99
+ for gate_event in noise_model.gate_events:
100
+ self.process_gate_event(gate_event)
101
+
102
+ if return_qreg:
103
+ self.block_list.append(func.Return(reg.result))
104
+ else:
105
+ self.block_list.append(func.Return(creg.result))
106
+
107
+ def process_gate_event(self, node: schema.GateEvent):
108
+ self.lower_atom_loss(node.error.survival_prob)
109
+
110
+ if isinstance(node.operation, schema.CZ):
111
+ assert isinstance(node.error, schema.CZError), "Only CZError is supported"
112
+ self.process_cz_pauli_error(node.operation.participants, node.error)
113
+ self.lower_cz_gates(node.operation)
114
+ else:
115
+
116
+ error = node.error
117
+ assert isinstance(
118
+ error, schema.SingleQubitError
119
+ ), "Only SingleQubitError is supported"
120
+ self.lower_pauli_errors(error.operator_error)
121
+
122
+ operation = node.operation
123
+ assert isinstance(
124
+ operation,
125
+ (
126
+ schema.GlobalW,
127
+ schema.LocalW,
128
+ schema.GlobalRz,
129
+ schema.LocalRz,
130
+ schema.Measurement,
131
+ ),
132
+ ), f"Only W and Rz gates are supported, found {type(operation)}.__name__"
133
+
134
+ if isinstance(operation, schema.GlobalW):
135
+ self.lower_w_gates(
136
+ tuple(self.qubit_id_map.keys()), operation.theta, operation.phi
137
+ )
138
+ elif isinstance(operation, schema.LocalW):
139
+ self.lower_w_gates(
140
+ operation.participants, operation.theta, operation.phi
141
+ )
142
+ elif isinstance(operation, schema.GlobalRz):
143
+ self.lower_rz_gates(tuple(self.qubit_id_map.keys()), operation.phi)
144
+ elif isinstance(operation, schema.LocalRz):
145
+ self.lower_rz_gates(operation.participants, operation.phi)
146
+ elif isinstance(operation, schema.Measurement):
147
+ self.lower_measurement(operation)
148
+
149
+ def process_cz_pauli_error(
150
+ self,
151
+ participants: Tuple[Tuple[int] | Tuple[int, int], ...],
152
+ node: schema.CZError[schema.PauliErrorModel],
153
+ ):
154
+
155
+ storage_error = node.storage_error
156
+ single_error = node.single_error
157
+ entangled_error = node.entangled_error
158
+
159
+ assert isinstance(
160
+ storage_error, schema.PauliErrorModel
161
+ ), "Only PauliErrorModel is supported"
162
+ assert isinstance(
163
+ single_error, schema.PauliErrorModel
164
+ ), "Only PauliErrorModel is supported"
165
+ assert isinstance(
166
+ entangled_error, schema.PauliErrorModel
167
+ ), "Only PauliErrorModel is supported"
168
+
169
+ self.lower_pauli_errors(storage_error)
170
+
171
+ single_error_dict = dict(single_error.errors)
172
+ entangled_error_dict = dict(entangled_error.errors)
173
+
174
+ single_layers = {}
175
+ paired_layers = {}
176
+ unpaired_layers = {}
177
+
178
+ for participant in participants:
179
+ if len(participant) == 1:
180
+ p_single = single_error_dict[participant[0]]
181
+ single_layers.setdefault(p_single, []).append(participant[0])
182
+ elif len(participant) == 2:
183
+ p_ctrl = entangled_error_dict[participant[0]]
184
+ up_ctrl = single_error_dict[participant[0]]
185
+ p_qarg = entangled_error_dict[participant[1]]
186
+ up_qarg = single_error_dict[participant[1]]
187
+ paired_layers.setdefault((p_ctrl, p_qarg), []).append(participant)
188
+ unpaired_layers.setdefault((up_ctrl, up_qarg), []).append(participant)
189
+
190
+ for (px, py, pz), qubits in single_layers.items():
191
+ self.block_list.append(
192
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qubits))
193
+ )
194
+ self.block_list.append(
195
+ noise.native.PauliChannel(px=px, py=py, pz=pz, qargs=qargs.result)
196
+ )
197
+
198
+ for (p_ctrl, p_qarg), qubits in paired_layers.items():
199
+ ctrls, qargs = list(zip(*qubits))
200
+ self.block_list.append(
201
+ ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
202
+ )
203
+ self.block_list.append(
204
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
205
+ )
206
+ self.block_list.append(
207
+ noise.native.CZPauliChannel(
208
+ paired=True,
209
+ px_ctrl=p_ctrl[0],
210
+ py_ctrl=p_ctrl[1],
211
+ pz_ctrl=p_ctrl[2],
212
+ px_qarg=p_qarg[0],
213
+ py_qarg=p_qarg[1],
214
+ pz_qarg=p_qarg[2],
215
+ ctrls=ctrls.result,
216
+ qargs=qargs.result,
217
+ )
218
+ )
219
+
220
+ for (p_ctrl, p_qarg), qubits in unpaired_layers.items():
221
+ ctrls, qargs = list(zip(*qubits))
222
+ self.block_list.append(
223
+ ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
224
+ )
225
+ self.block_list.append(
226
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
227
+ )
228
+ self.block_list.append(
229
+ noise.native.CZPauliChannel(
230
+ paired=False,
231
+ px_ctrl=p_ctrl[0],
232
+ py_ctrl=p_ctrl[1],
233
+ pz_ctrl=p_ctrl[2],
234
+ px_qarg=p_qarg[0],
235
+ py_qarg=p_qarg[1],
236
+ pz_qarg=p_qarg[2],
237
+ ctrls=ctrls.result,
238
+ qargs=qargs.result,
239
+ )
240
+ )
241
+
242
+ def lower_cz_gates(self, node: schema.CZ):
243
+ ctrls, qargs = list(zip(*(p for p in node.participants if len(p) == 2)))
244
+ self.block_list.append(
245
+ ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
246
+ )
247
+ self.block_list.append(
248
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
249
+ )
250
+ self.block_list.append(parallel.CZ(ctrls=ctrls.result, qargs=qargs.result))
251
+
252
+ def lower_w_gates(self, participants: Sequence[int], theta: float, phi: float):
253
+ self.block_list.append(
254
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in participants))
255
+ )
256
+ self.block_list.append(
257
+ parallel.UGate(
258
+ theta=self.lower_full_turns(theta),
259
+ phi=self.lower_full_turns(phi + 0.5),
260
+ lam=self.lower_full_turns(-(0.5 + phi)),
261
+ qargs=qargs.result,
262
+ )
263
+ )
264
+
265
+ def lower_rz_gates(self, participants: Tuple[int, ...], phi: float):
266
+ self.block_list.append(
267
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in participants))
268
+ )
269
+ self.block_list.append(
270
+ parallel.RZ(theta=self.lower_full_turns(phi), qargs=qargs.result)
271
+ )
272
+
273
+ def lower_pauli_errors(self, operator_error: schema.PauliErrorModel):
274
+ assert isinstance(
275
+ operator_error, schema.PauliErrorModel
276
+ ), "Only PauliErrorModel is supported"
277
+
278
+ layers = {}
279
+
280
+ for qubit_num, pauli_errors in operator_error.errors:
281
+ layers.setdefault(pauli_errors, []).append(qubit_num)
282
+
283
+ for (px, py, pz), qubits in layers.items():
284
+ self.block_list.append(
285
+ qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qubits))
286
+ )
287
+ self.block_list.append(
288
+ noise.native.PauliChannel(px=px, py=py, pz=pz, qargs=qargs.result)
289
+ )
290
+
291
+ def lower_measurement(self, operation: schema.Measurement):
292
+ for participant in operation.participants:
293
+ qubit = self.qubit_id_map[participant]
294
+ bit = self.bit_id_map[participant]
295
+ self.block_list.append(qasm2.core.Measure(qarg=qubit, carg=bit))
296
+
297
+ def lower_atom_loss(self, survival_probs: Tuple[float, ...]):
298
+ layers = {}
299
+
300
+ for qubit_num, survival_prob in zip(self.qubit_list, survival_probs):
301
+ layers.setdefault(survival_prob, []).append(qubit_num)
302
+
303
+ for survival_prob, qubits in layers.items():
304
+ self.block_list.append(qargs := ilist.New(values=qubits))
305
+ self.block_list.append(
306
+ noise.native.AtomLossChannel(prob=survival_prob, qargs=qargs.result)
307
+ )
308
+
309
+ def lower_number(self, value: float | int) -> ir.SSAValue:
310
+ if isinstance(value, int):
311
+ stmt = qasm2.expr.ConstInt(value=value)
312
+ else:
313
+ stmt = qasm2.expr.ConstFloat(value=value)
314
+
315
+ self.block_list.append(stmt)
316
+ return stmt.result
317
+
318
+ def lower_full_turns(self, value: float) -> ir.SSAValue:
319
+ const_pi = qasm2.expr.ConstPI()
320
+ self.block_list.append(const_pi)
321
+ turns = self.lower_number(2 * value)
322
+ mul = qasm2.expr.Mul(const_pi.result, turns)
323
+ self.block_list.append(mul)
324
+ return mul.result
@@ -0,0 +1,252 @@
1
+ from typing import List, Tuple, Union, Generic, Literal, TypeVar
2
+
3
+ from pydantic import Field, BaseModel
4
+
5
+
6
+ class Operation(BaseModel, frozen=True, extra="forbid"):
7
+ """Base class for operations."""
8
+
9
+ op_type: str = Field(init=False)
10
+
11
+
12
+ class CZ(Operation):
13
+ """A CZ gate operation.
14
+
15
+ Fields:
16
+ op_type (str): The type of operation (Literal["CZ"]).
17
+ participants (Tuple[Union[Tuple[int], Tuple[int, int]], ...]): The qubit indices that are participating in the CZ gate.
18
+
19
+ """
20
+
21
+ op_type: Literal["CZ"] = Field(init=False, default="CZ")
22
+ participants: Tuple[Union[Tuple[int], Tuple[int, int]], ...]
23
+
24
+
25
+ class GlobalRz(Operation):
26
+ """GlobalRz operation.
27
+
28
+ Fields:
29
+ op_type (str): The type of operation (Literal["GlobalRz"]).
30
+ phi (float): The angle of rotation.
31
+ """
32
+
33
+ op_type: Literal["GlobalRz"] = Field(init=False, default="GlobalRz")
34
+ phi: float
35
+
36
+
37
+ class GlobalW(Operation):
38
+ """GlobalW operation.
39
+
40
+ Fields:
41
+ op_type (str): The type of operation (Literal["GlobalW"]).
42
+ theta (float): The angle of rotation.
43
+ phi (float): The angle of rotation.
44
+ """
45
+
46
+ op_type: Literal["GlobalW"] = Field(init=False, default="GlobalW")
47
+ theta: float
48
+ phi: float
49
+
50
+
51
+ class LocalRz(Operation):
52
+ """LocalRz operation.
53
+
54
+ Fields:
55
+ op_type (str): The type of operation (Literal["LocalRz"]).
56
+ participants (Tuple[int, ...]): The qubit indices that are participating in the local Rz gate.
57
+ phi (float): The angle of rotation.
58
+
59
+ """
60
+
61
+ op_type: Literal["LocalRz"] = Field(init=False, default="LocalRz")
62
+ participants: Tuple[int, ...]
63
+ phi: float
64
+
65
+
66
+ class LocalW(Operation):
67
+ """LocalW operation.
68
+
69
+ Fields:
70
+ op_type (str): The type of operation (Literal["LocalW"]).
71
+ participants (Tuple[int, ...]): The qubit indices that are participating in the local W gate.
72
+ theta (float): The angle of rotation.
73
+ phi (float): The angle of rotation.
74
+
75
+ """
76
+
77
+ op_type: Literal["LocalW"] = Field(init=False, default="LocalW")
78
+ participants: Tuple[int, ...]
79
+ theta: float
80
+ phi: float
81
+
82
+
83
+ class Measurement(Operation):
84
+ """Measurement operation.
85
+
86
+ Fields:
87
+ op_type (str): The type of operation (Literal["Measurement"]).
88
+ measure_tag (str): The tag to use for the measurement.
89
+ participants (Tuple[int, ...]): The qubit indices that are participating in the measurement.
90
+
91
+ """
92
+
93
+ op_type: Literal["Measurement"] = Field(init=False, default="Measurement")
94
+ measure_tag: str = Field(default="m")
95
+ participants: Tuple[int, ...]
96
+
97
+
98
+ OperationType = TypeVar(
99
+ "OperationType", bound=Union[CZ, GlobalRz, GlobalW, LocalRz, LocalW, Measurement]
100
+ )
101
+
102
+
103
+ class ErrorModel(BaseModel, frozen=True, extra="forbid"):
104
+ """Base class for error models."""
105
+
106
+ error_model_type: str = Field(init=False)
107
+
108
+
109
+ class PauliErrorModel(ErrorModel):
110
+ """Pauli error model.
111
+
112
+ Fields:
113
+ error_model_type (str): The type of error model (Literal["PauliNoise"]).
114
+ errors (Tuple[Tuple[int, Tuple[float, float, float]], ...]): The qubit indices and the error rates for each qubit.
115
+
116
+ """
117
+
118
+ error_model_type: Literal["PauliNoise"] = Field(default="PauliNoise", init=False)
119
+ errors: Tuple[Tuple[int, Tuple[float, float, float]], ...] = Field(
120
+ default_factory=tuple
121
+ )
122
+
123
+
124
+ ErrorModelType = TypeVar("ErrorModelType", bound=PauliErrorModel)
125
+
126
+
127
+ class ErrorOperation(BaseModel, Generic[ErrorModelType], frozen=True, extra="forbid"):
128
+ """Base class for error operations."""
129
+
130
+ error_type: str = Field(init=False)
131
+ survival_prob: Tuple[float, ...]
132
+
133
+
134
+ class CZError(ErrorOperation[ErrorModelType]):
135
+ """CZError operation.
136
+
137
+ Fields:
138
+ survival_prob (Tuple[float, ...]): The survival probabilities for each qubit.
139
+ error_type (str): The type of error (Literal["CZError"]).
140
+ storage_error (ErrorModelType): The error model for storage.
141
+ entangled_error (ErrorModelType): The error model for entangled qubits.
142
+ single_error (ErrorModelType): The error model for single qubits.
143
+
144
+ """
145
+
146
+ error_type: Literal["CZError"] = Field(default="CZError", init=False)
147
+ storage_error: ErrorModelType
148
+ entangled_error: ErrorModelType
149
+ single_error: ErrorModelType
150
+
151
+
152
+ class SingleQubitError(ErrorOperation[ErrorModelType]):
153
+ """SingleQubitError operation.
154
+
155
+ Fields:
156
+ survival_prob (Tuple[float, ...]): The survival probabilities for each qubit.
157
+ error_type (str): The type of error (Literal["SingleQubitError"]).
158
+ operator_error (ErrorModelType): The error model for the single qubit.
159
+
160
+ """
161
+
162
+ error_type: Literal["SingleQubitError"] = Field(
163
+ default="SingleQubitError", init=False
164
+ )
165
+ operator_error: ErrorModelType
166
+
167
+
168
+ class GateEvent(BaseModel, Generic[ErrorModelType], frozen=True, extra="forbid"):
169
+ """A gate event.
170
+
171
+ Fields:
172
+ error (Union[SingleQubitError[ErrorModelType], CZError[ErrorModelType]]): The error model for the gate event.
173
+ operation (OperationType): The operation for the gate event.
174
+
175
+ """
176
+
177
+ error: Union[SingleQubitError[ErrorModelType], CZError[ErrorModelType]] = Field(
178
+ union_mode="left_to_right", discriminator="error_type"
179
+ )
180
+ operation: OperationType = Field(
181
+ union_mode="left_to_right", discriminator="op_type"
182
+ )
183
+
184
+ def __pydantic_post_init__(self):
185
+ assert (isinstance(self.operation, CZ) and isinstance(self.error, CZError)) or (
186
+ not isinstance(self.operation, CZ)
187
+ and isinstance(self.error, SingleQubitError)
188
+ ), "Operation and error must be of the same type"
189
+
190
+
191
+ class NoiseModel(BaseModel, Generic[ErrorModelType], extra="forbid"):
192
+ """Noise model for a circuit.
193
+
194
+ Fields:
195
+ all_qubits (Tuple[int, ...]): The qubit indices for the noise model.
196
+ gate_events (List[GateEvent[ErrorModelType]]): The gate events for the noise model.
197
+
198
+ """
199
+
200
+ all_qubits: Tuple[int, ...] = Field(default_factory=tuple)
201
+ gate_events: List[GateEvent[ErrorModelType]] = Field(default_factory=list)
202
+
203
+ @property
204
+ def num_qubits(self) -> int:
205
+ """Return the number of qubits in the noise model."""
206
+ return len(self.all_qubits)
207
+
208
+ def __add__(self, other: "NoiseModel") -> "NoiseModel":
209
+ if not isinstance(other, NoiseModel):
210
+ raise ValueError(f"Cannot add {type(other)} to Circuit")
211
+
212
+ if self.all_qubits != other.all_qubits:
213
+ raise ValueError("Circuits must have the same number of qubits")
214
+
215
+ return NoiseModel(
216
+ all_qubits=self.all_qubits,
217
+ gate_events=self.gate_events + other.gate_events,
218
+ )
219
+
220
+ def lower_noise_model(self, sym_name: str, return_qreg: bool = False):
221
+ """Lower the noise model to a method.
222
+
223
+ Args:
224
+ sym_name (str): The name of the method to generate.
225
+ return_qreg (bool): Whether to return the quantum register after the method
226
+ has completed execution. Useful for obtaining the full state vector.
227
+
228
+ Returns:
229
+ Method: The generated kirin method.
230
+
231
+ """
232
+ from bloqade.qbraid.lowering import Lowering
233
+
234
+ return Lowering().lower(sym_name, self, return_qreg)
235
+
236
+ def decompiled_circuit(self) -> str:
237
+ """Clean the circuit of noise.
238
+
239
+ Returns:
240
+ str: The decompiled circuit from hardware execution.
241
+
242
+ """
243
+ from bloqade.noise import native
244
+ from bloqade.qasm2.emit import QASM2
245
+ from bloqade.qasm2.passes import glob, parallel
246
+
247
+ mt = self.lower_noise_model("method")
248
+
249
+ native.RemoveNoisePass(mt.dialects)(mt)
250
+ parallel.ParallelToUOp(mt.dialects)(mt)
251
+ glob.GlobalToUOP(mt.dialects)(mt)
252
+ return QASM2(qelib1=True).emit_str(mt)
@@ -0,0 +1,99 @@
1
+ # Copyright (c) 2024, QuEra Computing Inc.
2
+ # All rights reserved.
3
+
4
+ from io import StringIO
5
+ from typing import Any, Dict, Optional
6
+ from dataclasses import dataclass
7
+
8
+ import pandas as pd
9
+ from pandas import DataFrame
10
+
11
+ from bloqade.visual.animation.runtime import qpustate as vis_qpustate
12
+
13
+ from .schema import NoiseModel
14
+
15
+
16
+ @dataclass
17
+ class QuEraSimulationResult:
18
+ """Results of the QuEra hardware model simulation.
19
+
20
+ Fields:
21
+ flair_visual_version (str): The version of the Flair Visual package used to generate the simulation result.
22
+ counts (dict[str, int]): The measurement bitstrings of the simulation.
23
+ logs (DataFrame): Grainular logs events of what happened to each atom during the simulation.
24
+ atom_animation_state (vis_qpustate.AnimateQPUState): Object used to play back atom trajectories and events during the simulation.
25
+ noise_model (NoiseModel): The noise model used in the simulation.
26
+
27
+ """
28
+
29
+ flair_visual_version: str
30
+ counts: dict[str, int]
31
+ logs: DataFrame
32
+ atom_animation_state: vis_qpustate.AnimateQPUState
33
+ noise_model: NoiseModel
34
+
35
+ @classmethod
36
+ def from_json(cls, json: dict) -> "QuEraSimulationResult":
37
+ """deserialize the object from a JSON serializable dictionary."""
38
+ flair_visual_version = json["flair_visual_version"]
39
+ counts = json["counts"]
40
+ logs = pd.read_csv(StringIO(json["logs"]), index_col=0)
41
+ atom_animation_state = vis_qpustate.AnimateQPUState.from_json(
42
+ json["atom_animation_state"]
43
+ )
44
+ noise_model = NoiseModel(**json["noise_model"])
45
+
46
+ return cls(
47
+ flair_visual_version=flair_visual_version,
48
+ counts=counts,
49
+ logs=logs,
50
+ atom_animation_state=atom_animation_state,
51
+ noise_model=noise_model,
52
+ )
53
+
54
+ def to_json(self) -> Dict[str, Any]:
55
+ """Turn the object into a JSON serializable dictionary."""
56
+ return {
57
+ "flair_visual_version": self.flair_visual_version,
58
+ "counts": self.counts,
59
+ "logs": self.logs.to_csv(),
60
+ "atom_animation_state": self.atom_animation_state.to_json(),
61
+ "noise_model": self.noise_model.model_dump(mode="json"),
62
+ }
63
+
64
+ def animate(
65
+ self,
66
+ dilation_rate: float = 0.05,
67
+ fps: int = 30,
68
+ gate_display_dilation: float = 1.0,
69
+ save_mpeg: bool = False,
70
+ filename: str = "vqpu_animation",
71
+ start_block: int = 0,
72
+ n_blocks: Optional[int] = None,
73
+ ):
74
+ """animate the qpu state
75
+
76
+ Args:
77
+ dilation_rate (float): Conversion factor from the qpu time to animation time units. when dilation_rate=1.0, 1 (us) of qpu exec time corresponds to 1 second of animation time.
78
+ fps (int, optional): frame per second. Defaults to 30.
79
+ gate_display_dilation (float, optional): relative dilation rate of a gate event. Defaults to 1. When setting higher value, the gate event will be displayed longer.
80
+ save_mpeg (bool, optional): Save as mpeg. Defaults to False.
81
+ filename (str, optional): The file name of saved mpeg file. Defaults to "vqpu_animation". When `save_mpeg` is False, this argument is ignored.
82
+ start_block (int, optional): The start block to animate. Defaults to 0.
83
+ n_blocks (int, optional): number of blocks to animate. Defaults to None. When None, animate all blocks after `start_block`.
84
+ Returns:
85
+ ani: matplotlib animation object
86
+ """
87
+ from bloqade.visual.animation.animate import animate_qpu_state
88
+
89
+ ani = animate_qpu_state(
90
+ state=self.atom_animation_state,
91
+ dilation_rate=dilation_rate,
92
+ fps=fps,
93
+ gate_display_dilation=gate_display_dilation,
94
+ start_block=start_block,
95
+ n_blocks=n_blocks,
96
+ save_mpeg=save_mpeg,
97
+ filename=filename,
98
+ )
99
+ return ani