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
@@ -0,0 +1,660 @@
1
+ from typing import Any
2
+ from dataclasses import field, dataclass
3
+
4
+ import cirq
5
+ from kirin import ir, types, lowering
6
+ from kirin.rewrite import Walk, CFGCompactify
7
+ from kirin.dialects import py, scf, func, ilist
8
+
9
+ from bloqade import qubit
10
+ from bloqade.squin import gate, noise, kernel, qalloc
11
+
12
+
13
+ def load_circuit(
14
+ circuit: cirq.Circuit,
15
+ kernel_name: str = "main",
16
+ dialects: ir.DialectGroup = kernel,
17
+ register_as_argument: bool = False,
18
+ return_register: bool = False,
19
+ register_argument_name: str = "q",
20
+ globals: dict[str, Any] | None = None,
21
+ file: str | None = None,
22
+ lineno_offset: int = 0,
23
+ col_offset: int = 0,
24
+ compactify: bool = True,
25
+ ):
26
+ """Converts a cirq.Circuit object into a squin kernel.
27
+
28
+ Args:
29
+ circuit (cirq.Circuit): The circuit to load.
30
+
31
+ Keyword Args:
32
+ kernel_name (str): The name of the kernel to load. Defaults to "main".
33
+ dialects (ir.DialectGroup | None): The dialects to use. Defaults to `squin.kernel`.
34
+ register_as_argument (bool): Determine whether the resulting kernel function should accept
35
+ a single `ilist.IList[Qubit, Any]` argument that is a list of qubits used within the
36
+ function. This allows you to compose kernel functions generated from circuits.
37
+ Defaults to `False`.
38
+ return_register (bool): Determine whether the resulting kernel functionr returns a
39
+ single value of type `ilist.IList[Qubit, Any]` that is the list of qubits used
40
+ in the kernel function. Useful when you want to compose multiple kernel functions
41
+ generated from circuits. Defaults to `False`.
42
+ register_argument_name (str): The name of the argument that represents the qubit register.
43
+ Only used when `register_as_argument=True`. Defaults to "q".
44
+ globals (dict[str, Any] | None): The global variables to use. Defaults to None.
45
+ file (str | None): The file name for error reporting. Defaults to None.
46
+ lineno_offset (int): The line number offset for error reporting. Defaults to 0.
47
+ col_offset (int): The column number offset for error reporting. Defaults to 0.
48
+ compactify (bool): Whether to compactify the output. Defaults to True.
49
+
50
+ ## Usage Examples:
51
+
52
+ ```python
53
+ # from cirq's "hello qubit" example
54
+ import cirq
55
+ from bloqade.cirq_utils import load_circuit
56
+
57
+ # Pick a qubit.
58
+ qubit = cirq.GridQubit(0, 0)
59
+
60
+ # Create a circuit.
61
+ circuit = cirq.Circuit(
62
+ cirq.X(qubit)**0.5, # Square root of NOT.
63
+ cirq.measure(qubit, key='m') # Measurement.
64
+ )
65
+
66
+ # load the circuit as squin
67
+ main = load_circuit(circuit)
68
+
69
+ # print the resulting IR
70
+ main.print()
71
+ ```
72
+
73
+ You can also compose kernel functions generated from circuits by passing in
74
+ and / or returning the respective quantum registers:
75
+
76
+ ```python
77
+ import cirq
78
+ from bloqade.cirq_utils import load_circuit
79
+ from bloqade import squin
80
+
81
+ q = cirq.LineQubit.range(2)
82
+ circuit = cirq.Circuit(cirq.H(q[0]), cirq.CX(*q))
83
+
84
+ get_entangled_qubits = load_circuit(
85
+ circuit, return_register=True, kernel_name="get_entangled_qubits"
86
+ )
87
+ get_entangled_qubits.print()
88
+
89
+ entangle_qubits = load_circuit(
90
+ circuit, register_as_argument=True, kernel_name="entangle_qubits"
91
+ )
92
+
93
+ @squin.kernel
94
+ def main():
95
+ qreg = get_entangled_qubits()
96
+ qreg2 = squin.qalloc(1)
97
+ entangle_qubits([qreg[1], qreg2[0]])
98
+ return squin.qubit.measure(qreg2)
99
+ ```
100
+ """
101
+
102
+ target = Squin(dialects, circuit)
103
+ body = target.run(
104
+ circuit,
105
+ source=str(circuit), # TODO: proper source string
106
+ file=file,
107
+ globals=globals,
108
+ lineno_offset=lineno_offset,
109
+ col_offset=col_offset,
110
+ compactify=compactify,
111
+ register_as_argument=register_as_argument,
112
+ register_argument_name=register_argument_name,
113
+ )
114
+
115
+ if return_register:
116
+ return_value = target.qreg
117
+ else:
118
+ return_value = func.ConstantNone()
119
+ body.blocks[0].stmts.append(return_value)
120
+
121
+ return_node = func.Return(value_or_stmt=return_value)
122
+ body.blocks[0].stmts.append(return_node)
123
+
124
+ self_arg_name = kernel_name + "_self"
125
+ arg_names = [self_arg_name]
126
+ if register_as_argument:
127
+ args = (target.qreg.type,)
128
+ arg_names.append(register_argument_name)
129
+ else:
130
+ args = ()
131
+
132
+ # NOTE: add _self as argument; need to know signature before so do it after lowering
133
+ signature = func.Signature(args, return_node.value.type)
134
+ body.blocks[0].args.insert_from(
135
+ 0,
136
+ types.Generic(ir.Method, types.Tuple.where(signature.inputs), signature.output),
137
+ self_arg_name,
138
+ )
139
+
140
+ code = func.Function(
141
+ sym_name=kernel_name,
142
+ signature=signature,
143
+ body=body,
144
+ )
145
+
146
+ mt = ir.Method(
147
+ sym_name=kernel_name,
148
+ arg_names=arg_names,
149
+ dialects=dialects,
150
+ code=code,
151
+ )
152
+
153
+ assert (run_pass := kernel.run_pass) is not None
154
+ run_pass(mt, typeinfer=True)
155
+
156
+ return mt
157
+
158
+
159
+ CirqNode = (
160
+ cirq.Circuit
161
+ | cirq.FrozenCircuit
162
+ | cirq.Moment
163
+ | cirq.Gate
164
+ | cirq.Qid
165
+ | cirq.Operation
166
+ )
167
+
168
+ DecomposeNode = (
169
+ cirq.SwapPowGate
170
+ | cirq.ISwapPowGate
171
+ | cirq.PhasedXPowGate
172
+ | cirq.PhasedXZGate
173
+ | cirq.CSwapGate
174
+ | cirq.XXPowGate
175
+ | cirq.YYPowGate
176
+ | cirq.CCXPowGate
177
+ | cirq.CCZPowGate
178
+ )
179
+
180
+
181
+ @dataclass
182
+ class Squin(lowering.LoweringABC[cirq.Circuit]):
183
+ """Lower a cirq.Circuit object to a squin kernel"""
184
+
185
+ circuit: cirq.Circuit
186
+ qreg: ir.SSAValue = field(init=False)
187
+ qreg_index: dict[cirq.Qid, int] = field(init=False, default_factory=dict)
188
+ next_qreg_index: int = field(init=False, default=0)
189
+
190
+ two_qubit_paulis = (
191
+ "IX",
192
+ "IY",
193
+ "IZ",
194
+ "XI",
195
+ "XX",
196
+ "XY",
197
+ "XZ",
198
+ "YI",
199
+ "YX",
200
+ "YY",
201
+ "YZ",
202
+ "ZI",
203
+ "ZX",
204
+ "ZY",
205
+ "ZZ",
206
+ )
207
+
208
+ def __post_init__(self):
209
+ # TODO: sort by cirq ordering
210
+ qbits = sorted(self.circuit.all_qubits())
211
+ self.qreg_index = {qid: idx for (idx, qid) in enumerate(qbits)}
212
+
213
+ def lower_qubit_getindex(self, state: lowering.State[cirq.Circuit], qid: cirq.Qid):
214
+ index = self.qreg_index[qid]
215
+ index_ssa = state.current_frame.push(py.Constant(index)).result
216
+ qbit_getitem = state.current_frame.push(py.GetItem(self.qreg, index_ssa))
217
+ return qbit_getitem.result
218
+
219
+ def lower_qubit_getindices(
220
+ self, state: lowering.State[cirq.Circuit], qids: tuple[cirq.Qid, ...]
221
+ ):
222
+ qbits_getitem = [self.lower_qubit_getindex(state, qid) for qid in qids]
223
+ qbits = state.current_frame.push(ilist.New(values=qbits_getitem))
224
+ return qbits.result
225
+
226
+ def run(
227
+ self,
228
+ stmt: cirq.Circuit,
229
+ *,
230
+ source: str | None = None,
231
+ globals: dict[str, Any] | None = None,
232
+ file: str | None = None,
233
+ lineno_offset: int = 0,
234
+ col_offset: int = 0,
235
+ compactify: bool = True,
236
+ register_as_argument: bool = False,
237
+ register_argument_name: str = "q",
238
+ ) -> ir.Region:
239
+
240
+ state = lowering.State(
241
+ self,
242
+ file=file,
243
+ lineno_offset=lineno_offset,
244
+ col_offset=col_offset,
245
+ )
246
+
247
+ with state.frame([stmt], globals=globals, finalize_next=False) as frame:
248
+
249
+ # NOTE: need a register of qubits before lowering statements
250
+ if register_as_argument:
251
+ # NOTE: register as argument to the kernel; we have freedom of choice for the name here
252
+ frame.curr_block.args.append_from(
253
+ ilist.IListType[qubit.QubitType, types.Any],
254
+ name=register_argument_name,
255
+ )
256
+ self.qreg = frame.curr_block.args[0]
257
+ else:
258
+ # NOTE: create a new register of appropriate size
259
+ n_qubits = len(self.qreg_index)
260
+ n = frame.push(py.Constant(n_qubits))
261
+ self.qreg = frame.push(func.Invoke((n.result,), callee=qalloc)).result
262
+
263
+ self.visit(state, stmt)
264
+
265
+ if compactify:
266
+ Walk(CFGCompactify()).rewrite(frame.curr_region)
267
+
268
+ region = frame.curr_region
269
+
270
+ return region
271
+
272
+ def visit(
273
+ self, state: lowering.State[cirq.Circuit], node: CirqNode
274
+ ) -> lowering.Result:
275
+ name = node.__class__.__name__
276
+ return getattr(self, f"visit_{name}", self.generic_visit)(state, node)
277
+
278
+ def generic_visit(self, state: lowering.State[cirq.Circuit], node: CirqNode):
279
+ if isinstance(node, CirqNode):
280
+ raise lowering.BuildError(
281
+ f"Cannot lower {node.__class__.__name__} node: {node}"
282
+ )
283
+ raise lowering.BuildError(f"Cannot lower {node}")
284
+
285
+ # return self.visit_Operation(state, node)
286
+
287
+ def lower_literal(self, state: lowering.State[cirq.Circuit], value) -> ir.SSAValue:
288
+ raise lowering.BuildError("Literals not supported in cirq circuit")
289
+
290
+ def lower_global(
291
+ self, state: lowering.State[cirq.Circuit], node: CirqNode
292
+ ) -> lowering.LoweringABC.Result:
293
+ raise lowering.BuildError("Literals not supported in cirq circuit")
294
+
295
+ def visit_Circuit(
296
+ self,
297
+ state: lowering.State[cirq.Circuit],
298
+ node: cirq.Circuit | cirq.FrozenCircuit,
299
+ ) -> lowering.Result:
300
+ for moment in node:
301
+ self.visit_Moment(state, moment)
302
+
303
+ def visit_Moment(
304
+ self, state: lowering.State[cirq.Circuit], node: cirq.Moment
305
+ ) -> lowering.Result:
306
+ for op_ in node.operations:
307
+ self.visit(state, op_)
308
+
309
+ def visit_GateOperation(
310
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
311
+ ):
312
+ if isinstance(node.gate, DecomposeNode):
313
+ # NOTE: easier to decompose these, but for that we need the qubits too,
314
+ # so we need to do this within this method
315
+ for subnode in cirq.decompose_once(node):
316
+ self.visit(state, subnode)
317
+ return
318
+
319
+ # NOTE: just forward to the appropriate method by getting the name
320
+ name = node.gate.__class__.__name__
321
+ return getattr(self, f"visit_{name}", self.generic_visit)(state, node)
322
+
323
+ def visit_TaggedOperation(
324
+ self, state: lowering.State[cirq.Circuit], node: cirq.TaggedOperation
325
+ ):
326
+ return self.visit(state, node.untagged)
327
+
328
+ def visit_ClassicallyControlledOperation(
329
+ self,
330
+ state: lowering.State[cirq.Circuit],
331
+ node: cirq.ClassicallyControlledOperation,
332
+ ):
333
+ conditions: list[ir.SSAValue] = []
334
+ for outcome in node.classical_controls:
335
+ key = outcome.key
336
+ if isinstance(key, cirq.MeasurementKey):
337
+ key = key.name
338
+ measurement_outcome = state.current_frame.defs[key]
339
+
340
+ if measurement_outcome.type.is_subseteq(ilist.IListType):
341
+ # NOTE: there is currently no convenient ilist.any method, so we need to use foldl
342
+ # with a simple function that just does an or
343
+
344
+ def bool_op_or(x: bool, y: bool) -> bool:
345
+ return x or y
346
+
347
+ f_code = state.current_frame.push(
348
+ lowering.Python(self.dialects).python_function(bool_op_or)
349
+ )
350
+ fn = ir.Method(
351
+ mod=None,
352
+ py_func=bool_op_or,
353
+ sym_name="bool_op_or",
354
+ arg_names=[],
355
+ dialects=self.dialects,
356
+ code=f_code,
357
+ )
358
+ f_const = state.current_frame.push(py.constant.Constant(fn))
359
+ init_val = state.current_frame.push(py.Constant(False)).result
360
+ condition = state.current_frame.push(
361
+ ilist.Foldl(f_const.result, measurement_outcome, init=init_val)
362
+ ).result
363
+ else:
364
+ condition = measurement_outcome
365
+
366
+ conditions.append(condition)
367
+
368
+ if len(conditions) == 1:
369
+ condition = conditions[0]
370
+ else:
371
+ condition = state.current_frame.push(
372
+ py.boolop.And(conditions[0], conditions[1])
373
+ ).result
374
+ for next_cond in conditions[2:]:
375
+ condition = state.current_frame.push(
376
+ py.boolop.And(condition, next_cond)
377
+ ).result
378
+
379
+ then_stmt = self.visit(state, node.without_classical_controls())
380
+
381
+ assert isinstance(
382
+ then_stmt, ir.Statement
383
+ ), f"Expected operation of classically controlled node {node} to be lowered to a statement, got type {type(then_stmt)}. \
384
+ Please report this issue!"
385
+
386
+ # NOTE: remove stmt from parent block
387
+ then_stmt.detach()
388
+ then_body = ir.Block((then_stmt,))
389
+ then_body.args.append_from(types.Bool, name="cond")
390
+ then_body.stmts.append(scf.Yield())
391
+
392
+ else_body = ir.Block(())
393
+ else_body.args.append_from(types.Bool, name="cond")
394
+ else_body.stmts.append(scf.Yield())
395
+
396
+ return state.current_frame.push(
397
+ scf.IfElse(condition, then_body=then_body, else_body=else_body)
398
+ )
399
+
400
+ def visit_MeasurementGate(
401
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
402
+ ):
403
+ qubits = self.lower_qubit_getindices(state, node.qubits)
404
+ stmt = state.current_frame.push(qubit.stmts.Measure(qubits))
405
+
406
+ # NOTE: add for classically controlled lowering
407
+ key = node.gate.key
408
+ if isinstance(key, cirq.MeasurementKey):
409
+ key = key.name
410
+ state.current_frame.defs[key] = stmt.result
411
+
412
+ return stmt
413
+
414
+ def visit_SingleQubitPauliStringGateOperation(
415
+ self,
416
+ state: lowering.State[cirq.Circuit],
417
+ node: cirq.SingleQubitPauliStringGateOperation,
418
+ ):
419
+ if isinstance(node.pauli, cirq.IdentityGate):
420
+ # TODO: do we need an identity gate in gate?
421
+ return
422
+
423
+ qargs = self.lower_qubit_getindices(state, (node.qubit,))
424
+ match node.pauli:
425
+ case cirq.X:
426
+ gate_stmt = gate.stmts.X
427
+ case cirq.Y:
428
+ gate_stmt = gate.stmts.Y
429
+ case cirq.Z:
430
+ gate_stmt = gate.stmts.Z
431
+ case _:
432
+ raise lowering.BuildError(f"Unexpected Pauli operation {node.pauli}")
433
+
434
+ return state.current_frame.push(gate_stmt(qargs))
435
+
436
+ def visit_HPowGate(self, state: lowering.State[cirq.Circuit], node: cirq.HPowGate):
437
+ qargs = self.lower_qubit_getindices(state, node.qubits)
438
+
439
+ if node.gate.exponent % 2 == 1:
440
+ return state.current_frame.push(gate.stmts.H(qargs))
441
+
442
+ # NOTE: decompose into products of paulis for arbitrary exponents according to _decompose_ method
443
+ for subnode in cirq.decompose_once(node):
444
+ self.visit(state, subnode)
445
+
446
+ def visit_XPowGate(
447
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
448
+ ):
449
+ qargs = self.lower_qubit_getindices(state, node.qubits)
450
+ if node.gate.exponent % 2 == 1:
451
+ return state.current_frame.push(gate.stmts.X(qargs))
452
+
453
+ angle = state.current_frame.push(py.Constant(0.5 * node.gate.exponent))
454
+ return state.current_frame.push(gate.stmts.Rx(angle.result, qargs))
455
+
456
+ def visit_YPowGate(
457
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
458
+ ):
459
+ qargs = self.lower_qubit_getindices(state, node.qubits)
460
+ if node.gate.exponent % 2 == 1:
461
+ return state.current_frame.push(gate.stmts.Y(qargs))
462
+
463
+ angle = state.current_frame.push(py.Constant(0.5 * node.gate.exponent))
464
+ return state.current_frame.push(gate.stmts.Ry(angle.result, qargs))
465
+
466
+ def visit_ZPowGate(
467
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
468
+ ):
469
+ qargs = self.lower_qubit_getindices(state, node.qubits)
470
+
471
+ if abs(node.gate.exponent) == 0.5:
472
+ adjoint = node.gate.exponent < 0
473
+ return state.current_frame.push(gate.stmts.S(adjoint=adjoint, qubits=qargs))
474
+
475
+ if abs(node.gate.exponent) == 0.25:
476
+ adjoint = node.gate.exponent < 0
477
+ return state.current_frame.push(gate.stmts.T(adjoint=adjoint, qubits=qargs))
478
+
479
+ if node.gate.exponent % 2 == 1:
480
+ return state.current_frame.push(gate.stmts.Z(qubits=qargs))
481
+
482
+ angle = state.current_frame.push(py.Constant(0.5 * node.gate.exponent))
483
+ return state.current_frame.push(gate.stmts.Rz(angle.result, qargs))
484
+
485
+ def visit_Rx(self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation):
486
+ qargs = self.lower_qubit_getindices(state, node.qubits)
487
+ angle = state.current_frame.push(py.Constant(value=0.5 * node.gate.exponent))
488
+ return state.current_frame.push(gate.stmts.Rx(angle.result, qargs))
489
+
490
+ def visit_Ry(self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation):
491
+ qargs = self.lower_qubit_getindices(state, node.qubits)
492
+ angle = state.current_frame.push(py.Constant(value=0.5 * node.gate.exponent))
493
+ return state.current_frame.push(gate.stmts.Ry(angle.result, qargs))
494
+
495
+ def visit_Rz(self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation):
496
+ qargs = self.lower_qubit_getindices(state, node.qubits)
497
+ angle = state.current_frame.push(py.Constant(value=0.5 * node.gate.exponent))
498
+ return state.current_frame.push(gate.stmts.Rz(angle.result, qargs))
499
+
500
+ def visit_CXPowGate(
501
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
502
+ ):
503
+ if node.gate.exponent % 2 == 0:
504
+ return
505
+
506
+ if node.gate.exponent % 2 != 1:
507
+ raise lowering.BuildError("Exponents of CX gate are not supported!")
508
+
509
+ control, target = node.qubits
510
+ control_qarg = self.lower_qubit_getindices(state, (control,))
511
+ target_qarg = self.lower_qubit_getindices(state, (target,))
512
+ return state.current_frame.push(
513
+ gate.stmts.CX(controls=control_qarg, targets=target_qarg)
514
+ )
515
+
516
+ def visit_CZPowGate(
517
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
518
+ ):
519
+ if node.gate.exponent % 2 == 0:
520
+ return
521
+
522
+ if node.gate.exponent % 2 != 1:
523
+ raise lowering.BuildError("Exponents of CZ gate are not supported!")
524
+
525
+ control, target = node.qubits
526
+ control_qarg = self.lower_qubit_getindices(state, (control,))
527
+ target_qarg = self.lower_qubit_getindices(state, (target,))
528
+ return state.current_frame.push(
529
+ gate.stmts.CZ(controls=control_qarg, targets=target_qarg)
530
+ )
531
+
532
+ def visit_ZZPowGate(
533
+ self, state: lowering.State[cirq.Circuit], node: cirq.GateOperation
534
+ ):
535
+ if node.gate.exponent % 2 == 0:
536
+ return
537
+
538
+ qubit1, qubit2 = node.qubits
539
+ qarg1 = self.lower_qubit_getindices(state, (qubit1,))
540
+ qarg2 = self.lower_qubit_getindices(state, (qubit2,))
541
+
542
+ if node.gate.exponent % 2 == 1:
543
+ state.current_frame.push(gate.stmts.X(qarg1))
544
+ state.current_frame.push(gate.stmts.X(qarg2))
545
+ return
546
+
547
+ # NOTE: arbitrary exponent, write as CX * Rz * CX (up to global phase)
548
+ state.current_frame.push(gate.stmts.CX(qarg1, qarg2))
549
+ angle = state.current_frame.push(py.Constant(0.5 * node.gate.exponent))
550
+ state.current_frame.push(gate.stmts.Rz(angle.result, qarg2))
551
+ state.current_frame.push(gate.stmts.CX(qarg1, qarg2))
552
+
553
+ def visit_ControlledOperation(
554
+ self, state: lowering.State[cirq.Circuit], node: cirq.ControlledOperation
555
+ ):
556
+ match node.gate.sub_gate:
557
+ case cirq.X:
558
+ stmt = gate.stmts.CX
559
+ case cirq.Y:
560
+ stmt = gate.stmts.CY
561
+ case cirq.Z:
562
+ stmt = gate.stmts.CZ
563
+ case _:
564
+ raise lowering.BuildError(
565
+ f"Cannot lowering controlled operation: {node}"
566
+ )
567
+
568
+ control, target = node.qubits
569
+ control_qarg = self.lower_qubit_getindices(state, (control,))
570
+ target_qarg = self.lower_qubit_getindices(state, (target,))
571
+ return state.current_frame.push(stmt(control_qarg, target_qarg))
572
+
573
+ def visit_FrozenCircuit(
574
+ self, state: lowering.State[cirq.Circuit], node: cirq.FrozenCircuit
575
+ ):
576
+ return self.visit_Circuit(state, node)
577
+
578
+ def visit_CircuitOperation(
579
+ self, state: lowering.State[cirq.Circuit], node: cirq.CircuitOperation
580
+ ):
581
+ reps = node.repetitions
582
+
583
+ if not isinstance(reps, int):
584
+ raise lowering.BuildError(
585
+ f"Cannot lower CircuitOperation with non-integer repetitions: {node}"
586
+ )
587
+
588
+ if reps > 1:
589
+ raise lowering.BuildError(
590
+ "Repetitions of circuit operatiosn not yet supported"
591
+ )
592
+
593
+ return self.visit(state, node.circuit)
594
+
595
+ def visit_BitFlipChannel(
596
+ self, state: lowering.State[cirq.Circuit], node: cirq.BitFlipChannel
597
+ ):
598
+ p = node.gate.p
599
+ p_x = state.current_frame.push(py.Constant(p)).result
600
+ p_y = p_z = state.current_frame.push(py.Constant(0)).result
601
+ qubits = self.lower_qubit_getindices(state, node.qubits)
602
+ return state.current_frame.push(
603
+ noise.stmts.SingleQubitPauliChannel(px=p_x, py=p_y, pz=p_z, qubits=qubits)
604
+ )
605
+
606
+ def visit_DepolarizingChannel(
607
+ self, state: lowering.State[cirq.Circuit], node: cirq.DepolarizingChannel
608
+ ):
609
+ p = state.current_frame.push(py.Constant(node.gate.p)).result
610
+ qubits = self.lower_qubit_getindices(state, node.qubits)
611
+ return state.current_frame.push(noise.stmts.Depolarize(p, qubits=qubits))
612
+
613
+ def visit_AsymmetricDepolarizingChannel(
614
+ self,
615
+ state: lowering.State[cirq.Circuit],
616
+ node: cirq.AsymmetricDepolarizingChannel,
617
+ ):
618
+ nqubits = node.gate.num_qubits()
619
+ if nqubits > 2:
620
+ raise lowering.BuildError(
621
+ "AsymmetricDepolarizingChannel applied to more than 2 qubits is not supported!"
622
+ )
623
+
624
+ if nqubits == 1:
625
+ qubits = self.lower_qubit_getindices(state, node.qubits)
626
+ p_x = state.current_frame.push(py.Constant(node.gate.p_x)).result
627
+ p_y = state.current_frame.push(py.Constant(node.gate.p_y)).result
628
+ p_z = state.current_frame.push(py.Constant(node.gate.p_z)).result
629
+ return state.current_frame.push(
630
+ noise.stmts.SingleQubitPauliChannel(p_x, p_y, p_z, qubits)
631
+ )
632
+
633
+ # NOTE: nqubits == 2
634
+ error_probs = node.gate.error_probabilities
635
+ probability_values = []
636
+ p0 = None
637
+ for key in self.two_qubit_paulis:
638
+ p = error_probs.get(key)
639
+
640
+ if p is None:
641
+ if p0 is None:
642
+ p0 = state.current_frame.push(py.Constant(0)).result
643
+ p_ssa = p0
644
+ else:
645
+ p_ssa = state.current_frame.push(py.Constant(p)).result
646
+ probability_values.append(p_ssa)
647
+
648
+ probabilities = state.current_frame.push(
649
+ ilist.New(values=probability_values)
650
+ ).result
651
+
652
+ control, target = node.qubits
653
+ control_qarg = self.lower_qubit_getindices(state, (control,))
654
+ target_qarg = self.lower_qubit_getindices(state, (target,))
655
+
656
+ return state.current_frame.push(
657
+ noise.stmts.TwoQubitPauliChannel(
658
+ probabilities, controls=control_qarg, targets=target_qarg
659
+ )
660
+ )
@@ -3,8 +3,6 @@
3
3
  from .model import (
4
4
  GeminiOneZoneNoiseModel as GeminiOneZoneNoiseModel,
5
5
  GeminiTwoZoneNoiseModel as GeminiTwoZoneNoiseModel,
6
- GeminiOneZoneNoiseModelABC as GeminiOneZoneNoiseModelABC,
7
- GeminiOneZoneNoiseModelCorrelated as GeminiOneZoneNoiseModelCorrelated,
8
6
  GeminiOneZoneNoiseModelConflictGraphMoves as GeminiOneZoneNoiseModelConflictGraphMoves,
9
7
  )
10
8
  from .transform import transform_circuit as transform_circuit