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,346 @@
1
+ import itertools
2
+ import dataclasses
3
+ from abc import abstractmethod
4
+ from enum import Enum
5
+ from typing import Any, Dict, List, Tuple, Callable, Optional
6
+
7
+ import matplotlib.patches as mpatches
8
+ import matplotlib.collections as mcollections
9
+
10
+ from .gate_event import GateEvent
11
+
12
+ _gate_color_code = {
13
+ "GlobalCZGate": "tab:red",
14
+ "TopHatCZGate": "tab:red",
15
+ "GlobalHyperfineRotation": "tab:blue",
16
+ "GlobalHyperfineZRotation": "tab:green",
17
+ "LocalHyperfineZRotation": "tab:green",
18
+ "LocalHyperfineZRotationPos": "tab:green",
19
+ "LocalHyperfineRotation": "tab:blue",
20
+ "LocalHyperfineRotationPos": "tab:blue",
21
+ }
22
+
23
+
24
+ class quera_color_code(str, Enum):
25
+ # TODO: for python 3.11+, replace traits with StrEnum
26
+ purple = "#6437FF"
27
+ red = "#C2477F"
28
+ yellow = "#EADD08"
29
+
30
+
31
+ @dataclasses.dataclass
32
+ class GateArtist:
33
+ mpl_ax: Any
34
+
35
+ @abstractmethod
36
+ def clear_data(self) -> None:
37
+ pass
38
+
39
+ @abstractmethod
40
+ def get_artists(self) -> Tuple[Any]:
41
+ pass
42
+
43
+
44
+ @dataclasses.dataclass(init=False)
45
+ class GlobalGateArtist(GateArtist):
46
+ mpl_obj: mpatches.Rectangle
47
+
48
+ def __init__(self, mpl_ax: Any, xmin, ymin, width, height, color):
49
+ super().__init__(mpl_ax)
50
+ rc = mpatches.Rectangle(
51
+ [xmin, ymin], width, height, color=color, alpha=0.6, visible=False
52
+ )
53
+ mpl_ax.add_patch(rc)
54
+ self.mpl_obj = rc
55
+
56
+ def clear_data(self) -> None:
57
+ self.mpl_obj.set_width(0)
58
+ self.mpl_obj.set_height(0)
59
+ self.mpl_obj.set_xy([0, 0])
60
+
61
+ def get_artists(self) -> Tuple[Any]:
62
+ return (self.mpl_obj,)
63
+
64
+ def set_visible(self, visible: bool):
65
+ self.mpl_obj.set_visible(visible)
66
+
67
+
68
+ @dataclasses.dataclass(init=False)
69
+ class RowRegionGateArtist(GateArtist):
70
+ """
71
+ A row region gate artist object.
72
+
73
+ bound box is [y_origin - width/2, y_origin + width/2]
74
+ """
75
+
76
+ mpl_obj: mpatches.Rectangle
77
+ mpl_obj_keepout_top: mpatches.Rectangle
78
+ mpl_obj_keepout_btm: mpatches.Rectangle
79
+ width: float
80
+ xmin: float
81
+
82
+ def __init__(
83
+ self, mpl_ax: Any, xmin, width, ymin, ymin_keepout, ymax, ymax_keepout, color
84
+ ):
85
+ super().__init__(mpl_ax)
86
+ self.width = width
87
+ self.xmin = xmin
88
+ rc_btm = mpatches.Rectangle(
89
+ [xmin, ymin_keepout],
90
+ width,
91
+ ymin - ymin_keepout,
92
+ color=color,
93
+ alpha=0.3,
94
+ visible=False,
95
+ )
96
+ mpl_ax.add_patch(rc_btm)
97
+ self.mpl_obj_keepout_btm = rc_btm
98
+
99
+ rc = mpatches.Rectangle(
100
+ [xmin, ymin], width, ymax - ymin, color=color, alpha=0.6, visible=False
101
+ )
102
+ mpl_ax.add_patch(rc)
103
+ self.mpl_obj = rc
104
+
105
+ rc_top = mpatches.Rectangle(
106
+ [xmin, ymax],
107
+ width,
108
+ ymax_keepout - ymax,
109
+ color=color,
110
+ alpha=0.3,
111
+ visible=False,
112
+ )
113
+ mpl_ax.add_patch(rc_top)
114
+ self.mpl_obj_keepout_top = rc_top
115
+
116
+ def clear_data(self) -> None:
117
+ self.mpl_obj.set_width(0)
118
+ self.mpl_obj.set_height(0)
119
+ self.mpl_obj.set_xy([0, 0])
120
+
121
+ self.mpl_obj_keepout_top.set_width(0)
122
+ self.mpl_obj_keepout_top.set_height(0)
123
+ self.mpl_obj_keepout_top.set_xy([0, 0])
124
+
125
+ self.mpl_obj_keepout_btm.set_width(0)
126
+ self.mpl_obj_keepout_btm.set_height(0)
127
+ self.mpl_obj_keepout_btm.set_xy([0, 0])
128
+
129
+ def get_artists(self) -> Tuple[Any]:
130
+ return (self.mpl_obj, self.mpl_obj_keepout_top, self.mpl_obj_keepout_btm)
131
+
132
+ def update_data(self, ymin, ymax, ymin_keepout, ymax_keepout):
133
+ self.mpl_obj.set_height(ymax - ymin)
134
+ self.mpl_obj.set_width(self.width)
135
+ self.mpl_obj.set_xy([self.xmin, ymin])
136
+
137
+ self.mpl_obj_keepout_top.set_height(ymax_keepout - ymax)
138
+ self.mpl_obj_keepout_top.set_width(self.width)
139
+ self.mpl_obj_keepout_top.set_xy([self.xmin, ymax])
140
+
141
+ self.mpl_obj_keepout_btm.set_height(ymin - ymin_keepout)
142
+ self.mpl_obj_keepout_btm.set_width(self.width)
143
+ self.mpl_obj_keepout_btm.set_xy([self.xmin, ymin_keepout])
144
+
145
+ def set_visible(self, visible: bool):
146
+ self.mpl_obj.set_visible(visible)
147
+ self.mpl_obj_keepout_btm.set_visible(visible)
148
+ self.mpl_obj_keepout_top.set_visible(visible)
149
+
150
+
151
+ @dataclasses.dataclass
152
+ class SpotGateArtist(GateArtist):
153
+ mpl_obj: mcollections.PathCollection
154
+
155
+ def __init__(self, mpl_ax: Any, xy: List[Tuple[float, float]], radius, color):
156
+ super().__init__(mpl_ax)
157
+ self.mpl_obj = mpl_ax.scatter(
158
+ [x for x, y in xy],
159
+ [y for x, y in xy],
160
+ s=radius,
161
+ visible=False,
162
+ marker="o",
163
+ facecolors=color,
164
+ alpha=0.5,
165
+ zorder=+100,
166
+ )
167
+
168
+ def clear_data(self) -> None:
169
+ self.mpl_obj.set_offsets([])
170
+
171
+ def get_artists(self) -> Tuple[Any]:
172
+ return (self.mpl_obj,)
173
+
174
+ def update_data(self, xy: List[Tuple[float, float]]):
175
+ self.mpl_obj.set_offsets(xy)
176
+
177
+ def set_visible(self, visible: bool):
178
+ self.mpl_obj.set_visible(visible)
179
+
180
+
181
+ @dataclasses.dataclass
182
+ class FieldOfView:
183
+ xmin: Optional[float] = dataclasses.field(default=None)
184
+ xmax: Optional[float] = dataclasses.field(default=None)
185
+ ymin: Optional[float] = dataclasses.field(default=None)
186
+ ymax: Optional[float] = dataclasses.field(default=None)
187
+
188
+ def not_defined(self):
189
+ return (
190
+ self.xmin is None
191
+ or self.xmax is None
192
+ or self.ymin is None
193
+ or self.ymax is None
194
+ )
195
+
196
+ @property
197
+ def width(self):
198
+ return self.xmax - self.xmin
199
+
200
+ @property
201
+ def height(self):
202
+ return self.ymax - self.ymin
203
+
204
+ def to_json(self):
205
+ return {
206
+ "xmin": self.xmin,
207
+ "xmax": self.xmax,
208
+ "ymin": self.ymin,
209
+ "ymax": self.ymax,
210
+ }
211
+
212
+ @classmethod
213
+ def from_json(cls, json_dict):
214
+ return FieldOfView(
215
+ xmin=json_dict["xmin"],
216
+ xmax=json_dict["xmax"],
217
+ ymin=json_dict["ymin"],
218
+ ymax=json_dict["ymax"],
219
+ )
220
+
221
+
222
+ @dataclasses.dataclass(init=False)
223
+ class GatePainter:
224
+ artist_objs: Dict[str, GateArtist]
225
+ artist_methods: Dict[str, Callable]
226
+
227
+ def __init__(self, mpl_ax, qpu_fov: FieldOfView, scale: float = 1.0):
228
+ # gates:
229
+ self.artist_objs = {}
230
+ self.artist_methods = {
231
+ "GlobalCZGate": self.process_global_cz_gate,
232
+ "GlobalHyperfineRotation": self.process_global_hyperfine_rotation,
233
+ "TopHatCZGate": self.process_tophat_cz_gate,
234
+ "LocalHyperfineZRotation": self.process_local_hyperfine_z_rotation,
235
+ "LocalHyperfineZRotationPos": self.process_local_hyperfine_z_rotation_pos,
236
+ "LocalHyperfineRotation": self.process_local_hyperfine_rotation,
237
+ "LocalHyperfineRotationPos": self.process_local_hyperfine_rotation_pos,
238
+ }
239
+ self.artist_objs["GlobalCZGate"] = GlobalGateArtist(
240
+ mpl_ax=mpl_ax,
241
+ xmin=qpu_fov.xmin,
242
+ width=qpu_fov.width,
243
+ ymin=qpu_fov.ymin,
244
+ height=qpu_fov.height,
245
+ color=_gate_color_code["GlobalCZGate"],
246
+ )
247
+ self.artist_objs["GlobalHyperfineRotation"] = GlobalGateArtist(
248
+ mpl_ax=mpl_ax,
249
+ xmin=qpu_fov.xmin,
250
+ width=qpu_fov.width,
251
+ ymin=qpu_fov.ymin,
252
+ height=qpu_fov.height,
253
+ color=_gate_color_code["GlobalHyperfineRotation"],
254
+ )
255
+ self.artist_objs["TopHatCZGate"] = RowRegionGateArtist(
256
+ mpl_ax=mpl_ax,
257
+ xmin=qpu_fov.xmin,
258
+ width=qpu_fov.width,
259
+ ymin=0,
260
+ ymin_keepout=0,
261
+ ymax=0,
262
+ ymax_keepout=0,
263
+ color=_gate_color_code["TopHatCZGate"],
264
+ )
265
+
266
+ self.artist_objs["LocalHyperfineZRotation"] = SpotGateArtist(
267
+ mpl_ax=mpl_ax,
268
+ xy=[],
269
+ radius=160 * scale,
270
+ color=_gate_color_code["LocalHyperfineZRotation"],
271
+ )
272
+ self.artist_objs["LocalHyperfineZRotationPos"] = SpotGateArtist(
273
+ mpl_ax=mpl_ax,
274
+ xy=[],
275
+ radius=160 * scale,
276
+ color=_gate_color_code["LocalHyperfineZRotationPos"],
277
+ )
278
+
279
+ self.artist_objs["LocalHyperfineRotation"] = SpotGateArtist(
280
+ mpl_ax=mpl_ax,
281
+ xy=[],
282
+ radius=80,
283
+ color=_gate_color_code["LocalHyperfineRotation"],
284
+ )
285
+ self.artist_objs["LocalHyperfineRotationPos"] = SpotGateArtist(
286
+ mpl_ax=mpl_ax,
287
+ xy=[],
288
+ radius=80,
289
+ color=_gate_color_code["LocalHyperfineRotationPos"],
290
+ )
291
+
292
+ @staticmethod
293
+ def process_global_cz_gate(artist_obj, **kwargs) -> None:
294
+ artist_obj.set_visible(True)
295
+
296
+ @staticmethod
297
+ def process_global_hyperfine_rotation(artist_obj, **kwargs) -> None:
298
+ artist_obj.set_visible(True)
299
+
300
+ @staticmethod
301
+ def process_tophat_cz_gate(artist_obj, **kwargs) -> None:
302
+ artist_obj.update_data(
303
+ ymin=kwargs["y_min"],
304
+ ymax=kwargs["y_max"],
305
+ ymin_keepout=kwargs["y_min_keepout"],
306
+ ymax_keepout=kwargs["y_max_keepout"],
307
+ )
308
+ artist_obj.set_visible(True)
309
+
310
+ @staticmethod
311
+ def process_local_hyperfine_z_rotation_pos(artist_obj, **kwargs) -> None:
312
+ artist_obj.update_data(xy=[(x, kwargs["y_row_loc"]) for x in kwargs["varargs"]])
313
+ artist_obj.set_visible(True)
314
+
315
+ @staticmethod
316
+ def process_local_hyperfine_rotation_pos(artist_obj, **kwargs) -> None:
317
+ artist_obj.update_data(xy=[(x, kwargs["y_row_loc"]) for x in kwargs["varargs"]])
318
+ artist_obj.set_visible(True)
319
+
320
+ @staticmethod
321
+ def process_local_hyperfine_z_rotation(artist_obj, **kwargs) -> None:
322
+ raise ValueError(
323
+ "cannot have local HyperfineZRotation, it should be already lowered to Pos variant!"
324
+ )
325
+
326
+ @staticmethod
327
+ def process_local_hyperfine_rotation(artist_obj, **kwargs) -> None:
328
+ raise ValueError(
329
+ "cannot have local HyperfineRotation, it should be already lowered to Pos variant!"
330
+ )
331
+
332
+ def process_gates(self, gates: List[GateEvent]) -> List:
333
+
334
+ for gate_artist in self.artist_objs.values():
335
+ gate_artist.set_visible(False)
336
+
337
+ for gate in gates:
338
+ self.artist_methods[gate.cls_name](
339
+ self.artist_objs[gate.cls_name], **gate.kwargs
340
+ )
341
+
342
+ return list(
343
+ itertools.chain.from_iterable(
344
+ gate_artist.get_artists() for gate_artist in self.artist_objs.values()
345
+ )
346
+ )
@@ -0,0 +1,24 @@
1
+ import dataclasses
2
+ from typing import Any, Dict
3
+
4
+
5
+ @dataclasses.dataclass(frozen=True)
6
+ class GateEvent:
7
+ cls_name: str
8
+ kwargs: Dict[str, Any]
9
+ duration: float
10
+
11
+ def to_json(self) -> Dict[str, Any]:
12
+ return {
13
+ "cls_name": self.cls_name,
14
+ "kwargs": self.kwargs,
15
+ "duration": self.duration,
16
+ }
17
+
18
+ @classmethod
19
+ def from_json(cls, json_dict: Dict[str, Any]) -> "GateEvent":
20
+ return cls(
21
+ json_dict["cls_name"],
22
+ json_dict["kwargs"],
23
+ json_dict["duration"],
24
+ )
File without changes
@@ -0,0 +1,36 @@
1
+ import dataclasses
2
+ from typing import Any, Dict, Tuple
3
+
4
+ from .ppoly import PPoly
5
+
6
+
7
+ @dataclasses.dataclass(frozen=True)
8
+ class AODMoveEvent:
9
+ time: float
10
+ duration: float
11
+ x: PPoly
12
+ y: PPoly
13
+
14
+ def __post_init__(self):
15
+ assert isinstance(self.x, PPoly), "x must be a PPoly instance"
16
+ assert isinstance(self.y, PPoly), "y must be a PPoly instance"
17
+
18
+ def sample(self, t: float) -> Tuple[float, float]:
19
+ return (self.x(t - self.time), self.y(t - self.time))
20
+
21
+ def to_json(self) -> Dict[str, Any]:
22
+ return {
23
+ "time": self.time,
24
+ "duration": self.duration,
25
+ "x": self.x.to_json(),
26
+ "y": self.y.to_json(),
27
+ }
28
+
29
+ @classmethod
30
+ def from_json(cls, json_dict: Dict[str, Any]) -> "AODMoveEvent":
31
+ return cls(
32
+ json_dict["time"],
33
+ json_dict["duration"],
34
+ PPoly.from_json(json_dict["x"]),
35
+ PPoly.from_json(json_dict["y"]),
36
+ )
@@ -0,0 +1,55 @@
1
+ import dataclasses
2
+ from typing import List, Tuple
3
+ from functools import cached_property
4
+
5
+ import numpy as np
6
+
7
+ from .ppoly import PPoly
8
+
9
+
10
+ @dataclasses.dataclass(frozen=True)
11
+ class AtomTrajectory:
12
+ id: int
13
+ x: PPoly
14
+ y: PPoly
15
+ events: List[Tuple[float, str]] # (time, event_type)
16
+
17
+ def __post_init__(self):
18
+ assert all(
19
+ isinstance(event, tuple) and len(event) == 2 for event in self.events
20
+ ), "All events must be (float, str) pairs"
21
+ assert isinstance(self.id, int), "id must be an int"
22
+ assert isinstance(self.x, PPoly), "x must be a PPoly instance"
23
+ assert isinstance(self.y, PPoly), "y must be a PPoly instance"
24
+
25
+ def position(self, t: float) -> Tuple[np.ndarray, np.ndarray]:
26
+ return (self.x(t), self.y(t))
27
+
28
+ @cached_property
29
+ def has_lost(self) -> bool:
30
+ return any(event_type == "Lost" for _, event_type in self.events)
31
+
32
+ def is_lost(self, time: float) -> bool:
33
+ if not self.has_lost:
34
+ return False
35
+
36
+ return (
37
+ self.events[-1][0] <= time
38
+ ) # TODO: maybe we need approx_eq to be separate package?
39
+
40
+ def to_json(self):
41
+ return {
42
+ "id": self.id,
43
+ "x": self.x.to_json(),
44
+ "y": self.y.to_json(),
45
+ "events": self.events,
46
+ }
47
+
48
+ @classmethod
49
+ def from_json(cls, json_dict):
50
+ return AtomTrajectory(
51
+ json_dict["id"],
52
+ PPoly.from_json(json_dict["x"]),
53
+ PPoly.from_json(json_dict["y"]),
54
+ list(map(tuple, json_dict["events"])),
55
+ )
@@ -0,0 +1,50 @@
1
+ import dataclasses
2
+ from typing import Any, Dict
3
+ from functools import cached_property
4
+
5
+ import numpy as np
6
+ import scipy.interpolate as interp
7
+
8
+ from .utils import array_to_json, json_to_array
9
+
10
+
11
+ @dataclasses.dataclass
12
+ class PPoly:
13
+ c: np.ndarray
14
+ x: np.ndarray
15
+ extrapolate: bool = dataclasses.field(default=False)
16
+
17
+ def __post_init__(self):
18
+ super().__setattr__("x", np.asarray(self.x))
19
+ super().__setattr__("c", np.asarray(self.c))
20
+
21
+ def __eq__(self, other: Any) -> bool:
22
+ if not isinstance(other, PPoly):
23
+ return False
24
+ return (
25
+ np.array_equal(self.x, other.x)
26
+ and np.array_equal(self.c, other.c)
27
+ and self.extrapolate == other.extrapolate
28
+ )
29
+
30
+ @cached_property
31
+ def get_ppoly(self) -> interp.PPoly:
32
+ return interp.PPoly(self.c, self.x, self.extrapolate)
33
+
34
+ def __call__(self, x: float) -> float:
35
+ return self.get_ppoly(x)
36
+
37
+ def to_json(self) -> Dict[str, Any]:
38
+ return {
39
+ "c": array_to_json(self.c),
40
+ "x": array_to_json(self.x),
41
+ "extrapolate": self.extrapolate,
42
+ }
43
+
44
+ @classmethod
45
+ def from_json(cls, json_dict: Dict[str, Any]) -> "PPoly":
46
+ return cls(
47
+ c=json_to_array(json_dict["c"]),
48
+ x=json_to_array(json_dict["x"]),
49
+ extrapolate=json_dict["extrapolate"],
50
+ )
@@ -0,0 +1,119 @@
1
+ import dataclasses
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Dict, List, Tuple
4
+
5
+ import numpy as np
6
+
7
+ from .aod import AODMoveEvent
8
+ from ..base import FieldOfView
9
+ from .atoms import AtomTrajectory
10
+ from ..gate_event import GateEvent
11
+
12
+
13
+ @dataclasses.dataclass
14
+ class QPUStateABC(ABC):
15
+
16
+ block_durations: List[float] = dataclasses.field(default_factory=list)
17
+ gate_events: List[Tuple[float, GateEvent]] = dataclasses.field(default_factory=list)
18
+ qpu_fov: FieldOfView = dataclasses.field(default_factory=FieldOfView)
19
+
20
+ def __post_init__(self):
21
+ assert all(
22
+ isinstance(gate, GateEvent) for _, gate in self.gate_events
23
+ ), "All gate events must be GateEvent instances"
24
+
25
+ @abstractmethod
26
+ def get_slm_sites(self) -> np.ndarray: ...
27
+
28
+ @abstractmethod
29
+ def sample_aod_traps(self, time: float) -> List[Tuple[float, float]]: ...
30
+
31
+ @abstractmethod
32
+ def get_atoms_lost_info(self, time: float) -> List[str]: ...
33
+
34
+ @abstractmethod
35
+ def get_atoms_position(
36
+ self, time: float, include_lost: bool = False
37
+ ) -> List[Tuple[int, Tuple[float, float]]]: ...
38
+
39
+ def get_gate_events_timing(self) -> List[Tuple[float, float]]:
40
+ # return [t_start, duration]
41
+ return [(t, gate.duration) for t, gate in self.gate_events]
42
+
43
+ def get_gate_events(self, time: float) -> List[Tuple[float, GateEvent]]:
44
+ out = []
45
+ if self.gate_events:
46
+ for t, gate in self.gate_events:
47
+ if t > time:
48
+ break
49
+ if t <= time < t + gate.duration:
50
+ out.append((t, gate))
51
+ return out
52
+
53
+
54
+ @dataclasses.dataclass
55
+ class AnimateQPUState(QPUStateABC):
56
+ atoms: List[AtomTrajectory] = dataclasses.field(default_factory=list)
57
+ slm_zone: List[Tuple[float, float]] = dataclasses.field(default_factory=list)
58
+ aod_moves: List[AODMoveEvent] = dataclasses.field(default_factory=list)
59
+
60
+ def __post_init__(self):
61
+ assert all(
62
+ isinstance(atom, AtomTrajectory) for atom in self.atoms
63
+ ), "All atoms must be AtomTrajectory instances"
64
+ assert all(
65
+ isinstance(aod, AODMoveEvent) for aod in self.aod_moves
66
+ ), "All AOD moves must be AODMoveEvent instances"
67
+ assert all(
68
+ isinstance(site, tuple) and len(site) == 2 for site in self.slm_zone
69
+ ), "All SLM sites must be tuples of length 2"
70
+
71
+ def get_slm_sites(self):
72
+ return np.array(self.slm_zone)
73
+
74
+ def sample_aod_traps(self, time: float) -> List[Tuple[float, float]]:
75
+ out = []
76
+ for aod_trap in self.aod_moves:
77
+ if aod_trap.time <= time < aod_trap.time + aod_trap.duration:
78
+ out.append(aod_trap.sample(time))
79
+
80
+ return out
81
+
82
+ def get_atoms_lost_info(self, time: float) -> List[str]:
83
+ return [
84
+ f"Lost: {atom.id} @{atom.events[-1][0]:.3f} (us)" + "\n"
85
+ for atom in self.atoms
86
+ if atom.is_lost(time)
87
+ ]
88
+
89
+ def get_atoms_position(
90
+ self, time: float, include_lost: bool = False
91
+ ) -> List[Tuple[int, Tuple[float, float]]]:
92
+ return [
93
+ (atom.id, atom.position(time))
94
+ for atom in self.atoms
95
+ if not atom.is_lost(time) or include_lost
96
+ ]
97
+
98
+ def to_json(self) -> Dict[str, Any]:
99
+ return {
100
+ "block_durations": self.block_durations,
101
+ "gate_events": [(t, gate.to_json()) for t, gate in self.gate_events],
102
+ "qpu_fov": self.qpu_fov.to_json(),
103
+ "atoms": [atom.to_json() for atom in self.atoms],
104
+ "slm_zone": self.slm_zone,
105
+ "aod_moves": [aod.to_json() for aod in self.aod_moves],
106
+ }
107
+
108
+ @classmethod
109
+ def from_json(cls, json_dict: Dict[str, Any]) -> "AnimateQPUState":
110
+ return cls(
111
+ block_durations=json_dict["block_durations"],
112
+ gate_events=[
113
+ (t, GateEvent.from_json(gate)) for t, gate in json_dict["gate_events"]
114
+ ],
115
+ qpu_fov=FieldOfView.from_json(json_dict["qpu_fov"]),
116
+ atoms=list(map(AtomTrajectory.from_json, json_dict["atoms"])),
117
+ slm_zone=list(map(tuple, json_dict["slm_zone"])),
118
+ aod_moves=list(map(AODMoveEvent.from_json, json_dict["aod_moves"])),
119
+ )
@@ -0,0 +1,43 @@
1
+ import zlib
2
+ import base64
3
+ from typing import Any, Dict
4
+
5
+ import numpy as np
6
+
7
+
8
+ def bytes_to_str(byte_array: bytes) -> str:
9
+ compressed_bytes = zlib.compress(byte_array)
10
+ return base64.b64encode(compressed_bytes).decode("ascii")
11
+
12
+
13
+ def str_to_bytes(string: str) -> bytes:
14
+ compressed_bytes = base64.b64decode(string)
15
+ return zlib.decompress(compressed_bytes)
16
+
17
+
18
+ def validate_dtype(dtype) -> None:
19
+ if np.dtype(dtype) == np.dtype(object):
20
+ raise ValueError("Object arrays are not supported")
21
+ elif dtype.subdtype is not None:
22
+ validate_dtype(dtype.subdtype[0])
23
+ elif dtype.fields is not None:
24
+ for _, (field_dtype, _) in dtype.fields.items():
25
+ validate_dtype(field_dtype)
26
+
27
+
28
+ def array_to_json(arr: np.ndarray) -> Dict[str, Any]:
29
+ validate_dtype(arr.dtype)
30
+
31
+ return {
32
+ "dtype": (arr.dtype.descr if arr.dtype.fields is not None else arr.dtype.str),
33
+ "shape": arr.shape,
34
+ "buffer": bytes_to_str(arr.tobytes()),
35
+ }
36
+
37
+
38
+ def json_to_array(json_dict: dict) -> np.ndarray:
39
+ _data = json_dict["buffer"]
40
+ _dtype = json_dict["dtype"]
41
+ _shape = json_dict["shape"]
42
+
43
+ return np.frombuffer(str_to_bytes(_data), dtype=_dtype).reshape(_shape)