bloqade-circuit 0.6.2__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 (192) 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/__init__.py +4 -1
  7. bloqade/analysis/measure_id/analysis.py +29 -20
  8. bloqade/analysis/measure_id/impls.py +72 -31
  9. bloqade/annotate/__init__.py +6 -0
  10. bloqade/annotate/_dialect.py +3 -0
  11. bloqade/annotate/_interface.py +22 -0
  12. bloqade/annotate/stmts.py +29 -0
  13. bloqade/annotate/types.py +13 -0
  14. bloqade/cirq_utils/__init__.py +4 -2
  15. bloqade/cirq_utils/emit/__init__.py +3 -0
  16. bloqade/cirq_utils/emit/base.py +246 -0
  17. bloqade/cirq_utils/emit/gate.py +104 -0
  18. bloqade/cirq_utils/emit/noise.py +90 -0
  19. bloqade/cirq_utils/emit/qubit.py +35 -0
  20. bloqade/cirq_utils/lowering.py +660 -0
  21. bloqade/cirq_utils/noise/__init__.py +0 -2
  22. bloqade/cirq_utils/noise/_two_zone_utils.py +7 -15
  23. bloqade/cirq_utils/noise/model.py +151 -191
  24. bloqade/cirq_utils/noise/transform.py +2 -2
  25. bloqade/cirq_utils/parallelize.py +9 -6
  26. bloqade/gemini/__init__.py +1 -0
  27. bloqade/gemini/analysis/__init__.py +3 -0
  28. bloqade/gemini/analysis/logical_validation/__init__.py +1 -0
  29. bloqade/gemini/analysis/logical_validation/analysis.py +17 -0
  30. bloqade/gemini/analysis/logical_validation/impls.py +101 -0
  31. bloqade/gemini/groups.py +67 -0
  32. bloqade/native/__init__.py +23 -0
  33. bloqade/native/_prelude.py +45 -0
  34. bloqade/native/dialects/__init__.py +0 -0
  35. bloqade/native/dialects/gate/__init__.py +2 -0
  36. bloqade/native/dialects/gate/_dialect.py +3 -0
  37. bloqade/native/dialects/gate/_interface.py +32 -0
  38. bloqade/native/dialects/gate/stmts.py +31 -0
  39. bloqade/native/stdlib/__init__.py +0 -0
  40. bloqade/native/stdlib/broadcast.py +246 -0
  41. bloqade/native/stdlib/simple.py +220 -0
  42. bloqade/native/upstream/__init__.py +4 -0
  43. bloqade/native/upstream/squin2native.py +79 -0
  44. bloqade/pyqrack/__init__.py +2 -2
  45. bloqade/pyqrack/base.py +7 -1
  46. bloqade/pyqrack/device.py +190 -4
  47. bloqade/pyqrack/native.py +49 -0
  48. bloqade/pyqrack/reg.py +6 -6
  49. bloqade/pyqrack/squin/gate/__init__.py +1 -0
  50. bloqade/pyqrack/squin/gate/gate.py +136 -0
  51. bloqade/pyqrack/squin/noise/native.py +120 -54
  52. bloqade/pyqrack/squin/qubit.py +39 -36
  53. bloqade/pyqrack/target.py +5 -4
  54. bloqade/pyqrack/task.py +114 -7
  55. bloqade/qasm2/_qasm_loading.py +3 -3
  56. bloqade/qasm2/dialects/core/address.py +21 -12
  57. bloqade/qasm2/dialects/expr/_emit.py +19 -8
  58. bloqade/qasm2/dialects/expr/stmts.py +7 -7
  59. bloqade/qasm2/dialects/noise/fidelity.py +4 -8
  60. bloqade/qasm2/dialects/noise/model.py +2 -1
  61. bloqade/qasm2/emit/base.py +16 -11
  62. bloqade/qasm2/emit/gate.py +11 -8
  63. bloqade/qasm2/emit/main.py +103 -3
  64. bloqade/qasm2/emit/target.py +9 -5
  65. bloqade/qasm2/groups.py +3 -2
  66. bloqade/qasm2/parse/lowering.py +0 -1
  67. bloqade/qasm2/passes/fold.py +14 -73
  68. bloqade/qasm2/passes/glob.py +2 -2
  69. bloqade/qasm2/passes/noise.py +1 -1
  70. bloqade/qasm2/passes/parallel.py +7 -5
  71. bloqade/qasm2/rewrite/__init__.py +0 -1
  72. bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
  73. bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
  74. bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
  75. bloqade/qasm2/rewrite/register.py +2 -2
  76. bloqade/qasm2/rewrite/uop_to_parallel.py +4 -2
  77. bloqade/qbraid/lowering.py +1 -0
  78. bloqade/qbraid/schema.py +2 -2
  79. bloqade/qubit/__init__.py +12 -0
  80. bloqade/qubit/_dialect.py +3 -0
  81. bloqade/qubit/_interface.py +49 -0
  82. bloqade/qubit/_prelude.py +45 -0
  83. bloqade/qubit/analysis/__init__.py +1 -0
  84. bloqade/qubit/analysis/address_impl.py +40 -0
  85. bloqade/qubit/stdlib/__init__.py +2 -0
  86. bloqade/qubit/stdlib/_new.py +34 -0
  87. bloqade/qubit/stdlib/broadcast.py +62 -0
  88. bloqade/qubit/stdlib/simple.py +59 -0
  89. bloqade/qubit/stmts.py +60 -0
  90. bloqade/rewrite/passes/__init__.py +6 -0
  91. bloqade/rewrite/passes/aggressive_unroll.py +103 -0
  92. bloqade/rewrite/passes/callgraph.py +116 -0
  93. bloqade/rewrite/passes/canonicalize_ilist.py +20 -14
  94. bloqade/rewrite/rules/split_ifs.py +18 -1
  95. bloqade/squin/__init__.py +47 -14
  96. bloqade/squin/analysis/__init__.py +0 -1
  97. bloqade/squin/analysis/schedule.py +10 -11
  98. bloqade/squin/gate/__init__.py +2 -0
  99. bloqade/squin/gate/_dialect.py +3 -0
  100. bloqade/squin/gate/_interface.py +98 -0
  101. bloqade/squin/gate/stmts.py +125 -0
  102. bloqade/squin/groups.py +5 -22
  103. bloqade/squin/noise/__init__.py +1 -10
  104. bloqade/squin/noise/_dialect.py +1 -1
  105. bloqade/squin/noise/_interface.py +45 -0
  106. bloqade/squin/noise/stmts.py +66 -28
  107. bloqade/squin/rewrite/U3_to_clifford.py +70 -51
  108. bloqade/squin/rewrite/__init__.py +0 -2
  109. bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
  110. bloqade/squin/rewrite/wrap_analysis.py +4 -35
  111. bloqade/squin/stdlib/__init__.py +0 -0
  112. bloqade/squin/stdlib/broadcast/__init__.py +34 -0
  113. bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
  114. bloqade/squin/stdlib/broadcast/gate.py +260 -0
  115. bloqade/squin/stdlib/broadcast/noise.py +144 -0
  116. bloqade/squin/stdlib/simple/__init__.py +33 -0
  117. bloqade/squin/stdlib/simple/gate.py +242 -0
  118. bloqade/squin/stdlib/simple/noise.py +126 -0
  119. bloqade/stim/__init__.py +1 -0
  120. bloqade/stim/_wrappers.py +6 -0
  121. bloqade/stim/dialects/auxiliary/emit.py +19 -18
  122. bloqade/stim/dialects/collapse/emit_str.py +7 -8
  123. bloqade/stim/dialects/gate/emit.py +9 -10
  124. bloqade/stim/dialects/noise/emit.py +17 -13
  125. bloqade/stim/dialects/noise/stmts.py +5 -3
  126. bloqade/stim/emit/__init__.py +1 -0
  127. bloqade/stim/emit/impls.py +16 -0
  128. bloqade/stim/emit/stim_str.py +48 -31
  129. bloqade/stim/groups.py +12 -2
  130. bloqade/stim/parse/lowering.py +14 -17
  131. bloqade/stim/passes/__init__.py +3 -1
  132. bloqade/stim/passes/flatten.py +26 -0
  133. bloqade/stim/passes/simplify_ifs.py +16 -2
  134. bloqade/stim/passes/squin_to_stim.py +18 -60
  135. bloqade/stim/rewrite/__init__.py +3 -4
  136. bloqade/stim/rewrite/get_record_util.py +24 -0
  137. bloqade/stim/rewrite/ifs_to_stim.py +29 -31
  138. bloqade/stim/rewrite/qubit_to_stim.py +90 -41
  139. bloqade/stim/rewrite/set_detector_to_stim.py +68 -0
  140. bloqade/stim/rewrite/set_observable_to_stim.py +52 -0
  141. bloqade/stim/rewrite/squin_measure.py +11 -79
  142. bloqade/stim/rewrite/squin_noise.py +134 -108
  143. bloqade/stim/rewrite/util.py +5 -192
  144. bloqade/test_utils.py +1 -1
  145. bloqade/types.py +10 -0
  146. bloqade/validation/__init__.py +2 -0
  147. bloqade/validation/analysis/__init__.py +5 -0
  148. bloqade/validation/analysis/analysis.py +41 -0
  149. bloqade/validation/analysis/lattice.py +58 -0
  150. bloqade/validation/kernel_validation.py +77 -0
  151. {bloqade_circuit-0.6.2.dist-info → bloqade_circuit-0.9.1.dist-info}/METADATA +5 -6
  152. bloqade_circuit-0.9.1.dist-info/RECORD +265 -0
  153. bloqade/pyqrack/squin/op.py +0 -166
  154. bloqade/pyqrack/squin/runtime.py +0 -535
  155. bloqade/pyqrack/squin/wire.py +0 -51
  156. bloqade/rewrite/rules/flatten_ilist.py +0 -51
  157. bloqade/rewrite/rules/inline_getitem_ilist.py +0 -31
  158. bloqade/squin/_typeinfer.py +0 -20
  159. bloqade/squin/analysis/address_impl.py +0 -71
  160. bloqade/squin/analysis/nsites/__init__.py +0 -9
  161. bloqade/squin/analysis/nsites/analysis.py +0 -50
  162. bloqade/squin/analysis/nsites/impls.py +0 -92
  163. bloqade/squin/analysis/nsites/lattice.py +0 -49
  164. bloqade/squin/cirq/__init__.py +0 -265
  165. bloqade/squin/cirq/emit/emit_circuit.py +0 -109
  166. bloqade/squin/cirq/emit/noise.py +0 -49
  167. bloqade/squin/cirq/emit/op.py +0 -125
  168. bloqade/squin/cirq/emit/qubit.py +0 -60
  169. bloqade/squin/cirq/emit/runtime.py +0 -242
  170. bloqade/squin/cirq/lowering.py +0 -440
  171. bloqade/squin/lowering.py +0 -54
  172. bloqade/squin/noise/_wrapper.py +0 -40
  173. bloqade/squin/noise/rewrite.py +0 -111
  174. bloqade/squin/op/__init__.py +0 -41
  175. bloqade/squin/op/_dialect.py +0 -3
  176. bloqade/squin/op/_wrapper.py +0 -121
  177. bloqade/squin/op/number.py +0 -5
  178. bloqade/squin/op/rewrite.py +0 -46
  179. bloqade/squin/op/stdlib.py +0 -62
  180. bloqade/squin/op/stmts.py +0 -276
  181. bloqade/squin/op/traits.py +0 -43
  182. bloqade/squin/op/types.py +0 -26
  183. bloqade/squin/qubit.py +0 -184
  184. bloqade/squin/rewrite/canonicalize.py +0 -60
  185. bloqade/squin/rewrite/desugar.py +0 -124
  186. bloqade/squin/types.py +0 -8
  187. bloqade/squin/wire.py +0 -201
  188. bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
  189. bloqade/stim/rewrite/wire_to_stim.py +0 -57
  190. bloqade_circuit-0.6.2.dist-info/RECORD +0 -234
  191. {bloqade_circuit-0.6.2.dist-info → bloqade_circuit-0.9.1.dist-info}/WHEEL +0 -0
  192. {bloqade_circuit-0.6.2.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,265 +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
- ) -> cirq.Circuit:
161
- """Converts a squin.kernel method to a cirq.Circuit object.
162
-
163
- Args:
164
- mt (ir.Method): The kernel method from which to construct the circuit.
165
-
166
- Keyword Args:
167
- qubits (Sequence[cirq.Qid] | None):
168
- A list of qubits to use as the qubits in the circuit. Defaults to None.
169
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
170
- statement in the order they appear inside the kernel.
171
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
172
- number of qubits for the resulting circuit.
173
-
174
- ## Examples:
175
-
176
- Here's a very basic example:
177
-
178
- ```python
179
- from bloqade import squin
180
-
181
- @squin.kernel
182
- def main():
183
- q = squin.qubit.new(2)
184
- h = squin.op.h()
185
- squin.qubit.apply(h, q[0])
186
- cx = squin.op.cx()
187
- squin.qubit.apply(cx, q)
188
-
189
- circuit = squin.cirq.emit_circuit(main)
190
-
191
- print(circuit)
192
- ```
193
-
194
- You can also compose multiple kernels. Those are emitted as subcircuits within the "main" circuit.
195
- Subkernels can accept arguments and return a value.
196
-
197
- ```python
198
- from bloqade import squin
199
- from kirin.dialects import ilist
200
- from typing import Literal
201
- import cirq
202
-
203
- @squin.kernel
204
- def entangle(q: ilist.IList[squin.qubit.Qubit, Literal[2]]):
205
- h = squin.op.h()
206
- squin.qubit.apply(h, q[0])
207
- cx = squin.op.cx()
208
- squin.qubit.apply(cx, q)
209
- return cx
210
-
211
- @squin.kernel
212
- def main():
213
- q = squin.qubit.new(2)
214
- cx = entangle(q)
215
- q2 = squin.qubit.new(3)
216
- squin.qubit.apply(cx, [q[1], q2[2]])
217
-
218
-
219
- # custom list of qubits on grid
220
- qubits = [cirq.GridQubit(i, i+1) for i in range(5)]
221
-
222
- circuit = squin.cirq.emit_circuit(main, qubits=qubits)
223
- print(circuit)
224
-
225
- ```
226
-
227
- We also passed in a custom list of qubits above. This allows you to provide a custom geometry
228
- and manipulate the qubits in other circuits directly written in cirq as well.
229
- """
230
-
231
- if isinstance(mt.code, func.Function) and not mt.code.signature.output.is_subseteq(
232
- types.NoneType
233
- ):
234
- raise EmitError(
235
- "The method you are trying to convert to a circuit has a return value, but returning from a circuit is not supported."
236
- )
237
-
238
- emitter = EmitCirq(qubits=qubits)
239
-
240
- # Rewrite noise statements
241
- mt_ = mt.similar(mt.dialects)
242
- RewriteNoiseStmts(mt_.dialects)(mt_)
243
-
244
- return emitter.run(mt_, args=())
245
-
246
-
247
- def dump_circuit(mt: ir.Method, qubits: Sequence[cirq.Qid] | None = None, **kwargs):
248
- """Converts a squin.kernel method to a cirq.Circuit object and dumps it as JSON.
249
-
250
- This just runs `emit_circuit` and calls the `cirq.to_json` function to emit a JSON.
251
-
252
- Args:
253
- mt (ir.Method): The kernel method from which to construct the circuit.
254
-
255
- Keyword Args:
256
- qubits (Sequence[cirq.Qid] | None):
257
- A list of qubits to use as the qubits in the circuit. Defaults to None.
258
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
259
- statement in the order they appear inside the kernel.
260
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
261
- number of qubits for the resulting circuit.
262
-
263
- """
264
- circuit = emit_circuit(mt, qubits=qubits)
265
- 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 ()
@@ -1,49 +0,0 @@
1
- import cirq
2
- from kirin.emit import EmitError
3
- from kirin.interp import MethodTable, impl
4
-
5
- from ... import noise
6
- from .runtime import (
7
- KronRuntime,
8
- BasicOpRuntime,
9
- OperatorRuntimeABC,
10
- PauliStringRuntime,
11
- )
12
- from .emit_circuit import EmitCirq, EmitCirqFrame
13
-
14
-
15
- @noise.dialect.register(key="emit.cirq")
16
- class EmitCirqNoiseMethods(MethodTable):
17
-
18
- @impl(noise.stmts.StochasticUnitaryChannel)
19
- def stochastic_unitary_channel(
20
- self,
21
- emit: EmitCirq,
22
- frame: EmitCirqFrame,
23
- stmt: noise.stmts.StochasticUnitaryChannel,
24
- ):
25
- ops = frame.get(stmt.operators)
26
- ps = frame.get(stmt.probabilities)
27
-
28
- error_probabilities = {self._op_to_key(op_): p for op_, p in zip(ops, ps)}
29
- cirq_op = cirq.asymmetric_depolarize(error_probabilities=error_probabilities)
30
- return (BasicOpRuntime(cirq_op),)
31
-
32
- @staticmethod
33
- def _op_to_key(operator: OperatorRuntimeABC) -> str:
34
- match operator:
35
- case KronRuntime():
36
- key_lhs = EmitCirqNoiseMethods._op_to_key(operator.lhs)
37
- key_rhs = EmitCirqNoiseMethods._op_to_key(operator.rhs)
38
- return key_lhs + key_rhs
39
-
40
- case BasicOpRuntime():
41
- return str(operator.gate)
42
-
43
- case PauliStringRuntime():
44
- return operator.string
45
-
46
- case _:
47
- raise EmitError(
48
- f"Unexpected operator runtime in StochasticUnitaryChannel of type {type(operator).__name__} encountered!"
49
- )