bloqade-circuit 0.1.0__py3-none-any.whl → 0.2.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 (74) hide show
  1. bloqade/analysis/address/impls.py +5 -9
  2. bloqade/analysis/address/lattice.py +1 -1
  3. bloqade/analysis/fidelity/__init__.py +1 -0
  4. bloqade/analysis/fidelity/analysis.py +69 -0
  5. bloqade/device.py +130 -0
  6. bloqade/noise/__init__.py +2 -1
  7. bloqade/noise/fidelity.py +51 -0
  8. bloqade/noise/native/model.py +1 -2
  9. bloqade/noise/native/rewrite.py +5 -5
  10. bloqade/noise/native/stmts.py +40 -11
  11. bloqade/pyqrack/__init__.py +8 -2
  12. bloqade/pyqrack/base.py +24 -3
  13. bloqade/pyqrack/device.py +166 -0
  14. bloqade/pyqrack/noise/native.py +1 -2
  15. bloqade/pyqrack/qasm2/core.py +31 -15
  16. bloqade/pyqrack/qasm2/glob.py +28 -0
  17. bloqade/pyqrack/qasm2/uop.py +9 -1
  18. bloqade/pyqrack/reg.py +17 -49
  19. bloqade/pyqrack/squin/__init__.py +0 -0
  20. bloqade/pyqrack/squin/op.py +154 -0
  21. bloqade/pyqrack/squin/qubit.py +85 -0
  22. bloqade/pyqrack/squin/runtime.py +515 -0
  23. bloqade/pyqrack/squin/wire.py +69 -0
  24. bloqade/pyqrack/target.py +9 -2
  25. bloqade/pyqrack/task.py +30 -0
  26. bloqade/qasm2/_wrappers.py +11 -1
  27. bloqade/qasm2/dialects/core/stmts.py +15 -4
  28. bloqade/qasm2/dialects/expr/_emit.py +9 -8
  29. bloqade/qasm2/emit/base.py +4 -2
  30. bloqade/qasm2/emit/gate.py +0 -14
  31. bloqade/qasm2/emit/main.py +19 -15
  32. bloqade/qasm2/emit/target.py +2 -6
  33. bloqade/qasm2/glob.py +1 -1
  34. bloqade/qasm2/parse/lowering.py +124 -1
  35. bloqade/qasm2/passes/glob.py +3 -3
  36. bloqade/qasm2/passes/lift_qubits.py +26 -0
  37. bloqade/qasm2/passes/noise.py +6 -14
  38. bloqade/qasm2/passes/parallel.py +3 -3
  39. bloqade/qasm2/passes/py2qasm.py +1 -2
  40. bloqade/qasm2/passes/qasm2py.py +1 -2
  41. bloqade/qasm2/rewrite/desugar.py +6 -6
  42. bloqade/qasm2/rewrite/glob.py +9 -9
  43. bloqade/qasm2/rewrite/heuristic_noise.py +30 -38
  44. bloqade/qasm2/rewrite/insert_qubits.py +34 -0
  45. bloqade/qasm2/rewrite/native_gates.py +54 -55
  46. bloqade/qasm2/rewrite/parallel_to_uop.py +9 -9
  47. bloqade/qasm2/rewrite/uop_to_parallel.py +20 -22
  48. bloqade/qasm2/types.py +3 -6
  49. bloqade/qbraid/schema.py +10 -12
  50. bloqade/squin/__init__.py +1 -1
  51. bloqade/squin/analysis/nsites/analysis.py +4 -6
  52. bloqade/squin/analysis/nsites/impls.py +2 -6
  53. bloqade/squin/analysis/schedule.py +1 -1
  54. bloqade/squin/groups.py +15 -7
  55. bloqade/squin/noise/__init__.py +27 -0
  56. bloqade/squin/noise/_dialect.py +3 -0
  57. bloqade/squin/noise/stmts.py +59 -0
  58. bloqade/squin/op/__init__.py +35 -5
  59. bloqade/squin/op/number.py +5 -0
  60. bloqade/squin/op/rewrite.py +46 -0
  61. bloqade/squin/op/stmts.py +23 -2
  62. bloqade/squin/op/types.py +14 -0
  63. bloqade/squin/qubit.py +79 -11
  64. bloqade/squin/rewrite/__init__.py +0 -0
  65. bloqade/squin/rewrite/measure_desugar.py +33 -0
  66. bloqade/squin/wire.py +31 -2
  67. bloqade/stim/emit/stim.py +1 -1
  68. bloqade/task.py +94 -0
  69. bloqade/visual/animation/base.py +25 -15
  70. {bloqade_circuit-0.1.0.dist-info → bloqade_circuit-0.2.0.dist-info}/METADATA +8 -2
  71. {bloqade_circuit-0.1.0.dist-info → bloqade_circuit-0.2.0.dist-info}/RECORD +73 -52
  72. bloqade/squin/op/complex.py +0 -6
  73. {bloqade_circuit-0.1.0.dist-info → bloqade_circuit-0.2.0.dist-info}/WHEEL +0 -0
  74. {bloqade_circuit-0.1.0.dist-info → bloqade_circuit-0.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,515 @@
1
+ from typing import Any
2
+ from dataclasses import field, dataclass
3
+
4
+ import numpy as np
5
+ from kirin.dialects import ilist
6
+
7
+ from pyqrack.pauli import Pauli
8
+ from bloqade.pyqrack import PyQrackQubit
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class OperatorRuntimeABC:
13
+ """The number of sites the operator applies to (including controls)"""
14
+
15
+ @property
16
+ def n_sites(self) -> int: ...
17
+
18
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
19
+ raise NotImplementedError(
20
+ "Operator runtime base class should not be called directly, override the method"
21
+ )
22
+
23
+ def control_apply(
24
+ self,
25
+ controls: tuple[PyQrackQubit, ...],
26
+ targets: tuple[PyQrackQubit, ...],
27
+ adjoint: bool = False,
28
+ ) -> None:
29
+ raise RuntimeError(f"Can't apply controlled version of {self}")
30
+
31
+ def broadcast_apply(self, qubits: ilist.IList[PyQrackQubit, Any], **kwargs) -> None:
32
+ n = self.n_sites
33
+
34
+ if len(qubits) % n != 0:
35
+ raise RuntimeError(
36
+ f"Cannot broadcast operator {self} that applies to {n} over {len(qubits)} qubits."
37
+ )
38
+
39
+ for qubit_index in range(0, len(qubits), n):
40
+ targets = qubits[qubit_index : qubit_index + n]
41
+ self.apply(*targets, **kwargs)
42
+
43
+
44
+ @dataclass(frozen=True)
45
+ class OperatorRuntime(OperatorRuntimeABC):
46
+ method_name: str
47
+
48
+ @property
49
+ def n_sites(self) -> int:
50
+ return 1
51
+
52
+ def get_method_name(self, adjoint: bool, control: bool) -> str:
53
+ method_name = ""
54
+ if control:
55
+ method_name += "mc"
56
+
57
+ if adjoint and self.method_name in ("s", "t"):
58
+ method_name += "adj"
59
+
60
+ return method_name + self.method_name
61
+
62
+ def apply(self, qubit: PyQrackQubit, adjoint: bool = False) -> None:
63
+ if not qubit.is_active():
64
+ return
65
+ method_name = self.get_method_name(adjoint=adjoint, control=False)
66
+ getattr(qubit.sim_reg, method_name)(qubit.addr)
67
+
68
+ def control_apply(
69
+ self,
70
+ controls: tuple[PyQrackQubit, ...],
71
+ targets: tuple[PyQrackQubit],
72
+ adjoint: bool = False,
73
+ ) -> None:
74
+ target = targets[0]
75
+ if not target.is_active():
76
+ return
77
+
78
+ ctrls: list[int] = []
79
+ for qbit in controls:
80
+ if not qbit.is_active():
81
+ return
82
+
83
+ ctrls.append(qbit.addr)
84
+
85
+ method_name = self.get_method_name(adjoint=adjoint, control=True)
86
+ getattr(target.sim_reg, method_name)(ctrls, target.addr)
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class ControlRuntime(OperatorRuntimeABC):
91
+ op: OperatorRuntimeABC
92
+ n_controls: int
93
+
94
+ @property
95
+ def n_sites(self) -> int:
96
+ return self.op.n_sites + self.n_controls
97
+
98
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
99
+ ctrls = qubits[: self.n_controls]
100
+ targets = qubits[self.n_controls :]
101
+
102
+ if len(targets) != self.op.n_sites:
103
+ raise RuntimeError(
104
+ f"Cannot apply operator {self.op} to {len(targets)} qubits! It applies to {self.op.n_sites}, check your inputs!"
105
+ )
106
+
107
+ self.op.control_apply(controls=ctrls, targets=targets, adjoint=adjoint)
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class ProjectorRuntime(OperatorRuntimeABC):
112
+ to_state: bool
113
+
114
+ @property
115
+ def n_sites(self) -> int:
116
+ return 1
117
+
118
+ def apply(self, qubit: PyQrackQubit, adjoint: bool = False) -> None:
119
+ if not qubit.is_active():
120
+ return
121
+ qubit.sim_reg.force_m(qubit.addr, self.to_state)
122
+
123
+ def control_apply(
124
+ self,
125
+ controls: tuple[PyQrackQubit, ...],
126
+ targets: tuple[PyQrackQubit],
127
+ adjoint: bool = False,
128
+ ) -> None:
129
+ target = targets[0]
130
+ if not target.is_active():
131
+ return
132
+
133
+ ctrls: list[int] = []
134
+ for qbit in controls:
135
+ if not qbit.is_active():
136
+ return
137
+
138
+ m = [not self.to_state, 0, 0, self.to_state]
139
+ target.sim_reg.mcmtrx(ctrls, m, target.addr)
140
+
141
+
142
+ @dataclass(frozen=True)
143
+ class IdentityRuntime(OperatorRuntimeABC):
144
+ # TODO: do we even need sites? The apply never does anything
145
+ sites: int
146
+
147
+ @property
148
+ def n_sites(self) -> int:
149
+ return self.sites
150
+
151
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
152
+ pass
153
+
154
+ def control_apply(
155
+ self,
156
+ controls: tuple[PyQrackQubit, ...],
157
+ targets: tuple[PyQrackQubit, ...],
158
+ adjoint: bool = False,
159
+ ) -> None:
160
+ pass
161
+
162
+
163
+ @dataclass(frozen=True)
164
+ class MultRuntime(OperatorRuntimeABC):
165
+ lhs: OperatorRuntimeABC
166
+ rhs: OperatorRuntimeABC
167
+
168
+ @property
169
+ def n_sites(self) -> int:
170
+ if self.lhs.n_sites != self.rhs.n_sites:
171
+ raise RuntimeError("Multiplication of operators with unequal size.")
172
+
173
+ return self.lhs.n_sites
174
+
175
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
176
+ if adjoint:
177
+ # NOTE: inverted order
178
+ self.lhs.apply(*qubits, adjoint=adjoint)
179
+ self.rhs.apply(*qubits, adjoint=adjoint)
180
+ else:
181
+ self.rhs.apply(*qubits)
182
+ self.lhs.apply(*qubits)
183
+
184
+ def control_apply(
185
+ self,
186
+ controls: tuple[PyQrackQubit, ...],
187
+ targets: tuple[PyQrackQubit, ...],
188
+ adjoint: bool = False,
189
+ ) -> None:
190
+ if adjoint:
191
+ self.lhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
192
+ self.rhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
193
+ else:
194
+ self.rhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
195
+ self.lhs.control_apply(controls=controls, targets=targets, adjoint=adjoint)
196
+
197
+
198
+ @dataclass(frozen=True)
199
+ class KronRuntime(OperatorRuntimeABC):
200
+ lhs: OperatorRuntimeABC
201
+ rhs: OperatorRuntimeABC
202
+
203
+ @property
204
+ def n_sites(self) -> int:
205
+ return self.lhs.n_sites + self.rhs.n_sites
206
+
207
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
208
+ self.lhs.apply(*qubits[: self.lhs.n_sites], adjoint=adjoint)
209
+ self.rhs.apply(*qubits[self.lhs.n_sites :], adjoint=adjoint)
210
+
211
+ def control_apply(
212
+ self,
213
+ controls: tuple[PyQrackQubit, ...],
214
+ targets: tuple[PyQrackQubit, ...],
215
+ adjoint: bool = False,
216
+ ) -> None:
217
+ self.lhs.control_apply(
218
+ controls=controls,
219
+ targets=tuple(targets[: self.lhs.n_sites]),
220
+ adjoint=adjoint,
221
+ )
222
+ self.rhs.control_apply(
223
+ controls=controls,
224
+ targets=tuple(targets[self.lhs.n_sites :]),
225
+ adjoint=adjoint,
226
+ )
227
+
228
+
229
+ @dataclass(frozen=True)
230
+ class ScaleRuntime(OperatorRuntimeABC):
231
+ op: OperatorRuntimeABC
232
+ factor: complex
233
+
234
+ @property
235
+ def n_sites(self) -> int:
236
+ return self.op.n_sites
237
+
238
+ @staticmethod
239
+ def mat(factor, adjoint: bool):
240
+ if adjoint:
241
+ return [np.conj(factor), 0, 0, factor]
242
+ else:
243
+ return [factor, 0, 0, factor]
244
+
245
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
246
+ self.op.apply(*qubits, adjoint=adjoint)
247
+
248
+ # NOTE: when applying to multiple qubits, we "spread" the factor evenly
249
+ applied_factor = self.factor ** (1.0 / len(qubits))
250
+ for qbit in qubits:
251
+ if not qbit.is_active():
252
+ continue
253
+
254
+ # NOTE: just factor * eye(2)
255
+ m = self.mat(applied_factor, adjoint)
256
+
257
+ # TODO: output seems to always be normalized -- no-op?
258
+ qbit.sim_reg.mtrx(m, qbit.addr)
259
+
260
+ def control_apply(
261
+ self,
262
+ controls: tuple[PyQrackQubit, ...],
263
+ targets: tuple[PyQrackQubit, ...],
264
+ adjoint: bool = False,
265
+ ) -> None:
266
+
267
+ ctrls: list[int] = []
268
+ for qbit in controls:
269
+ if not qbit.is_active():
270
+ return
271
+
272
+ ctrls.append(qbit.addr)
273
+
274
+ self.op.control_apply(controls=controls, targets=targets, adjoint=adjoint)
275
+
276
+ applied_factor = self.factor ** (1.0 / len(targets))
277
+ for target in targets:
278
+ m = self.mat(applied_factor, adjoint=adjoint)
279
+ target.sim_reg.mcmtrx(ctrls, m, target.addr)
280
+
281
+
282
+ @dataclass(frozen=True)
283
+ class MtrxOpRuntime(OperatorRuntimeABC):
284
+ def mat(self, adjoint: bool) -> list[complex]:
285
+ raise NotImplementedError("Override this method in the subclass!")
286
+
287
+ @property
288
+ def n_sites(self) -> int:
289
+ # NOTE: pyqrack only supports 2x2 matrices, i.e. single qubit applications
290
+ return 1
291
+
292
+ def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
293
+ if not target.is_active():
294
+ return
295
+
296
+ m = self.mat(adjoint=adjoint)
297
+ target.sim_reg.mtrx(m, target.addr)
298
+
299
+ def control_apply(
300
+ self,
301
+ controls: tuple[PyQrackQubit, ...],
302
+ targets: tuple[PyQrackQubit, ...],
303
+ adjoint: bool = False,
304
+ ) -> None:
305
+ target = targets[0]
306
+ if not target.is_active():
307
+ return
308
+
309
+ ctrls: list[int] = []
310
+ for qbit in controls:
311
+ if not qbit.is_active():
312
+ return
313
+
314
+ ctrls.append(qbit.addr)
315
+
316
+ m = self.mat(adjoint=adjoint)
317
+ target.sim_reg.mcmtrx(ctrls, m, target.addr)
318
+
319
+
320
+ @dataclass(frozen=True)
321
+ class SpRuntime(MtrxOpRuntime):
322
+ def mat(self, adjoint: bool) -> list[complex]:
323
+ if adjoint:
324
+ return [0, 0, 0.5, 0]
325
+ else:
326
+ return [0, 0.5, 0, 0]
327
+
328
+
329
+ @dataclass(frozen=True)
330
+ class SnRuntime(MtrxOpRuntime):
331
+ def mat(self, adjoint: bool) -> list[complex]:
332
+ if adjoint:
333
+ return [0, 0.5, 0, 0]
334
+ else:
335
+ return [0, 0, 0.5, 0]
336
+
337
+
338
+ @dataclass(frozen=True)
339
+ class PhaseOpRuntime(MtrxOpRuntime):
340
+ theta: float
341
+ global_: bool
342
+
343
+ def mat(self, adjoint: bool) -> list[complex]:
344
+ sign = (-1) ** (not adjoint)
345
+ local_phase = np.exp(sign * 1j * self.theta)
346
+
347
+ # NOTE: this is just 1 if we want a local shift
348
+ global_phase = np.exp(sign * 1j * self.theta * self.global_)
349
+
350
+ return [global_phase, 0, 0, local_phase]
351
+
352
+
353
+ @dataclass(frozen=True)
354
+ class RotRuntime(OperatorRuntimeABC):
355
+ axis: OperatorRuntimeABC
356
+ angle: float
357
+ pyqrack_axis: Pauli = field(init=False)
358
+
359
+ @property
360
+ def n_sites(self) -> int:
361
+ return 1
362
+
363
+ def __post_init__(self):
364
+ if not isinstance(self.axis, OperatorRuntime):
365
+ raise RuntimeError(
366
+ f"Rotation only supported for Pauli operators! Got {self.axis}"
367
+ )
368
+
369
+ try:
370
+ axis = getattr(Pauli, "Pauli" + self.axis.method_name.upper())
371
+ except KeyError:
372
+ raise RuntimeError(
373
+ f"Rotation only supported for Pauli operators! Got {self.axis}"
374
+ )
375
+
376
+ # NOTE: weird setattr for frozen dataclasses
377
+ object.__setattr__(self, "pyqrack_axis", axis)
378
+
379
+ def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
380
+ if not target.is_active():
381
+ return
382
+
383
+ sign = (-1) ** adjoint
384
+ angle = sign * self.angle
385
+ target.sim_reg.r(self.pyqrack_axis, angle, target.addr)
386
+
387
+ def control_apply(
388
+ self,
389
+ controls: tuple[PyQrackQubit, ...],
390
+ targets: tuple[PyQrackQubit, ...],
391
+ adjoint: bool = False,
392
+ ) -> None:
393
+ target = targets[0]
394
+ if not target.is_active():
395
+ return
396
+
397
+ ctrls: list[int] = []
398
+ for qbit in controls:
399
+ if not qbit.is_active():
400
+ return
401
+
402
+ ctrls.append(qbit.addr)
403
+
404
+ sign = (-1) ** (not adjoint)
405
+ angle = sign * self.angle
406
+ target.sim_reg.mcr(self.pyqrack_axis, angle, ctrls, target.addr)
407
+
408
+
409
+ @dataclass(frozen=True)
410
+ class AdjointRuntime(OperatorRuntimeABC):
411
+ op: OperatorRuntimeABC
412
+
413
+ @property
414
+ def n_sites(self) -> int:
415
+ return self.op.n_sites
416
+
417
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
418
+ # NOTE: to account for adjoint(adjoint(op))
419
+ passed_on_adjoint = not adjoint
420
+
421
+ self.op.apply(*qubits, adjoint=passed_on_adjoint)
422
+
423
+ def control_apply(
424
+ self,
425
+ controls: tuple[PyQrackQubit, ...],
426
+ targets: tuple[PyQrackQubit, ...],
427
+ adjoint: bool = False,
428
+ ) -> None:
429
+ passed_on_adjoint = not adjoint
430
+ self.op.control_apply(
431
+ controls=controls, targets=targets, adjoint=passed_on_adjoint
432
+ )
433
+
434
+
435
+ @dataclass(frozen=True)
436
+ class U3Runtime(OperatorRuntimeABC):
437
+ theta: float
438
+ phi: float
439
+ lam: float
440
+
441
+ @property
442
+ def n_sites(self) -> int:
443
+ return 1
444
+
445
+ def angles(self, adjoint: bool) -> tuple[float, float, float]:
446
+ if adjoint:
447
+ # NOTE: adjoint(U(theta, phi, lam)) == U(-theta, -lam, -phi)
448
+ return -self.theta, -self.lam, -self.phi
449
+ else:
450
+ return self.theta, self.phi, self.lam
451
+
452
+ def apply(self, target: PyQrackQubit, adjoint: bool = False) -> None:
453
+ if not target.is_active():
454
+ return
455
+
456
+ angles = self.angles(adjoint=adjoint)
457
+ target.sim_reg.u(target.addr, *angles)
458
+
459
+ def control_apply(
460
+ self,
461
+ controls: tuple[PyQrackQubit, ...],
462
+ targets: tuple[PyQrackQubit, ...],
463
+ adjoint: bool = False,
464
+ ) -> None:
465
+ target = targets[0]
466
+ if not target.is_active():
467
+ return
468
+
469
+ ctrls: list[int] = []
470
+ for qbit in controls:
471
+ if not qbit.is_active():
472
+ return
473
+
474
+ ctrls.append(qbit.addr)
475
+
476
+ angles = self.angles(adjoint=adjoint)
477
+ target.sim_reg.mcu(ctrls, target.addr, *angles)
478
+
479
+
480
+ @dataclass(frozen=True)
481
+ class PauliStringRuntime(OperatorRuntimeABC):
482
+ string: str
483
+ ops: list[OperatorRuntime]
484
+
485
+ @property
486
+ def n_sites(self) -> int:
487
+ return sum((op.n_sites for op in self.ops))
488
+
489
+ def apply(self, *qubits: PyQrackQubit, adjoint: bool = False):
490
+ if len(qubits) != self.n_sites:
491
+ raise RuntimeError(
492
+ f"Cannot apply Pauli string {self.string} to {len(qubits)} qubits! Make sure the number of qubits matches."
493
+ )
494
+
495
+ qubit_index = 0
496
+ for op in self.ops:
497
+ next_qubit_index = qubit_index + op.n_sites
498
+ op.apply(*qubits[qubit_index:next_qubit_index], adjoint=adjoint)
499
+ qubit_index = next_qubit_index
500
+
501
+ def control_apply(
502
+ self,
503
+ controls: tuple[PyQrackQubit, ...],
504
+ targets: tuple[PyQrackQubit, ...],
505
+ adjoint: bool = False,
506
+ ) -> None:
507
+ if len(targets) != self.n_sites:
508
+ raise RuntimeError(
509
+ f"Cannot apply Pauli string {self.string} to {len(targets)} qubits! Make sure the number of qubits matches."
510
+ )
511
+
512
+ for i, op in enumerate(self.ops):
513
+ # NOTE: this is fine as the size of each op is actually just 1 by definition
514
+ target = targets[i]
515
+ op.control_apply(controls=controls, targets=(target,))
@@ -0,0 +1,69 @@
1
+ from kirin import interp
2
+
3
+ from bloqade.squin import wire
4
+ from bloqade.pyqrack.reg import PyQrackWire, PyQrackQubit
5
+ from bloqade.pyqrack.base import PyQrackInterpreter
6
+
7
+ from .runtime import OperatorRuntimeABC
8
+
9
+
10
+ @wire.dialect.register(key="pyqrack")
11
+ class PyQrackMethods(interp.MethodTable):
12
+ # @interp.impl(wire.Wrap)
13
+ # def wrap(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Wrap):
14
+ # traits = frozenset({lowering.FromPythonCall(), WireTerminator()})
15
+ # wire: ir.SSAValue = info.argument(WireType)
16
+ # qubit: ir.SSAValue = info.argument(QubitType)
17
+
18
+ @interp.impl(wire.Unwrap)
19
+ def unwrap(
20
+ self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Unwrap
21
+ ):
22
+ q: PyQrackQubit = frame.get(stmt.qubit)
23
+ return (PyQrackWire(q),)
24
+
25
+ @interp.impl(wire.Apply)
26
+ def apply(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Apply):
27
+ ws = stmt.inputs
28
+ assert isinstance(ws, tuple)
29
+ qubits: list[PyQrackQubit] = []
30
+ for w in ws:
31
+ assert isinstance(w, PyQrackWire)
32
+ qubits.append(w.qubit)
33
+ op: OperatorRuntimeABC = frame.get(stmt.operator)
34
+
35
+ op.apply(*qubits)
36
+
37
+ out_ws = [PyQrackWire(qbit) for qbit in qubits]
38
+ return (out_ws,)
39
+
40
+ @interp.impl(wire.Measure)
41
+ def measure(
42
+ self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Measure
43
+ ):
44
+ w: PyQrackWire = frame.get(stmt.wire)
45
+ qbit = w.qubit
46
+ res: int = qbit.sim_reg.m(qbit.addr)
47
+ return (res,)
48
+
49
+ @interp.impl(wire.MeasureAndReset)
50
+ def measure_and_reset(
51
+ self,
52
+ interp: PyQrackInterpreter,
53
+ frame: interp.Frame,
54
+ stmt: wire.MeasureAndReset,
55
+ ):
56
+ w: PyQrackWire = frame.get(stmt.wire)
57
+ qbit = w.qubit
58
+ res: int = qbit.sim_reg.m(qbit.addr)
59
+ qbit.sim_reg.force_m(qbit.addr, False)
60
+ new_w = PyQrackWire(qbit)
61
+ return (new_w, res)
62
+
63
+ @interp.impl(wire.Reset)
64
+ def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Reset):
65
+ w: PyQrackWire = frame.get(stmt.wire)
66
+ qbit = w.qubit
67
+ qbit.sim_reg.force_m(qbit.addr, False)
68
+ new_w = PyQrackWire(qbit)
69
+ return (new_w,)
bloqade/pyqrack/target.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from typing import List, TypeVar, ParamSpec
2
+ from warnings import warn
2
3
  from dataclasses import field, dataclass
3
4
 
4
5
  from kirin import ir
@@ -32,6 +33,12 @@ class PyQrack:
32
33
  """Options to pass to the QrackSimulator object, node `qubitCount` will be overwritten."""
33
34
 
34
35
  def __post_init__(self):
36
+ warn(
37
+ "The PyQrack target is deprecated and will be removed "
38
+ "in a future release. Please use the DynamicMemorySimulator / "
39
+ "StackMemorySimulator instead."
40
+ )
41
+
35
42
  self.pyqrack_options = PyQrackOptions(
36
43
  {**_default_pyqrack_args(), **self.pyqrack_options}
37
44
  )
@@ -80,7 +87,7 @@ class PyQrack:
80
87
  """
81
88
  fold = Fold(mt.dialects)
82
89
  fold(mt)
83
- return self._get_interp(mt).run(mt, args, kwargs).expect()
90
+ return self._get_interp(mt).run(mt, args, kwargs)
84
91
 
85
92
  def multi_run(
86
93
  self,
@@ -107,6 +114,6 @@ class PyQrack:
107
114
  interpreter = self._get_interp(mt)
108
115
  batched_results = []
109
116
  for _ in range(_shots):
110
- batched_results.append(interpreter.run(mt, args, kwargs).expect())
117
+ batched_results.append(interpreter.run(mt, args, kwargs))
111
118
 
112
119
  return batched_results
@@ -0,0 +1,30 @@
1
+ from typing import TypeVar, ParamSpec
2
+ from dataclasses import dataclass
3
+
4
+ from bloqade.task import AbstractSimulatorTask
5
+ from bloqade.pyqrack.base import (
6
+ MemoryABC,
7
+ PyQrackInterpreter,
8
+ )
9
+
10
+ RetType = TypeVar("RetType")
11
+ Param = ParamSpec("Param")
12
+ MemoryType = TypeVar("MemoryType", bound=MemoryABC)
13
+
14
+
15
+ @dataclass
16
+ class PyQrackSimulatorTask(AbstractSimulatorTask[Param, RetType, MemoryType]):
17
+ """PyQrack simulator task for Bloqade."""
18
+
19
+ pyqrack_interp: PyQrackInterpreter[MemoryType]
20
+
21
+ def run(self) -> RetType:
22
+ return self.pyqrack_interp.run(
23
+ self.kernel,
24
+ args=self.args,
25
+ kwargs=self.kwargs,
26
+ )
27
+
28
+ @property
29
+ def state(self) -> MemoryType:
30
+ return self.pyqrack_interp.memory
@@ -1,3 +1,5 @@
1
+ from typing import overload
2
+
1
3
  from kirin.lowering import wraps
2
4
 
3
5
  from .types import Bit, CReg, QReg, Qubit
@@ -58,8 +60,16 @@ def reset(qarg: Qubit) -> None:
58
60
  ...
59
61
 
60
62
 
63
+ @overload
64
+ def measure(qreg: QReg, creg: CReg) -> None: ...
65
+
66
+
67
+ @overload
68
+ def measure(qarg: Qubit, cbit: Bit) -> None: ...
69
+
70
+
61
71
  @wraps(core.Measure)
62
- def measure(qarg: Qubit, cbit: Bit) -> None:
72
+ def measure(qarg, cbit) -> None:
63
73
  """
64
74
  Measure the qubit `qarg` and store the result in the classical bit `cbit`.
65
75
 
@@ -46,10 +46,21 @@ class Measure(ir.Statement):
46
46
 
47
47
  name = "measure"
48
48
  traits = frozenset({lowering.FromPythonCall()})
49
- qarg: ir.SSAValue = info.argument(QubitType)
50
- """qarg (Qubit): The qubit to measure."""
51
- carg: ir.SSAValue = info.argument(BitType)
52
- """carg (Bit): The bit to store the result in."""
49
+ qarg: ir.SSAValue = info.argument(QubitType | QRegType)
50
+ """qarg (Qubit | QReg): The qubit or quantum register to measure."""
51
+ carg: ir.SSAValue = info.argument(BitType | CRegType)
52
+ """carg (Bit | CReg): The bit or register to store the result in."""
53
+
54
+ def check_type(self) -> None:
55
+ qarg_is_qubit = self.qarg.type.is_subseteq(QubitType)
56
+ carg_is_bit = self.carg.type.is_subseteq(BitType)
57
+ if (qarg_is_qubit and not carg_is_bit) or (not qarg_is_qubit and carg_is_bit):
58
+ raise ir.TypeCheckError(
59
+ self,
60
+ "Can't perform measurement with single (qu)bit and an entire register!",
61
+ help="Instead of `measure(qreg[i], creg)` or `measure(qreg, creg[i])`"
62
+ "use `measure(qreg[i], creg[j])` or `measure(qreg, creg)`, respectively.",
63
+ )
53
64
 
54
65
 
55
66
  @statement(dialect=dialect)