bloqade-circuit 0.7.13__py3-none-any.whl → 0.8.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 (136) hide show
  1. bloqade/analysis/address/__init__.py +8 -4
  2. bloqade/analysis/address/analysis.py +119 -29
  3. bloqade/analysis/address/impls.py +290 -87
  4. bloqade/analysis/address/lattice.py +209 -24
  5. bloqade/analysis/fidelity/analysis.py +2 -2
  6. bloqade/analysis/measure_id/impls.py +3 -27
  7. bloqade/cirq_utils/__init__.py +3 -1
  8. bloqade/cirq_utils/emit/__init__.py +3 -0
  9. bloqade/cirq_utils/emit/base.py +243 -0
  10. bloqade/cirq_utils/emit/gate.py +104 -0
  11. bloqade/cirq_utils/emit/noise.py +90 -0
  12. bloqade/cirq_utils/emit/qubit.py +35 -0
  13. bloqade/cirq_utils/lowering.py +664 -0
  14. bloqade/native/__init__.py +0 -1
  15. bloqade/native/_prelude.py +3 -3
  16. bloqade/native/dialects/gate/__init__.py +2 -0
  17. bloqade/native/dialects/gate/_dialect.py +3 -0
  18. bloqade/native/dialects/{gates → gate}/_interface.py +5 -5
  19. bloqade/native/dialects/{gates → gate}/stmts.py +5 -5
  20. bloqade/native/stdlib/broadcast.py +19 -19
  21. bloqade/native/stdlib/simple.py +14 -13
  22. bloqade/native/upstream/__init__.py +5 -0
  23. bloqade/native/upstream/squin2native.py +136 -0
  24. bloqade/pyqrack/__init__.py +1 -2
  25. bloqade/pyqrack/device.py +6 -17
  26. bloqade/pyqrack/native.py +17 -17
  27. bloqade/pyqrack/reg.py +1 -6
  28. bloqade/pyqrack/squin/gate/__init__.py +1 -0
  29. bloqade/pyqrack/squin/gate/gate.py +136 -0
  30. bloqade/pyqrack/squin/noise/native.py +120 -54
  31. bloqade/pyqrack/squin/qubit.py +25 -41
  32. bloqade/pyqrack/target.py +2 -2
  33. bloqade/qasm2/dialects/core/address.py +21 -12
  34. bloqade/qasm2/dialects/noise/fidelity.py +2 -6
  35. bloqade/qasm2/dialects/noise/model.py +2 -1
  36. bloqade/qasm2/passes/parallel.py +3 -1
  37. bloqade/qasm2/rewrite/__init__.py +0 -1
  38. bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
  39. bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
  40. bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
  41. bloqade/qubit/__init__.py +12 -0
  42. bloqade/qubit/_dialect.py +3 -0
  43. bloqade/qubit/_interface.py +49 -0
  44. bloqade/qubit/_prelude.py +45 -0
  45. bloqade/qubit/analysis/__init__.py +1 -0
  46. bloqade/qubit/analysis/address_impl.py +40 -0
  47. bloqade/qubit/stdlib/__init__.py +2 -0
  48. bloqade/qubit/stdlib/_new.py +34 -0
  49. bloqade/qubit/stdlib/broadcast.py +62 -0
  50. bloqade/qubit/stdlib/simple.py +59 -0
  51. bloqade/qubit/stmts.py +60 -0
  52. bloqade/rewrite/passes/aggressive_unroll.py +2 -1
  53. bloqade/squin/__init__.py +44 -17
  54. bloqade/squin/analysis/__init__.py +0 -1
  55. bloqade/squin/analysis/schedule.py +2 -2
  56. bloqade/squin/gate/__init__.py +2 -0
  57. bloqade/squin/gate/_dialect.py +3 -0
  58. bloqade/squin/gate/_interface.py +98 -0
  59. bloqade/squin/gate/stmts.py +119 -0
  60. bloqade/squin/groups.py +4 -21
  61. bloqade/squin/noise/__init__.py +1 -9
  62. bloqade/squin/noise/_dialect.py +1 -1
  63. bloqade/squin/noise/_interface.py +45 -0
  64. bloqade/squin/noise/stmts.py +65 -29
  65. bloqade/squin/rewrite/U3_to_clifford.py +70 -51
  66. bloqade/squin/rewrite/__init__.py +0 -2
  67. bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
  68. bloqade/squin/rewrite/wrap_analysis.py +4 -35
  69. bloqade/squin/stdlib/broadcast/__init__.py +34 -0
  70. bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
  71. bloqade/squin/stdlib/broadcast/gate.py +260 -0
  72. bloqade/squin/stdlib/broadcast/noise.py +144 -0
  73. bloqade/squin/stdlib/simple/__init__.py +33 -0
  74. bloqade/squin/stdlib/simple/gate.py +242 -0
  75. bloqade/squin/stdlib/simple/noise.py +126 -0
  76. bloqade/stim/__init__.py +1 -0
  77. bloqade/stim/_wrappers.py +6 -0
  78. bloqade/stim/dialects/noise/emit.py +6 -1
  79. bloqade/stim/dialects/noise/stmts.py +5 -3
  80. bloqade/stim/emit/stim_str.py +2 -0
  81. bloqade/stim/parse/lowering.py +12 -17
  82. bloqade/stim/passes/__init__.py +0 -1
  83. bloqade/stim/passes/flatten.py +26 -0
  84. bloqade/stim/passes/simplify_ifs.py +6 -1
  85. bloqade/stim/passes/squin_to_stim.py +4 -70
  86. bloqade/stim/rewrite/__init__.py +0 -4
  87. bloqade/stim/rewrite/ifs_to_stim.py +23 -29
  88. bloqade/stim/rewrite/qubit_to_stim.py +96 -51
  89. bloqade/stim/rewrite/squin_measure.py +9 -18
  90. bloqade/stim/rewrite/squin_noise.py +132 -108
  91. bloqade/stim/rewrite/util.py +5 -204
  92. bloqade/types.py +10 -0
  93. {bloqade_circuit-0.7.13.dist-info → bloqade_circuit-0.8.0.dist-info}/METADATA +2 -2
  94. {bloqade_circuit-0.7.13.dist-info → bloqade_circuit-0.8.0.dist-info}/RECORD +96 -100
  95. bloqade/native/dialects/gates/__init__.py +0 -3
  96. bloqade/native/dialects/gates/_dialect.py +0 -3
  97. bloqade/pyqrack/squin/op.py +0 -180
  98. bloqade/pyqrack/squin/runtime.py +0 -543
  99. bloqade/pyqrack/squin/wire.py +0 -51
  100. bloqade/squin/_typeinfer.py +0 -20
  101. bloqade/squin/analysis/address_impl.py +0 -71
  102. bloqade/squin/analysis/nsites/__init__.py +0 -9
  103. bloqade/squin/analysis/nsites/analysis.py +0 -50
  104. bloqade/squin/analysis/nsites/impls.py +0 -99
  105. bloqade/squin/analysis/nsites/lattice.py +0 -49
  106. bloqade/squin/cirq/__init__.py +0 -306
  107. bloqade/squin/cirq/emit/emit_circuit.py +0 -129
  108. bloqade/squin/cirq/emit/noise.py +0 -49
  109. bloqade/squin/cirq/emit/op.py +0 -176
  110. bloqade/squin/cirq/emit/qubit.py +0 -58
  111. bloqade/squin/cirq/emit/runtime.py +0 -242
  112. bloqade/squin/cirq/lowering.py +0 -439
  113. bloqade/squin/lowering.py +0 -80
  114. bloqade/squin/noise/_wrapper.py +0 -36
  115. bloqade/squin/noise/rewrite.py +0 -129
  116. bloqade/squin/op/__init__.py +0 -41
  117. bloqade/squin/op/_dialect.py +0 -3
  118. bloqade/squin/op/_wrapper.py +0 -121
  119. bloqade/squin/op/number.py +0 -5
  120. bloqade/squin/op/rewrite.py +0 -46
  121. bloqade/squin/op/stdlib.py +0 -62
  122. bloqade/squin/op/stmts.py +0 -300
  123. bloqade/squin/op/traits.py +0 -43
  124. bloqade/squin/op/types.py +0 -128
  125. bloqade/squin/parallel.py +0 -200
  126. bloqade/squin/qubit.py +0 -194
  127. bloqade/squin/rewrite/canonicalize.py +0 -60
  128. bloqade/squin/rewrite/desugar.py +0 -102
  129. bloqade/squin/stdlib/channel.py +0 -86
  130. bloqade/squin/stdlib/gate.py +0 -201
  131. bloqade/squin/types.py +0 -8
  132. bloqade/squin/wire.py +0 -201
  133. bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
  134. bloqade/stim/rewrite/wire_to_stim.py +0 -57
  135. {bloqade_circuit-0.7.13.dist-info → bloqade_circuit-0.8.0.dist-info}/WHEEL +0 -0
  136. {bloqade_circuit-0.7.13.dist-info → bloqade_circuit-0.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,51 +0,0 @@
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
-
47
- if not qbit.is_active():
48
- return (interp.loss_m_result,)
49
-
50
- res: bool = bool(qbit.sim_reg.m(qbit.addr))
51
- return (res,)
@@ -1,20 +0,0 @@
1
- from kirin import types, interp
2
- from kirin.analysis import TypeInference, const
3
- from kirin.dialects import ilist
4
-
5
- from bloqade import squin
6
-
7
-
8
- @squin.qubit.dialect.register(key="typeinfer")
9
- class TypeInfer(interp.MethodTable):
10
- @interp.impl(squin.qubit.New)
11
- def _call(self, interp: TypeInference, frame: interp.Frame, stmt: squin.qubit.New):
12
- # based on Xiu-zhe (Roger) Luo's get_const_value function
13
-
14
- if (hint := stmt.n_qubits.hints.get("const")) is None:
15
- return (ilist.IListType[squin.qubit.QubitType, types.Any],)
16
-
17
- if isinstance(hint, const.Value) and isinstance(hint.data, int):
18
- return (ilist.IListType[squin.qubit.QubitType, types.Literal(hint.data)],)
19
-
20
- return (ilist.IListType[squin.qubit.QubitType, types.Any],)
@@ -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,99 +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
- @interp.impl(op.stmts.PauliString)
85
- def pauli_string(
86
- self, interp: NSitesAnalysis, frame: interp.Frame, stmt: op.stmts.PauliString
87
- ):
88
- s = stmt.string
89
- return (NumberSites(sites=len(s)),)
90
-
91
-
92
- @scf.dialect.register(key="op.nsites")
93
- class ScfSquinOp(ScfTypeInfer):
94
- pass
95
-
96
-
97
- @func.dialect.register(key="op.nsites")
98
- class FuncSquinOp(func.typeinfer.TypeInfer):
99
- 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,306 +0,0 @@
1
- from typing import Any, Sequence
2
- from warnings import warn
3
-
4
- import cirq
5
- from kirin import ir, types
6
- from kirin.emit import EmitError
7
- from kirin.dialects import func
8
-
9
- from . import lowering as lowering
10
- from .. import kernel
11
-
12
- # NOTE: just to register methods
13
- from .emit import op as op, noise as noise, qubit as qubit
14
- from .lowering import Squin
15
- from ..noise.rewrite import RewriteNoiseStmts
16
- from .emit.emit_circuit import EmitCirq
17
-
18
-
19
- def load_circuit(
20
- circuit: cirq.Circuit,
21
- kernel_name: str = "main",
22
- dialects: ir.DialectGroup = kernel,
23
- register_as_argument: bool = False,
24
- return_register: bool = False,
25
- register_argument_name: str = "q",
26
- globals: dict[str, Any] | None = None,
27
- file: str | None = None,
28
- lineno_offset: int = 0,
29
- col_offset: int = 0,
30
- compactify: bool = True,
31
- ):
32
- """Converts a cirq.Circuit object into a squin kernel.
33
-
34
- Args:
35
- circuit (cirq.Circuit): The circuit to load.
36
-
37
- Keyword Args:
38
- kernel_name (str): The name of the kernel to load. Defaults to "main".
39
- dialects (ir.DialectGroup | None): The dialects to use. Defaults to `squin.kernel`.
40
- register_as_argument (bool): Determine whether the resulting kernel function should accept
41
- a single `ilist.IList[Qubit, Any]` argument that is a list of qubits used within the
42
- function. This allows you to compose kernel functions generated from circuits.
43
- Defaults to `False`.
44
- return_register (bool): Determine whether the resulting kernel functionr returns a
45
- single value of type `ilist.IList[Qubit, Any]` that is the list of qubits used
46
- in the kernel function. Useful when you want to compose multiple kernel functions
47
- generated from circuits. Defaults to `False`.
48
- register_argument_name (str): The name of the argument that represents the qubit register.
49
- Only used when `register_as_argument=True`. Defaults to "q".
50
- globals (dict[str, Any] | None): The global variables to use. Defaults to None.
51
- file (str | None): The file name for error reporting. Defaults to None.
52
- lineno_offset (int): The line number offset for error reporting. Defaults to 0.
53
- col_offset (int): The column number offset for error reporting. Defaults to 0.
54
- compactify (bool): Whether to compactify the output. Defaults to True.
55
-
56
- ## Usage Examples:
57
-
58
- ```python
59
- # from cirq's "hello qubit" example
60
- import cirq
61
- from bloqade import squin
62
-
63
- # Pick a qubit.
64
- qubit = cirq.GridQubit(0, 0)
65
-
66
- # Create a circuit.
67
- circuit = cirq.Circuit(
68
- cirq.X(qubit)**0.5, # Square root of NOT.
69
- cirq.measure(qubit, key='m') # Measurement.
70
- )
71
-
72
- # load the circuit as squin
73
- main = squin.load_circuit(circuit)
74
-
75
- # print the resulting IR
76
- main.print()
77
- ```
78
-
79
- You can also compose kernel functions generated from circuits by passing in
80
- and / or returning the respective quantum registers:
81
-
82
- ```python
83
- q = cirq.LineQubit.range(2)
84
- circuit = cirq.Circuit(cirq.H(q[0]), cirq.CX(*q))
85
-
86
- get_entangled_qubits = squin.cirq.load_circuit(
87
- circuit, return_register=True, kernel_name="get_entangled_qubits"
88
- )
89
- get_entangled_qubits.print()
90
-
91
- entangle_qubits = squin.cirq.load_circuit(
92
- circuit, register_as_argument=True, kernel_name="entangle_qubits"
93
- )
94
-
95
- @squin.kernel
96
- def main():
97
- qreg = get_entangled_qubits()
98
- qreg2 = squin.qubit.new(1)
99
- entangle_qubits([qreg[1], qreg2[0]])
100
- return squin.qubit.measure(qreg2)
101
- ```
102
- """
103
-
104
- target = Squin(dialects=dialects, circuit=circuit)
105
- body = target.run(
106
- circuit,
107
- source=str(circuit), # TODO: proper source string
108
- file=file,
109
- globals=globals,
110
- lineno_offset=lineno_offset,
111
- col_offset=col_offset,
112
- compactify=compactify,
113
- register_as_argument=register_as_argument,
114
- register_argument_name=register_argument_name,
115
- )
116
-
117
- if return_register:
118
- return_value = target.qreg
119
- else:
120
- return_value = func.ConstantNone()
121
- body.blocks[0].stmts.append(return_value)
122
-
123
- return_node = func.Return(value_or_stmt=return_value)
124
- body.blocks[0].stmts.append(return_node)
125
-
126
- self_arg_name = kernel_name + "_self"
127
- arg_names = [self_arg_name]
128
- if register_as_argument:
129
- args = (target.qreg.type,)
130
- arg_names.append(register_argument_name)
131
- else:
132
- args = ()
133
-
134
- # NOTE: add _self as argument; need to know signature before so do it after lowering
135
- signature = func.Signature(args, return_node.value.type)
136
- body.blocks[0].args.insert_from(
137
- 0,
138
- types.Generic(ir.Method, types.Tuple.where(signature.inputs), signature.output),
139
- self_arg_name,
140
- )
141
-
142
- code = func.Function(
143
- sym_name=kernel_name,
144
- signature=signature,
145
- body=body,
146
- )
147
-
148
- return ir.Method(
149
- mod=None,
150
- py_func=None,
151
- sym_name=kernel_name,
152
- arg_names=arg_names,
153
- dialects=dialects,
154
- code=code,
155
- )
156
-
157
-
158
- def emit_circuit(
159
- mt: ir.Method,
160
- qubits: Sequence[cirq.Qid] | None = None,
161
- circuit_qubits: Sequence[cirq.Qid] | None = None,
162
- args: tuple = (),
163
- ignore_returns: bool = False,
164
- ) -> cirq.Circuit:
165
- """Converts a squin.kernel method to a cirq.Circuit object.
166
-
167
- Args:
168
- mt (ir.Method): The kernel method from which to construct the circuit.
169
-
170
- Keyword Args:
171
- circuit_qubits (Sequence[cirq.Qid] | None):
172
- A list of qubits to use as the qubits in the circuit. Defaults to None.
173
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
174
- statement in the order they appear inside the kernel.
175
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
176
- number of qubits for the resulting circuit.
177
- args (tuple):
178
- The arguments of the kernel function from which to emit a circuit.
179
- ignore_returns (bool):
180
- If `False`, emitting a circuit from a kernel that returns a value will error.
181
- Set it to `True` in order to ignore the return value(s). Defaults to `False`.
182
-
183
- ## Examples:
184
-
185
- Here's a very basic example:
186
-
187
- ```python
188
- from bloqade import squin
189
-
190
- @squin.kernel
191
- def main():
192
- q = squin.qubit.new(2)
193
- h = squin.op.h()
194
- squin.qubit.apply(h, q[0])
195
- cx = squin.op.cx()
196
- squin.qubit.apply(cx, q)
197
-
198
- circuit = squin.cirq.emit_circuit(main)
199
-
200
- print(circuit)
201
- ```
202
-
203
- You can also compose multiple kernels. Those are emitted as subcircuits within the "main" circuit.
204
- Subkernels can accept arguments and return a value.
205
-
206
- ```python
207
- from bloqade import squin
208
- from kirin.dialects import ilist
209
- from typing import Literal
210
- import cirq
211
-
212
- @squin.kernel
213
- def entangle(q: ilist.IList[squin.qubit.Qubit, Literal[2]]):
214
- h = squin.op.h()
215
- squin.qubit.apply(h, q[0])
216
- cx = squin.op.cx()
217
- squin.qubit.apply(cx, q)
218
- return cx
219
-
220
- @squin.kernel
221
- def main():
222
- q = squin.qubit.new(2)
223
- cx = entangle(q)
224
- q2 = squin.qubit.new(3)
225
- squin.qubit.apply(cx, [q[1], q2[2]])
226
-
227
-
228
- # custom list of qubits on grid
229
- qubits = [cirq.GridQubit(i, i+1) for i in range(5)]
230
-
231
- circuit = squin.cirq.emit_circuit(main, circuit_qubits=qubits)
232
- print(circuit)
233
-
234
- ```
235
-
236
- We also passed in a custom list of qubits above. This allows you to provide a custom geometry
237
- and manipulate the qubits in other circuits directly written in cirq as well.
238
- """
239
-
240
- if circuit_qubits is None and qubits is not None:
241
- circuit_qubits = qubits
242
- warn(
243
- "The keyword argument `qubits` is deprecated. Use `circuit_qubits` instead."
244
- )
245
-
246
- if (
247
- not ignore_returns
248
- and isinstance(mt.code, func.Function)
249
- and not mt.code.signature.output.is_subseteq(types.NoneType)
250
- ):
251
- raise EmitError(
252
- "The method you are trying to convert to a circuit has a return value, but returning from a circuit is not supported."
253
- " Set `ignore_returns = True` in order to simply ignore the return values and emit a circuit."
254
- )
255
-
256
- if len(args) != len(mt.args):
257
- raise ValueError(
258
- f"The method from which you're trying to emit a circuit takes {len(mt.args)} as input, but you passed in {len(args)} via the `args` keyword!"
259
- )
260
-
261
- emitter = EmitCirq(qubits=qubits)
262
-
263
- # Rewrite noise statements
264
- mt_ = mt.similar(mt.dialects)
265
- RewriteNoiseStmts(mt_.dialects)(mt_)
266
-
267
- return emitter.run(mt_, args=args)
268
-
269
-
270
- def dump_circuit(
271
- mt: ir.Method,
272
- circuit_qubits: Sequence[cirq.Qid] | None = None,
273
- args: tuple = (),
274
- qubits: Sequence[cirq.Qid] | None = None,
275
- ignore_returns: bool = False,
276
- **kwargs,
277
- ):
278
- """Converts a squin.kernel method to a cirq.Circuit object and dumps it as JSON.
279
-
280
- This just runs `emit_circuit` and calls the `cirq.to_json` function to emit a JSON.
281
-
282
- Args:
283
- mt (ir.Method): The kernel method from which to construct the circuit.
284
-
285
- Keyword Args:
286
- circuit_qubits (Sequence[cirq.Qid] | None):
287
- A list of qubits to use as the qubits in the circuit. Defaults to None.
288
- If this is None, then `cirq.LineQubit`s are inserted for every `squin.qubit.new`
289
- statement in the order they appear inside the kernel.
290
- **Note**: If a list of qubits is provided, make sure that there is a sufficient
291
- number of qubits for the resulting circuit.
292
- args (tuple):
293
- The arguments of the kernel function from which to emit a circuit.
294
- ignore_returns (bool):
295
- If `False`, emitting a circuit from a kernel that returns a value will error.
296
- Set it to `True` in order to ignore the return value(s). Defaults to `False`.
297
-
298
- """
299
- circuit = emit_circuit(
300
- mt,
301
- circuit_qubits=circuit_qubits,
302
- qubits=qubits,
303
- args=args,
304
- ignore_returns=ignore_returns,
305
- )
306
- return cirq.to_json(circuit, **kwargs)