bloqade-circuit 0.6.4__py3-none-any.whl → 0.9.1__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.
Files changed (191) hide show
  1. bloqade/analysis/address/__init__.py +8 -4
  2. bloqade/analysis/address/analysis.py +123 -33
  3. bloqade/analysis/address/impls.py +293 -90
  4. bloqade/analysis/address/lattice.py +209 -24
  5. bloqade/analysis/fidelity/analysis.py +11 -23
  6. bloqade/analysis/measure_id/analysis.py +18 -20
  7. bloqade/analysis/measure_id/impls.py +31 -29
  8. bloqade/annotate/__init__.py +6 -0
  9. bloqade/annotate/_dialect.py +3 -0
  10. bloqade/annotate/_interface.py +22 -0
  11. bloqade/annotate/stmts.py +29 -0
  12. bloqade/annotate/types.py +13 -0
  13. bloqade/cirq_utils/__init__.py +4 -2
  14. bloqade/cirq_utils/emit/__init__.py +3 -0
  15. bloqade/cirq_utils/emit/base.py +246 -0
  16. bloqade/cirq_utils/emit/gate.py +104 -0
  17. bloqade/cirq_utils/emit/noise.py +90 -0
  18. bloqade/cirq_utils/emit/qubit.py +35 -0
  19. bloqade/cirq_utils/lowering.py +660 -0
  20. bloqade/cirq_utils/noise/__init__.py +0 -2
  21. bloqade/cirq_utils/noise/_two_zone_utils.py +7 -15
  22. bloqade/cirq_utils/noise/model.py +151 -191
  23. bloqade/cirq_utils/noise/transform.py +2 -2
  24. bloqade/cirq_utils/parallelize.py +9 -6
  25. bloqade/gemini/__init__.py +1 -0
  26. bloqade/gemini/analysis/__init__.py +3 -0
  27. bloqade/gemini/analysis/logical_validation/__init__.py +1 -0
  28. bloqade/gemini/analysis/logical_validation/analysis.py +17 -0
  29. bloqade/gemini/analysis/logical_validation/impls.py +101 -0
  30. bloqade/gemini/groups.py +67 -0
  31. bloqade/native/__init__.py +23 -0
  32. bloqade/native/_prelude.py +45 -0
  33. bloqade/native/dialects/__init__.py +0 -0
  34. bloqade/native/dialects/gate/__init__.py +2 -0
  35. bloqade/native/dialects/gate/_dialect.py +3 -0
  36. bloqade/native/dialects/gate/_interface.py +32 -0
  37. bloqade/native/dialects/gate/stmts.py +31 -0
  38. bloqade/native/stdlib/__init__.py +0 -0
  39. bloqade/native/stdlib/broadcast.py +246 -0
  40. bloqade/native/stdlib/simple.py +220 -0
  41. bloqade/native/upstream/__init__.py +4 -0
  42. bloqade/native/upstream/squin2native.py +79 -0
  43. bloqade/pyqrack/__init__.py +2 -2
  44. bloqade/pyqrack/base.py +7 -1
  45. bloqade/pyqrack/device.py +192 -18
  46. bloqade/pyqrack/native.py +49 -0
  47. bloqade/pyqrack/reg.py +6 -6
  48. bloqade/pyqrack/squin/gate/__init__.py +1 -0
  49. bloqade/pyqrack/squin/gate/gate.py +136 -0
  50. bloqade/pyqrack/squin/noise/native.py +120 -54
  51. bloqade/pyqrack/squin/qubit.py +39 -36
  52. bloqade/pyqrack/target.py +5 -4
  53. bloqade/pyqrack/task.py +114 -7
  54. bloqade/qasm2/_qasm_loading.py +3 -3
  55. bloqade/qasm2/dialects/core/address.py +21 -12
  56. bloqade/qasm2/dialects/expr/_emit.py +19 -8
  57. bloqade/qasm2/dialects/expr/stmts.py +7 -7
  58. bloqade/qasm2/dialects/noise/fidelity.py +4 -8
  59. bloqade/qasm2/dialects/noise/model.py +2 -1
  60. bloqade/qasm2/emit/base.py +16 -11
  61. bloqade/qasm2/emit/gate.py +11 -8
  62. bloqade/qasm2/emit/main.py +103 -3
  63. bloqade/qasm2/emit/target.py +9 -5
  64. bloqade/qasm2/groups.py +3 -2
  65. bloqade/qasm2/parse/lowering.py +0 -1
  66. bloqade/qasm2/passes/fold.py +14 -73
  67. bloqade/qasm2/passes/glob.py +2 -2
  68. bloqade/qasm2/passes/noise.py +1 -1
  69. bloqade/qasm2/passes/parallel.py +7 -5
  70. bloqade/qasm2/rewrite/__init__.py +0 -1
  71. bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
  72. bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
  73. bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
  74. bloqade/qasm2/rewrite/register.py +2 -2
  75. bloqade/qasm2/rewrite/uop_to_parallel.py +4 -2
  76. bloqade/qbraid/lowering.py +1 -0
  77. bloqade/qbraid/schema.py +2 -2
  78. bloqade/qubit/__init__.py +12 -0
  79. bloqade/qubit/_dialect.py +3 -0
  80. bloqade/qubit/_interface.py +49 -0
  81. bloqade/qubit/_prelude.py +45 -0
  82. bloqade/qubit/analysis/__init__.py +1 -0
  83. bloqade/qubit/analysis/address_impl.py +40 -0
  84. bloqade/qubit/stdlib/__init__.py +2 -0
  85. bloqade/qubit/stdlib/_new.py +34 -0
  86. bloqade/qubit/stdlib/broadcast.py +62 -0
  87. bloqade/qubit/stdlib/simple.py +59 -0
  88. bloqade/qubit/stmts.py +60 -0
  89. bloqade/rewrite/passes/__init__.py +6 -0
  90. bloqade/rewrite/passes/aggressive_unroll.py +103 -0
  91. bloqade/rewrite/passes/callgraph.py +116 -0
  92. bloqade/rewrite/passes/canonicalize_ilist.py +20 -14
  93. bloqade/rewrite/rules/split_ifs.py +18 -1
  94. bloqade/squin/__init__.py +47 -14
  95. bloqade/squin/analysis/__init__.py +0 -1
  96. bloqade/squin/analysis/schedule.py +10 -11
  97. bloqade/squin/gate/__init__.py +2 -0
  98. bloqade/squin/gate/_dialect.py +3 -0
  99. bloqade/squin/gate/_interface.py +98 -0
  100. bloqade/squin/gate/stmts.py +125 -0
  101. bloqade/squin/groups.py +5 -22
  102. bloqade/squin/noise/__init__.py +1 -10
  103. bloqade/squin/noise/_dialect.py +1 -1
  104. bloqade/squin/noise/_interface.py +45 -0
  105. bloqade/squin/noise/stmts.py +66 -28
  106. bloqade/squin/rewrite/U3_to_clifford.py +70 -51
  107. bloqade/squin/rewrite/__init__.py +0 -2
  108. bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
  109. bloqade/squin/rewrite/wrap_analysis.py +4 -35
  110. bloqade/squin/stdlib/__init__.py +0 -0
  111. bloqade/squin/stdlib/broadcast/__init__.py +34 -0
  112. bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
  113. bloqade/squin/stdlib/broadcast/gate.py +260 -0
  114. bloqade/squin/stdlib/broadcast/noise.py +144 -0
  115. bloqade/squin/stdlib/simple/__init__.py +33 -0
  116. bloqade/squin/stdlib/simple/gate.py +242 -0
  117. bloqade/squin/stdlib/simple/noise.py +126 -0
  118. bloqade/stim/__init__.py +1 -0
  119. bloqade/stim/_wrappers.py +6 -0
  120. bloqade/stim/dialects/auxiliary/emit.py +19 -18
  121. bloqade/stim/dialects/collapse/emit_str.py +7 -8
  122. bloqade/stim/dialects/gate/emit.py +9 -10
  123. bloqade/stim/dialects/noise/emit.py +17 -13
  124. bloqade/stim/dialects/noise/stmts.py +5 -3
  125. bloqade/stim/emit/__init__.py +1 -0
  126. bloqade/stim/emit/impls.py +16 -0
  127. bloqade/stim/emit/stim_str.py +48 -31
  128. bloqade/stim/groups.py +12 -2
  129. bloqade/stim/parse/lowering.py +14 -17
  130. bloqade/stim/passes/__init__.py +0 -2
  131. bloqade/stim/passes/flatten.py +26 -0
  132. bloqade/stim/passes/simplify_ifs.py +6 -1
  133. bloqade/stim/passes/squin_to_stim.py +9 -84
  134. bloqade/stim/rewrite/__init__.py +2 -4
  135. bloqade/stim/rewrite/get_record_util.py +24 -0
  136. bloqade/stim/rewrite/ifs_to_stim.py +24 -25
  137. bloqade/stim/rewrite/qubit_to_stim.py +90 -41
  138. bloqade/stim/rewrite/set_detector_to_stim.py +68 -0
  139. bloqade/stim/rewrite/set_observable_to_stim.py +52 -0
  140. bloqade/stim/rewrite/squin_measure.py +9 -18
  141. bloqade/stim/rewrite/squin_noise.py +134 -108
  142. bloqade/stim/rewrite/util.py +5 -192
  143. bloqade/test_utils.py +1 -1
  144. bloqade/types.py +10 -0
  145. bloqade/validation/__init__.py +2 -0
  146. bloqade/validation/analysis/__init__.py +5 -0
  147. bloqade/validation/analysis/analysis.py +41 -0
  148. bloqade/validation/analysis/lattice.py +58 -0
  149. bloqade/validation/kernel_validation.py +77 -0
  150. {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/METADATA +5 -6
  151. bloqade_circuit-0.9.1.dist-info/RECORD +265 -0
  152. bloqade/pyqrack/squin/op.py +0 -180
  153. bloqade/pyqrack/squin/runtime.py +0 -535
  154. bloqade/pyqrack/squin/wire.py +0 -51
  155. bloqade/rewrite/rules/flatten_ilist.py +0 -51
  156. bloqade/rewrite/rules/inline_getitem_ilist.py +0 -31
  157. bloqade/squin/_typeinfer.py +0 -20
  158. bloqade/squin/analysis/address_impl.py +0 -71
  159. bloqade/squin/analysis/nsites/__init__.py +0 -9
  160. bloqade/squin/analysis/nsites/analysis.py +0 -50
  161. bloqade/squin/analysis/nsites/impls.py +0 -92
  162. bloqade/squin/analysis/nsites/lattice.py +0 -49
  163. bloqade/squin/cirq/__init__.py +0 -280
  164. bloqade/squin/cirq/emit/emit_circuit.py +0 -109
  165. bloqade/squin/cirq/emit/noise.py +0 -49
  166. bloqade/squin/cirq/emit/op.py +0 -125
  167. bloqade/squin/cirq/emit/qubit.py +0 -60
  168. bloqade/squin/cirq/emit/runtime.py +0 -242
  169. bloqade/squin/cirq/lowering.py +0 -440
  170. bloqade/squin/lowering.py +0 -54
  171. bloqade/squin/noise/_wrapper.py +0 -40
  172. bloqade/squin/noise/rewrite.py +0 -111
  173. bloqade/squin/op/__init__.py +0 -41
  174. bloqade/squin/op/_dialect.py +0 -3
  175. bloqade/squin/op/_wrapper.py +0 -121
  176. bloqade/squin/op/number.py +0 -5
  177. bloqade/squin/op/rewrite.py +0 -46
  178. bloqade/squin/op/stdlib.py +0 -62
  179. bloqade/squin/op/stmts.py +0 -276
  180. bloqade/squin/op/traits.py +0 -43
  181. bloqade/squin/op/types.py +0 -26
  182. bloqade/squin/qubit.py +0 -184
  183. bloqade/squin/rewrite/canonicalize.py +0 -60
  184. bloqade/squin/rewrite/desugar.py +0 -124
  185. bloqade/squin/types.py +0 -8
  186. bloqade/squin/wire.py +0 -201
  187. bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
  188. bloqade/stim/rewrite/wire_to_stim.py +0 -57
  189. bloqade_circuit-0.6.4.dist-info/RECORD +0 -234
  190. {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/WHEEL +0 -0
  191. {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,71 +0,0 @@
1
- from kirin import interp
2
- from kirin.analysis import ForwardFrame
3
-
4
- from bloqade.analysis.address.lattice import (
5
- Address,
6
- AddressReg,
7
- AddressWire,
8
- AddressQubit,
9
- )
10
- from bloqade.analysis.address.analysis import AddressAnalysis
11
-
12
- from .. import wire, qubit
13
-
14
- # Address lattice elements we can work with:
15
- ## NotQubit (bottom), AnyAddress (top)
16
-
17
- ## AddressTuple -> data: tuple[Address, ...]
18
- ### Recursive type, could contain itself or other variants
19
- ### This pops up in cases where you can have an IList/Tuple
20
- ### That contains elements that could be other Address types
21
-
22
- ## AddressReg -> data: Sequence[int]
23
- ### specific to creation of a register of qubits
24
-
25
- ## AddressQubit -> data: int
26
- ### Base qubit address type
27
-
28
-
29
- @wire.dialect.register(key="qubit.address")
30
- class SquinWireMethodTable(interp.MethodTable):
31
-
32
- @interp.impl(wire.Unwrap)
33
- def unwrap(
34
- self,
35
- interp_: AddressAnalysis,
36
- frame: ForwardFrame[Address],
37
- stmt: wire.Unwrap,
38
- ):
39
-
40
- origin_qubit = frame.get(stmt.qubit)
41
-
42
- if isinstance(origin_qubit, AddressQubit):
43
- return (AddressWire(origin_qubit=origin_qubit),)
44
- else:
45
- return (Address.top(),)
46
-
47
- @interp.impl(wire.Apply)
48
- def apply(
49
- self,
50
- interp_: AddressAnalysis,
51
- frame: ForwardFrame[Address],
52
- stmt: wire.Apply,
53
- ):
54
- return frame.get_values(stmt.inputs)
55
-
56
-
57
- @qubit.dialect.register(key="qubit.address")
58
- class SquinQubitMethodTable(interp.MethodTable):
59
-
60
- # This can be treated like a QRegNew impl
61
- @interp.impl(qubit.New)
62
- def new(
63
- self,
64
- interp_: AddressAnalysis,
65
- frame: ForwardFrame[Address],
66
- stmt: qubit.New,
67
- ):
68
- n_qubits = interp_.get_const_value(int, stmt.n_qubits)
69
- addr = AddressReg(range(interp_.next_address, interp_.next_address + n_qubits))
70
- interp_.next_address += n_qubits
71
- return (addr,)
@@ -1,9 +0,0 @@
1
- # Need this for impl registration to work properly!
2
- from . import impls as impls
3
- from .lattice import (
4
- Sites as Sites,
5
- NoSites as NoSites,
6
- AnySites as AnySites,
7
- NumberSites as NumberSites,
8
- )
9
- from .analysis import NSitesAnalysis as NSitesAnalysis
@@ -1,50 +0,0 @@
1
- # from typing import cast
2
-
3
- from kirin import ir
4
- from kirin.analysis import Forward
5
- from kirin.analysis.forward import ForwardFrame
6
-
7
- from bloqade.squin.op.types import OpType
8
- from bloqade.squin.op.traits import HasSites, FixedSites
9
-
10
- from .lattice import Sites, NoSites, NumberSites
11
-
12
-
13
- class NSitesAnalysis(Forward[Sites]):
14
-
15
- keys = ["op.nsites"]
16
- lattice = Sites
17
-
18
- # Take a page from how constprop works in Kirin
19
-
20
- ## This gets called before the registry look up
21
- def eval_stmt(self, frame: ForwardFrame, stmt: ir.Statement):
22
- method = self.lookup_registry(frame, stmt)
23
- if method is not None:
24
- return method(self, frame, stmt)
25
- elif stmt.has_trait(HasSites):
26
- has_sites_trait = stmt.get_present_trait(HasSites)
27
- sites = has_sites_trait.get_sites(stmt)
28
- return (NumberSites(sites=sites),)
29
- elif stmt.has_trait(FixedSites):
30
- sites_trait = stmt.get_present_trait(FixedSites)
31
- return (NumberSites(sites=sites_trait.data),)
32
- else:
33
- return (NoSites(),)
34
-
35
- # For when no implementation is found for the statement
36
- def eval_stmt_fallback(
37
- self, frame: ForwardFrame[Sites], stmt: ir.Statement
38
- ) -> tuple[Sites, ...]: # some form of Sites will go back into the frame
39
- return tuple(
40
- (
41
- self.lattice.top()
42
- if result.type.is_subseteq(OpType)
43
- else self.lattice.bottom()
44
- )
45
- for result in stmt.results
46
- )
47
-
48
- def run_method(self, method: ir.Method, args: tuple[Sites, ...]):
49
- # NOTE: we do not support dynamic calls here, thus no need to propagate method object
50
- return self.run_callable(method.code, (self.lattice.bottom(),) + args)
@@ -1,92 +0,0 @@
1
- from kirin import interp
2
- from kirin.dialects import scf, func
3
- from kirin.dialects.scf.typeinfer import TypeInfer as ScfTypeInfer
4
-
5
- from bloqade.squin import op, wire
6
-
7
- from .lattice import (
8
- NoSites,
9
- NumberSites,
10
- )
11
- from .analysis import NSitesAnalysis
12
-
13
-
14
- @wire.dialect.register(key="op.nsites")
15
- class SquinWire(interp.MethodTable):
16
-
17
- @interp.impl(wire.Apply)
18
- @interp.impl(wire.Broadcast)
19
- def apply(
20
- self,
21
- interp: NSitesAnalysis,
22
- frame: interp.Frame,
23
- stmt: wire.Apply | wire.Broadcast,
24
- ):
25
-
26
- return tuple(frame.get(input) for input in stmt.inputs)
27
-
28
-
29
- @op.dialect.register(key="op.nsites")
30
- class SquinOp(interp.MethodTable):
31
-
32
- @interp.impl(op.stmts.Kron)
33
- def kron(self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.Kron):
34
- lhs = frame.get(stmt.lhs)
35
- rhs = frame.get(stmt.rhs)
36
- if isinstance(lhs, NumberSites) and isinstance(rhs, NumberSites):
37
- new_n_sites = lhs.sites + rhs.sites
38
- return (NumberSites(sites=new_n_sites),)
39
- else:
40
- return (NoSites(),)
41
-
42
- @interp.impl(op.stmts.Mult)
43
- def mult(self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.Mult):
44
- lhs = frame.get(stmt.lhs)
45
- rhs = frame.get(stmt.rhs)
46
-
47
- if isinstance(lhs, NumberSites) and isinstance(rhs, NumberSites):
48
- lhs_sites = lhs.sites
49
- rhs_sites = rhs.sites
50
- # I originally considered throwing an exception here
51
- # but Xiu-zhe (Roger) Luo has pointed out it would be
52
- # a much better UX to add a type element that
53
- # could explicitly indicate the error. The downside
54
- # is you'll have some added complexity in the type lattice.
55
- if lhs_sites != rhs_sites:
56
- return (NoSites(),)
57
- else:
58
- return (NumberSites(sites=lhs_sites + rhs_sites),)
59
- else:
60
- return (NoSites(),)
61
-
62
- @interp.impl(op.stmts.Control)
63
- def control(
64
- self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.Control
65
- ):
66
- op_sites = frame.get(stmt.op)
67
-
68
- if isinstance(op_sites, NumberSites):
69
- n_sites = op_sites.sites
70
- return (NumberSites(sites=n_sites + stmt.n_controls),)
71
- else:
72
- return (NoSites(),)
73
-
74
- @interp.impl(op.stmts.Rot)
75
- def rot(self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.Rot):
76
- op_sites = frame.get(stmt.axis)
77
- return (op_sites,)
78
-
79
- @interp.impl(op.stmts.Scale)
80
- def scale(self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.Scale):
81
- op_sites = frame.get(stmt.op)
82
- return (op_sites,)
83
-
84
-
85
- @scf.dialect.register(key="op.nsites")
86
- class ScfSquinOp(ScfTypeInfer):
87
- pass
88
-
89
-
90
- @func.dialect.register(key="op.nsites")
91
- class FuncSquinOp(func.typeinfer.TypeInfer):
92
- pass
@@ -1,49 +0,0 @@
1
- from typing import final
2
- from dataclasses import dataclass
3
-
4
- from kirin.lattice import (
5
- SingletonMeta,
6
- BoundedLattice,
7
- SimpleJoinMixin,
8
- SimpleMeetMixin,
9
- )
10
-
11
-
12
- @dataclass
13
- class Sites(
14
- SimpleJoinMixin["Sites"], SimpleMeetMixin["Sites"], BoundedLattice["Sites"]
15
- ):
16
- @classmethod
17
- def bottom(cls) -> "Sites":
18
- return NoSites()
19
-
20
- @classmethod
21
- def top(cls) -> "Sites":
22
- return AnySites()
23
-
24
-
25
- @final
26
- @dataclass
27
- class NoSites(Sites, metaclass=SingletonMeta):
28
-
29
- def is_subseteq(self, other: Sites) -> bool:
30
- return True
31
-
32
-
33
- @final
34
- @dataclass
35
- class AnySites(Sites, metaclass=SingletonMeta):
36
-
37
- def is_subseteq(self, other: Sites) -> bool:
38
- return isinstance(other, Sites)
39
-
40
-
41
- @final
42
- @dataclass
43
- class NumberSites(Sites):
44
- sites: int
45
-
46
- def is_subseteq(self, other: Sites) -> bool:
47
- if isinstance(other, NumberSites):
48
- return self.sites == other.sites
49
- return False
@@ -1,280 +0,0 @@
1
- from typing import Any, Sequence
2
-
3
- import cirq
4
- from kirin import ir, types
5
- from kirin.emit import EmitError
6
- from kirin.dialects import func
7
-
8
- from . import lowering as lowering
9
- from .. import kernel
10
-
11
- # NOTE: just to register methods
12
- from .emit import op as op, noise as noise, qubit as qubit
13
- from .lowering import Squin
14
- from ..noise.rewrite import RewriteNoiseStmts
15
- from .emit.emit_circuit import EmitCirq
16
-
17
-
18
- def load_circuit(
19
- circuit: cirq.Circuit,
20
- kernel_name: str = "main",
21
- dialects: ir.DialectGroup = kernel,
22
- register_as_argument: bool = False,
23
- return_register: bool = False,
24
- register_argument_name: str = "q",
25
- globals: dict[str, Any] | None = None,
26
- file: str | None = None,
27
- lineno_offset: int = 0,
28
- col_offset: int = 0,
29
- compactify: bool = True,
30
- ):
31
- """Converts a cirq.Circuit object into a squin kernel.
32
-
33
- Args:
34
- circuit (cirq.Circuit): The circuit to load.
35
-
36
- Keyword Args:
37
- kernel_name (str): The name of the kernel to load. Defaults to "main".
38
- dialects (ir.DialectGroup | None): The dialects to use. Defaults to `squin.kernel`.
39
- register_as_argument (bool): Determine whether the resulting kernel function should accept
40
- a single `ilist.IList[Qubit, Any]` argument that is a list of qubits used within the
41
- function. This allows you to compose kernel functions generated from circuits.
42
- Defaults to `False`.
43
- return_register (bool): Determine whether the resulting kernel functionr returns a
44
- single value of type `ilist.IList[Qubit, Any]` that is the list of qubits used
45
- in the kernel function. Useful when you want to compose multiple kernel functions
46
- generated from circuits. Defaults to `False`.
47
- register_argument_name (str): The name of the argument that represents the qubit register.
48
- Only used when `register_as_argument=True`. Defaults to "q".
49
- globals (dict[str, Any] | None): The global variables to use. Defaults to None.
50
- file (str | None): The file name for error reporting. Defaults to None.
51
- lineno_offset (int): The line number offset for error reporting. Defaults to 0.
52
- col_offset (int): The column number offset for error reporting. Defaults to 0.
53
- compactify (bool): Whether to compactify the output. Defaults to True.
54
-
55
- ## Usage Examples:
56
-
57
- ```python
58
- # from cirq's "hello qubit" example
59
- import cirq
60
- from bloqade import squin
61
-
62
- # Pick a qubit.
63
- qubit = cirq.GridQubit(0, 0)
64
-
65
- # Create a circuit.
66
- circuit = cirq.Circuit(
67
- cirq.X(qubit)**0.5, # Square root of NOT.
68
- cirq.measure(qubit, key='m') # Measurement.
69
- )
70
-
71
- # load the circuit as squin
72
- main = squin.load_circuit(circuit)
73
-
74
- # print the resulting IR
75
- main.print()
76
- ```
77
-
78
- You can also compose kernel functions generated from circuits by passing in
79
- and / or returning the respective quantum registers:
80
-
81
- ```python
82
- q = cirq.LineQubit.range(2)
83
- circuit = cirq.Circuit(cirq.H(q[0]), cirq.CX(*q))
84
-
85
- get_entangled_qubits = squin.cirq.load_circuit(
86
- circuit, return_register=True, kernel_name="get_entangled_qubits"
87
- )
88
- get_entangled_qubits.print()
89
-
90
- entangle_qubits = squin.cirq.load_circuit(
91
- circuit, register_as_argument=True, kernel_name="entangle_qubits"
92
- )
93
-
94
- @squin.kernel
95
- def main():
96
- qreg = get_entangled_qubits()
97
- qreg2 = squin.qubit.new(1)
98
- entangle_qubits([qreg[1], qreg2[0]])
99
- return squin.qubit.measure(qreg2)
100
- ```
101
- """
102
-
103
- target = Squin(dialects=dialects, circuit=circuit)
104
- body = target.run(
105
- circuit,
106
- source=str(circuit), # TODO: proper source string
107
- file=file,
108
- globals=globals,
109
- lineno_offset=lineno_offset,
110
- col_offset=col_offset,
111
- compactify=compactify,
112
- register_as_argument=register_as_argument,
113
- register_argument_name=register_argument_name,
114
- )
115
-
116
- if return_register:
117
- return_value = target.qreg
118
- else:
119
- return_value = func.ConstantNone()
120
- body.blocks[0].stmts.append(return_value)
121
-
122
- return_node = func.Return(value_or_stmt=return_value)
123
- body.blocks[0].stmts.append(return_node)
124
-
125
- self_arg_name = kernel_name + "_self"
126
- arg_names = [self_arg_name]
127
- if register_as_argument:
128
- args = (target.qreg.type,)
129
- arg_names.append(register_argument_name)
130
- else:
131
- args = ()
132
-
133
- # NOTE: add _self as argument; need to know signature before so do it after lowering
134
- signature = func.Signature(args, return_node.value.type)
135
- body.blocks[0].args.insert_from(
136
- 0,
137
- types.Generic(ir.Method, types.Tuple.where(signature.inputs), signature.output),
138
- self_arg_name,
139
- )
140
-
141
- code = func.Function(
142
- sym_name=kernel_name,
143
- signature=signature,
144
- body=body,
145
- )
146
-
147
- return ir.Method(
148
- mod=None,
149
- py_func=None,
150
- sym_name=kernel_name,
151
- arg_names=arg_names,
152
- dialects=dialects,
153
- code=code,
154
- )
155
-
156
-
157
- def emit_circuit(
158
- mt: ir.Method,
159
- qubits: Sequence[cirq.Qid] | None = None,
160
- ignore_returns: bool = False,
161
- ) -> cirq.Circuit:
162
- """Converts a squin.kernel method to a cirq.Circuit object.
163
-
164
- Args:
165
- mt (ir.Method): The kernel method from which to construct the circuit.
166
-
167
- Keyword Args:
168
- qubits (Sequence[cirq.Qid] | None):
169
- A list of qubits to use as the qubits in the circuit. Defaults to None.
170
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
171
- statement in the order they appear inside the kernel.
172
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
173
- number of qubits for the resulting circuit.
174
- ignore_returns (bool):
175
- If `False`, emitting a circuit from a kernel that returns a value will error.
176
- Set it to `True` in order to ignore the return value(s). Defaults to `False`.
177
-
178
- ## Examples:
179
-
180
- Here's a very basic example:
181
-
182
- ```python
183
- from bloqade import squin
184
-
185
- @squin.kernel
186
- def main():
187
- q = squin.qubit.new(2)
188
- h = squin.op.h()
189
- squin.qubit.apply(h, q[0])
190
- cx = squin.op.cx()
191
- squin.qubit.apply(cx, q)
192
-
193
- circuit = squin.cirq.emit_circuit(main)
194
-
195
- print(circuit)
196
- ```
197
-
198
- You can also compose multiple kernels. Those are emitted as subcircuits within the "main" circuit.
199
- Subkernels can accept arguments and return a value.
200
-
201
- ```python
202
- from bloqade import squin
203
- from kirin.dialects import ilist
204
- from typing import Literal
205
- import cirq
206
-
207
- @squin.kernel
208
- def entangle(q: ilist.IList[squin.qubit.Qubit, Literal[2]]):
209
- h = squin.op.h()
210
- squin.qubit.apply(h, q[0])
211
- cx = squin.op.cx()
212
- squin.qubit.apply(cx, q)
213
- return cx
214
-
215
- @squin.kernel
216
- def main():
217
- q = squin.qubit.new(2)
218
- cx = entangle(q)
219
- q2 = squin.qubit.new(3)
220
- squin.qubit.apply(cx, [q[1], q2[2]])
221
-
222
-
223
- # custom list of qubits on grid
224
- qubits = [cirq.GridQubit(i, i+1) for i in range(5)]
225
-
226
- circuit = squin.cirq.emit_circuit(main, qubits=qubits)
227
- print(circuit)
228
-
229
- ```
230
-
231
- We also passed in a custom list of qubits above. This allows you to provide a custom geometry
232
- and manipulate the qubits in other circuits directly written in cirq as well.
233
- """
234
-
235
- if (
236
- not ignore_returns
237
- and isinstance(mt.code, func.Function)
238
- and not mt.code.signature.output.is_subseteq(types.NoneType)
239
- ):
240
- raise EmitError(
241
- "The method you are trying to convert to a circuit has a return value, but returning from a circuit is not supported."
242
- " Set `ignore_returns = True` in order to simply ignore the return values and emit a circuit."
243
- )
244
-
245
- emitter = EmitCirq(qubits=qubits)
246
-
247
- # Rewrite noise statements
248
- mt_ = mt.similar(mt.dialects)
249
- RewriteNoiseStmts(mt_.dialects)(mt_)
250
-
251
- return emitter.run(mt_, args=())
252
-
253
-
254
- def dump_circuit(
255
- mt: ir.Method,
256
- qubits: Sequence[cirq.Qid] | None = None,
257
- ignore_returns: bool = False,
258
- **kwargs,
259
- ):
260
- """Converts a squin.kernel method to a cirq.Circuit object and dumps it as JSON.
261
-
262
- This just runs `emit_circuit` and calls the `cirq.to_json` function to emit a JSON.
263
-
264
- Args:
265
- mt (ir.Method): The kernel method from which to construct the circuit.
266
-
267
- Keyword Args:
268
- qubits (Sequence[cirq.Qid] | None):
269
- A list of qubits to use as the qubits in the circuit. Defaults to None.
270
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
271
- statement in the order they appear inside the kernel.
272
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
273
- number of qubits for the resulting circuit.
274
- ignore_returns (bool):
275
- If `False`, emitting a circuit from a kernel that returns a value will error.
276
- Set it to `True` in order to ignore the return value(s). Defaults to `False`.
277
-
278
- """
279
- circuit = emit_circuit(mt, qubits=qubits, ignore_returns=ignore_returns)
280
- return cirq.to_json(circuit, **kwargs)
@@ -1,109 +0,0 @@
1
- from typing import Sequence
2
- from dataclasses import field, dataclass
3
-
4
- import cirq
5
- from kirin import ir
6
- from kirin.emit import EmitABC, EmitError, EmitFrame
7
- from kirin.interp import MethodTable, impl
8
- from kirin.dialects import func
9
- from typing_extensions import Self
10
-
11
- from ... import kernel
12
-
13
-
14
- @dataclass
15
- class EmitCirqFrame(EmitFrame):
16
- qubit_index: int = 0
17
- qubits: Sequence[cirq.Qid] | None = None
18
- circuit: cirq.Circuit = field(default_factory=cirq.Circuit)
19
-
20
-
21
- def _default_kernel():
22
- return kernel
23
-
24
-
25
- @dataclass
26
- class EmitCirq(EmitABC[EmitCirqFrame, cirq.Circuit]):
27
- keys = ["emit.cirq", "main"]
28
- dialects: ir.DialectGroup = field(default_factory=_default_kernel)
29
- void = cirq.Circuit()
30
- qubits: Sequence[cirq.Qid] | None = None
31
- _cached_circuit_operations: dict[int, cirq.CircuitOperation] = field(
32
- init=False, default_factory=dict
33
- )
34
-
35
- def initialize(self) -> Self:
36
- return super().initialize()
37
-
38
- def initialize_frame(
39
- self, code: ir.Statement, *, has_parent_access: bool = False
40
- ) -> EmitCirqFrame:
41
- return EmitCirqFrame(
42
- code, has_parent_access=has_parent_access, qubits=self.qubits
43
- )
44
-
45
- def run_method(self, method: ir.Method, args: tuple[cirq.Circuit, ...]):
46
- return self.run_callable(method.code, args)
47
-
48
- def emit_block(self, frame: EmitCirqFrame, block: ir.Block) -> cirq.Circuit:
49
- for stmt in block.stmts:
50
- result = self.eval_stmt(frame, stmt)
51
- if isinstance(result, tuple):
52
- frame.set_values(stmt.results, result)
53
-
54
- return frame.circuit
55
-
56
-
57
- @func.dialect.register(key="emit.cirq")
58
- class FuncEmit(MethodTable):
59
-
60
- @impl(func.Function)
61
- def emit_func(self, emit: EmitCirq, frame: EmitCirqFrame, stmt: func.Function):
62
- emit.run_ssacfg_region(frame, stmt.body, ())
63
- return (frame.circuit,)
64
-
65
- @impl(func.Invoke)
66
- def emit_invoke(self, emit: EmitCirq, frame: EmitCirqFrame, stmt: func.Invoke):
67
- stmt_hash = hash((stmt.callee, stmt.inputs))
68
- if (
69
- cached_circuit_op := emit._cached_circuit_operations.get(stmt_hash)
70
- ) is not None:
71
- # NOTE: cache hit
72
- frame.circuit.append(cached_circuit_op)
73
- return ()
74
-
75
- ret = stmt.result
76
-
77
- with emit.new_frame(stmt.callee.code, has_parent_access=True) as sub_frame:
78
- sub_frame.qubit_index = frame.qubit_index
79
- sub_frame.qubits = frame.qubits
80
-
81
- region = stmt.callee.callable_region
82
- if len(region.blocks) > 1:
83
- raise EmitError(
84
- "Subroutine with more than a single block encountered. This is not supported!"
85
- )
86
-
87
- # NOTE: get the arguments, "self" is just an empty circuit
88
- method_self = emit.void
89
- args = [frame.get(arg_) for arg_ in stmt.inputs]
90
- emit.run_ssacfg_region(
91
- sub_frame, stmt.callee.callable_region, args=(method_self, *args)
92
- )
93
- sub_circuit = sub_frame.circuit
94
-
95
- # NOTE: check to see if the call terminates with a return value and fetch the value;
96
- # we don't support multiple return statements via control flow so we just pick the first one
97
- block = region.blocks[0]
98
- return_stmt = next(
99
- (stmt for stmt in block.stmts if isinstance(stmt, func.Return)), None
100
- )
101
- if return_stmt is not None:
102
- frame.entries[ret] = sub_frame.get(return_stmt.value)
103
-
104
- circuit_op = cirq.CircuitOperation(
105
- sub_circuit.freeze(), use_repetition_ids=False
106
- )
107
- emit._cached_circuit_operations[stmt_hash] = circuit_op
108
- frame.circuit.append(circuit_op)
109
- return ()