bloqade-circuit 0.6.4__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bloqade/analysis/address/__init__.py +8 -4
- bloqade/analysis/address/analysis.py +123 -33
- bloqade/analysis/address/impls.py +293 -90
- bloqade/analysis/address/lattice.py +209 -24
- bloqade/analysis/fidelity/analysis.py +11 -23
- bloqade/analysis/measure_id/analysis.py +18 -20
- bloqade/analysis/measure_id/impls.py +31 -29
- bloqade/annotate/__init__.py +6 -0
- bloqade/annotate/_dialect.py +3 -0
- bloqade/annotate/_interface.py +22 -0
- bloqade/annotate/stmts.py +29 -0
- bloqade/annotate/types.py +13 -0
- bloqade/cirq_utils/__init__.py +4 -2
- bloqade/cirq_utils/emit/__init__.py +3 -0
- bloqade/cirq_utils/emit/base.py +246 -0
- bloqade/cirq_utils/emit/gate.py +104 -0
- bloqade/cirq_utils/emit/noise.py +90 -0
- bloqade/cirq_utils/emit/qubit.py +35 -0
- bloqade/cirq_utils/lowering.py +660 -0
- bloqade/cirq_utils/noise/__init__.py +0 -2
- bloqade/cirq_utils/noise/_two_zone_utils.py +7 -15
- bloqade/cirq_utils/noise/model.py +151 -191
- bloqade/cirq_utils/noise/transform.py +2 -2
- bloqade/cirq_utils/parallelize.py +9 -6
- bloqade/gemini/__init__.py +1 -0
- bloqade/gemini/analysis/__init__.py +3 -0
- bloqade/gemini/analysis/logical_validation/__init__.py +1 -0
- bloqade/gemini/analysis/logical_validation/analysis.py +17 -0
- bloqade/gemini/analysis/logical_validation/impls.py +101 -0
- bloqade/gemini/groups.py +67 -0
- bloqade/native/__init__.py +23 -0
- bloqade/native/_prelude.py +45 -0
- bloqade/native/dialects/__init__.py +0 -0
- bloqade/native/dialects/gate/__init__.py +2 -0
- bloqade/native/dialects/gate/_dialect.py +3 -0
- bloqade/native/dialects/gate/_interface.py +32 -0
- bloqade/native/dialects/gate/stmts.py +31 -0
- bloqade/native/stdlib/__init__.py +0 -0
- bloqade/native/stdlib/broadcast.py +246 -0
- bloqade/native/stdlib/simple.py +220 -0
- bloqade/native/upstream/__init__.py +4 -0
- bloqade/native/upstream/squin2native.py +79 -0
- bloqade/pyqrack/__init__.py +2 -2
- bloqade/pyqrack/base.py +7 -1
- bloqade/pyqrack/device.py +192 -18
- bloqade/pyqrack/native.py +49 -0
- bloqade/pyqrack/reg.py +6 -6
- bloqade/pyqrack/squin/gate/__init__.py +1 -0
- bloqade/pyqrack/squin/gate/gate.py +136 -0
- bloqade/pyqrack/squin/noise/native.py +120 -54
- bloqade/pyqrack/squin/qubit.py +39 -36
- bloqade/pyqrack/target.py +5 -4
- bloqade/pyqrack/task.py +114 -7
- bloqade/qasm2/_qasm_loading.py +3 -3
- bloqade/qasm2/dialects/core/address.py +21 -12
- bloqade/qasm2/dialects/expr/_emit.py +19 -8
- bloqade/qasm2/dialects/expr/stmts.py +7 -7
- bloqade/qasm2/dialects/noise/fidelity.py +4 -8
- bloqade/qasm2/dialects/noise/model.py +2 -1
- bloqade/qasm2/emit/base.py +16 -11
- bloqade/qasm2/emit/gate.py +11 -8
- bloqade/qasm2/emit/main.py +103 -3
- bloqade/qasm2/emit/target.py +9 -5
- bloqade/qasm2/groups.py +3 -2
- bloqade/qasm2/parse/lowering.py +0 -1
- bloqade/qasm2/passes/fold.py +14 -73
- bloqade/qasm2/passes/glob.py +2 -2
- bloqade/qasm2/passes/noise.py +1 -1
- bloqade/qasm2/passes/parallel.py +7 -5
- bloqade/qasm2/rewrite/__init__.py +0 -1
- bloqade/qasm2/rewrite/noise/heuristic_noise.py +7 -17
- bloqade/qasm2/rewrite/parallel_to_glob.py +28 -15
- bloqade/qasm2/rewrite/parallel_to_uop.py +2 -8
- bloqade/qasm2/rewrite/register.py +2 -2
- bloqade/qasm2/rewrite/uop_to_parallel.py +4 -2
- bloqade/qbraid/lowering.py +1 -0
- bloqade/qbraid/schema.py +2 -2
- bloqade/qubit/__init__.py +12 -0
- bloqade/qubit/_dialect.py +3 -0
- bloqade/qubit/_interface.py +49 -0
- bloqade/qubit/_prelude.py +45 -0
- bloqade/qubit/analysis/__init__.py +1 -0
- bloqade/qubit/analysis/address_impl.py +40 -0
- bloqade/qubit/stdlib/__init__.py +2 -0
- bloqade/qubit/stdlib/_new.py +34 -0
- bloqade/qubit/stdlib/broadcast.py +62 -0
- bloqade/qubit/stdlib/simple.py +59 -0
- bloqade/qubit/stmts.py +60 -0
- bloqade/rewrite/passes/__init__.py +6 -0
- bloqade/rewrite/passes/aggressive_unroll.py +103 -0
- bloqade/rewrite/passes/callgraph.py +116 -0
- bloqade/rewrite/passes/canonicalize_ilist.py +20 -14
- bloqade/rewrite/rules/split_ifs.py +18 -1
- bloqade/squin/__init__.py +47 -14
- bloqade/squin/analysis/__init__.py +0 -1
- bloqade/squin/analysis/schedule.py +10 -11
- bloqade/squin/gate/__init__.py +2 -0
- bloqade/squin/gate/_dialect.py +3 -0
- bloqade/squin/gate/_interface.py +98 -0
- bloqade/squin/gate/stmts.py +125 -0
- bloqade/squin/groups.py +5 -22
- bloqade/squin/noise/__init__.py +1 -10
- bloqade/squin/noise/_dialect.py +1 -1
- bloqade/squin/noise/_interface.py +45 -0
- bloqade/squin/noise/stmts.py +66 -28
- bloqade/squin/rewrite/U3_to_clifford.py +70 -51
- bloqade/squin/rewrite/__init__.py +0 -2
- bloqade/squin/rewrite/remove_dangling_qubits.py +2 -2
- bloqade/squin/rewrite/wrap_analysis.py +4 -35
- bloqade/squin/stdlib/__init__.py +0 -0
- bloqade/squin/stdlib/broadcast/__init__.py +34 -0
- bloqade/squin/stdlib/broadcast/_qubit.py +4 -0
- bloqade/squin/stdlib/broadcast/gate.py +260 -0
- bloqade/squin/stdlib/broadcast/noise.py +144 -0
- bloqade/squin/stdlib/simple/__init__.py +33 -0
- bloqade/squin/stdlib/simple/gate.py +242 -0
- bloqade/squin/stdlib/simple/noise.py +126 -0
- bloqade/stim/__init__.py +1 -0
- bloqade/stim/_wrappers.py +6 -0
- bloqade/stim/dialects/auxiliary/emit.py +19 -18
- bloqade/stim/dialects/collapse/emit_str.py +7 -8
- bloqade/stim/dialects/gate/emit.py +9 -10
- bloqade/stim/dialects/noise/emit.py +17 -13
- bloqade/stim/dialects/noise/stmts.py +5 -3
- bloqade/stim/emit/__init__.py +1 -0
- bloqade/stim/emit/impls.py +16 -0
- bloqade/stim/emit/stim_str.py +48 -31
- bloqade/stim/groups.py +12 -2
- bloqade/stim/parse/lowering.py +14 -17
- bloqade/stim/passes/__init__.py +0 -2
- bloqade/stim/passes/flatten.py +26 -0
- bloqade/stim/passes/simplify_ifs.py +6 -1
- bloqade/stim/passes/squin_to_stim.py +9 -84
- bloqade/stim/rewrite/__init__.py +2 -4
- bloqade/stim/rewrite/get_record_util.py +24 -0
- bloqade/stim/rewrite/ifs_to_stim.py +24 -25
- bloqade/stim/rewrite/qubit_to_stim.py +90 -41
- bloqade/stim/rewrite/set_detector_to_stim.py +68 -0
- bloqade/stim/rewrite/set_observable_to_stim.py +52 -0
- bloqade/stim/rewrite/squin_measure.py +9 -18
- bloqade/stim/rewrite/squin_noise.py +134 -108
- bloqade/stim/rewrite/util.py +5 -192
- bloqade/test_utils.py +1 -1
- bloqade/types.py +10 -0
- bloqade/validation/__init__.py +2 -0
- bloqade/validation/analysis/__init__.py +5 -0
- bloqade/validation/analysis/analysis.py +41 -0
- bloqade/validation/analysis/lattice.py +58 -0
- bloqade/validation/kernel_validation.py +77 -0
- {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/METADATA +5 -6
- bloqade_circuit-0.9.1.dist-info/RECORD +265 -0
- bloqade/pyqrack/squin/op.py +0 -180
- bloqade/pyqrack/squin/runtime.py +0 -535
- bloqade/pyqrack/squin/wire.py +0 -51
- bloqade/rewrite/rules/flatten_ilist.py +0 -51
- bloqade/rewrite/rules/inline_getitem_ilist.py +0 -31
- bloqade/squin/_typeinfer.py +0 -20
- bloqade/squin/analysis/address_impl.py +0 -71
- bloqade/squin/analysis/nsites/__init__.py +0 -9
- bloqade/squin/analysis/nsites/analysis.py +0 -50
- bloqade/squin/analysis/nsites/impls.py +0 -92
- bloqade/squin/analysis/nsites/lattice.py +0 -49
- bloqade/squin/cirq/__init__.py +0 -280
- bloqade/squin/cirq/emit/emit_circuit.py +0 -109
- bloqade/squin/cirq/emit/noise.py +0 -49
- bloqade/squin/cirq/emit/op.py +0 -125
- bloqade/squin/cirq/emit/qubit.py +0 -60
- bloqade/squin/cirq/emit/runtime.py +0 -242
- bloqade/squin/cirq/lowering.py +0 -440
- bloqade/squin/lowering.py +0 -54
- bloqade/squin/noise/_wrapper.py +0 -40
- bloqade/squin/noise/rewrite.py +0 -111
- bloqade/squin/op/__init__.py +0 -41
- bloqade/squin/op/_dialect.py +0 -3
- bloqade/squin/op/_wrapper.py +0 -121
- bloqade/squin/op/number.py +0 -5
- bloqade/squin/op/rewrite.py +0 -46
- bloqade/squin/op/stdlib.py +0 -62
- bloqade/squin/op/stmts.py +0 -276
- bloqade/squin/op/traits.py +0 -43
- bloqade/squin/op/types.py +0 -26
- bloqade/squin/qubit.py +0 -184
- bloqade/squin/rewrite/canonicalize.py +0 -60
- bloqade/squin/rewrite/desugar.py +0 -124
- bloqade/squin/types.py +0 -8
- bloqade/squin/wire.py +0 -201
- bloqade/stim/rewrite/wire_identity_elimination.py +0 -24
- bloqade/stim/rewrite/wire_to_stim.py +0 -57
- bloqade_circuit-0.6.4.dist-info/RECORD +0 -234
- {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.6.4.dist-info → bloqade_circuit-0.9.1.dist-info}/licenses/LICENSE +0 -0
bloqade/squin/qubit.py
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
"""qubit dialect for squin language.
|
|
2
|
-
|
|
3
|
-
This dialect defines the operations that can be performed on qubits.
|
|
4
|
-
|
|
5
|
-
Depends on:
|
|
6
|
-
- `bloqade.squin.op`: provides the `OpType` type and semantics for operators applied to qubits.
|
|
7
|
-
- `kirin.dialects.ilist`: provides the `ilist.IListType` type for lists of qubits.
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from typing import Any, overload
|
|
11
|
-
|
|
12
|
-
from kirin import ir, types, lowering
|
|
13
|
-
from kirin.decl import info, statement
|
|
14
|
-
from kirin.dialects import ilist
|
|
15
|
-
from kirin.lowering import wraps
|
|
16
|
-
|
|
17
|
-
from bloqade.types import Qubit, QubitType
|
|
18
|
-
from bloqade.squin.op.types import Op, OpType
|
|
19
|
-
|
|
20
|
-
from .types import MeasurementResult, MeasurementResultType
|
|
21
|
-
from .lowering import ApplyAnyCallLowering
|
|
22
|
-
|
|
23
|
-
dialect = ir.Dialect("squin.qubit")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@statement(dialect=dialect)
|
|
27
|
-
class New(ir.Statement):
|
|
28
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
29
|
-
n_qubits: ir.SSAValue = info.argument(types.Int)
|
|
30
|
-
result: ir.ResultValue = info.result(ilist.IListType[QubitType, types.Any])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@statement(dialect=dialect)
|
|
34
|
-
class Apply(ir.Statement):
|
|
35
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
36
|
-
operator: ir.SSAValue = info.argument(OpType)
|
|
37
|
-
qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType])
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@statement(dialect=dialect)
|
|
41
|
-
class ApplyAny(ir.Statement):
|
|
42
|
-
# NOTE: custom lowering to deal with vararg calls
|
|
43
|
-
traits = frozenset({ApplyAnyCallLowering()})
|
|
44
|
-
operator: ir.SSAValue = info.argument(OpType)
|
|
45
|
-
qubits: tuple[ir.SSAValue, ...] = info.argument()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@statement(dialect=dialect)
|
|
49
|
-
class Broadcast(ir.Statement):
|
|
50
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
51
|
-
operator: ir.SSAValue = info.argument(OpType)
|
|
52
|
-
qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType])
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@statement(dialect=dialect)
|
|
56
|
-
class MeasureAny(ir.Statement):
|
|
57
|
-
name = "measure"
|
|
58
|
-
|
|
59
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
60
|
-
input: ir.SSAValue = info.argument(types.Any)
|
|
61
|
-
result: ir.ResultValue = info.result(types.Any)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
@statement(dialect=dialect)
|
|
65
|
-
class MeasureQubit(ir.Statement):
|
|
66
|
-
name = "measure.qubit"
|
|
67
|
-
|
|
68
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
69
|
-
qubit: ir.SSAValue = info.argument(QubitType)
|
|
70
|
-
result: ir.ResultValue = info.result(MeasurementResultType)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@statement(dialect=dialect)
|
|
74
|
-
class MeasureQubitList(ir.Statement):
|
|
75
|
-
name = "measure.qubit.list"
|
|
76
|
-
|
|
77
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
78
|
-
qubits: ir.SSAValue = info.argument(ilist.IListType[QubitType])
|
|
79
|
-
result: ir.ResultValue = info.result(ilist.IListType[MeasurementResultType])
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# NOTE: no dependent types in Python, so we have to mark it Any...
|
|
83
|
-
@wraps(New)
|
|
84
|
-
def new(n_qubits: int) -> ilist.IList[Qubit, Any]:
|
|
85
|
-
"""Create a new list of qubits.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
n_qubits(int): The number of qubits to create.
|
|
89
|
-
|
|
90
|
-
Returns:
|
|
91
|
-
(ilist.IList[Qubit, n_qubits]) A list of qubits.
|
|
92
|
-
"""
|
|
93
|
-
...
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
@overload
|
|
97
|
-
def apply(operator: Op, qubits: ilist.IList[Qubit, Any] | list[Qubit]) -> None:
|
|
98
|
-
"""Apply an operator to a list of qubits.
|
|
99
|
-
|
|
100
|
-
Note, that when considering atom loss, lost qubits will be skipped.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
operator: The operator to apply.
|
|
104
|
-
qubits: The list of qubits to apply the operator to. The size of the list
|
|
105
|
-
must be inferable and match the number of qubits expected by the operator.
|
|
106
|
-
|
|
107
|
-
Returns:
|
|
108
|
-
None
|
|
109
|
-
"""
|
|
110
|
-
...
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
@overload
|
|
114
|
-
def apply(operator: Op, *qubits: Qubit) -> None:
|
|
115
|
-
"""Apply and operator to any number of qubits.
|
|
116
|
-
|
|
117
|
-
Note, that when considering atom loss, lost qubits will be skipped.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
operator: The operator to apply.
|
|
121
|
-
*qubits: The qubits to apply the operator to. The number of qubits must
|
|
122
|
-
match the size of the operator.
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
None
|
|
126
|
-
"""
|
|
127
|
-
...
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@wraps(ApplyAny)
|
|
131
|
-
def apply(operator: Op, *qubits) -> None: ...
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
@overload
|
|
135
|
-
def measure(input: Qubit) -> MeasurementResult: ...
|
|
136
|
-
@overload
|
|
137
|
-
def measure(
|
|
138
|
-
input: ilist.IList[Qubit, Any] | list[Qubit],
|
|
139
|
-
) -> ilist.IList[MeasurementResult, Any]: ...
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
@wraps(MeasureAny)
|
|
143
|
-
def measure(input: Any) -> Any:
|
|
144
|
-
"""Measure a qubit or qubits in the list.
|
|
145
|
-
|
|
146
|
-
Args:
|
|
147
|
-
input: A qubit or a list of qubits to measure.
|
|
148
|
-
|
|
149
|
-
Returns:
|
|
150
|
-
bool | list[bool]: The result of the measurement. If a single qubit is measured,
|
|
151
|
-
a single boolean is returned. If a list of qubits is measured, a list of booleans
|
|
152
|
-
is returned.
|
|
153
|
-
"""
|
|
154
|
-
...
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
@wraps(Broadcast)
|
|
158
|
-
def broadcast(operator: Op, qubits: ilist.IList[Qubit, Any] | list[Qubit]) -> None:
|
|
159
|
-
"""Broadcast and apply an operator to a list of qubits. For example, an operator
|
|
160
|
-
that expects 2 qubits can be applied to a list of 2n qubits, where n is an integer > 0.
|
|
161
|
-
|
|
162
|
-
For controlled operators, the list of qubits is interpreted as sets of (controls, targets).
|
|
163
|
-
For example
|
|
164
|
-
|
|
165
|
-
```
|
|
166
|
-
apply(CX, [q0, q1, q2, q3])
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
is equivalent to
|
|
170
|
-
|
|
171
|
-
```
|
|
172
|
-
apply(CX, [q0, q1])
|
|
173
|
-
apply(CX, [q2, q3])
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Args:
|
|
177
|
-
operator: The operator to broadcast and apply.
|
|
178
|
-
qubits: The list of qubits to broadcast and apply the operator to. The size of the list
|
|
179
|
-
must be inferable and match the number of qubits expected by the operator.
|
|
180
|
-
|
|
181
|
-
Returns:
|
|
182
|
-
None
|
|
183
|
-
"""
|
|
184
|
-
...
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
from typing import cast
|
|
2
|
-
|
|
3
|
-
from kirin import ir
|
|
4
|
-
from kirin.rewrite import abc
|
|
5
|
-
from kirin.dialects import cf
|
|
6
|
-
|
|
7
|
-
from .. import wire
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class CanonicalizeWired(abc.RewriteRule):
|
|
11
|
-
def rewrite_Statement(self, node: ir.Statement) -> abc.RewriteResult:
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
not isinstance(node, wire.Wired)
|
|
15
|
-
or len(node.qubits) != 0
|
|
16
|
-
or (parent_region := node.parent_region) is None
|
|
17
|
-
):
|
|
18
|
-
return abc.RewriteResult()
|
|
19
|
-
|
|
20
|
-
parent_block = cast(ir.Block, node.parent_block)
|
|
21
|
-
|
|
22
|
-
# the body doesn't contain any quantum operations so we can safely inline the
|
|
23
|
-
# body into the parent block
|
|
24
|
-
|
|
25
|
-
# move all statements after `node` in the current block into another block
|
|
26
|
-
after_block = ir.Block()
|
|
27
|
-
|
|
28
|
-
stmt = node.next_stmt
|
|
29
|
-
while stmt is not None:
|
|
30
|
-
stmt.detach()
|
|
31
|
-
after_block.stmts.append(stmt)
|
|
32
|
-
stmt = node.next_stmt
|
|
33
|
-
|
|
34
|
-
# remap all results of the node to the arguments of the after_block
|
|
35
|
-
for result in node.results:
|
|
36
|
-
arg = after_block.args.append_from(result.type, result.name)
|
|
37
|
-
result.replace_by(arg)
|
|
38
|
-
|
|
39
|
-
parent_block_idx = parent_region._block_idx[parent_block]
|
|
40
|
-
# insert goto of parent block to the body block of the node.
|
|
41
|
-
parent_region.blocks.insert(parent_block_idx + 1, after_block)
|
|
42
|
-
# insert all blocks of the body of the node after the parent region
|
|
43
|
-
# making sure to convert any yield statements to jump statements to the after_block
|
|
44
|
-
parent_block.stmts.append(
|
|
45
|
-
cf.Branch(
|
|
46
|
-
arguments=(),
|
|
47
|
-
successor=node.body.blocks[0],
|
|
48
|
-
)
|
|
49
|
-
)
|
|
50
|
-
for block in reversed(node.body.blocks):
|
|
51
|
-
block.detach()
|
|
52
|
-
if isinstance((yield_stmt := block.last_stmt), wire.Yield):
|
|
53
|
-
yield_stmt.replace_by(
|
|
54
|
-
cf.Branch(yield_stmt.values, successor=after_block)
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
parent_region.blocks.insert(parent_block_idx + 1, block)
|
|
58
|
-
|
|
59
|
-
node.delete()
|
|
60
|
-
return abc.RewriteResult(has_done_something=True)
|
bloqade/squin/rewrite/desugar.py
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
from kirin import ir, types
|
|
2
|
-
from kirin.dialects import py, ilist
|
|
3
|
-
from kirin.rewrite.abc import RewriteRule, RewriteResult
|
|
4
|
-
|
|
5
|
-
from bloqade.squin.qubit import (
|
|
6
|
-
Apply,
|
|
7
|
-
ApplyAny,
|
|
8
|
-
QubitType,
|
|
9
|
-
MeasureAny,
|
|
10
|
-
MeasureQubit,
|
|
11
|
-
MeasureQubitList,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class MeasureDesugarRule(RewriteRule):
|
|
16
|
-
"""
|
|
17
|
-
Desugar measure operations in the circuit.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:
|
|
21
|
-
|
|
22
|
-
if not isinstance(node, MeasureAny):
|
|
23
|
-
return RewriteResult()
|
|
24
|
-
|
|
25
|
-
if node.input.type.is_subseteq(QubitType):
|
|
26
|
-
node.replace_by(
|
|
27
|
-
MeasureQubit(
|
|
28
|
-
qubit=node.input,
|
|
29
|
-
)
|
|
30
|
-
)
|
|
31
|
-
return RewriteResult(has_done_something=True)
|
|
32
|
-
elif node.input.type.is_subseteq(ilist.IListType[QubitType, types.Any]):
|
|
33
|
-
node.replace_by(
|
|
34
|
-
MeasureQubitList(
|
|
35
|
-
qubits=node.input,
|
|
36
|
-
)
|
|
37
|
-
)
|
|
38
|
-
return RewriteResult(has_done_something=True)
|
|
39
|
-
|
|
40
|
-
return RewriteResult()
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class ApplyDesugarRule(RewriteRule):
|
|
44
|
-
"""
|
|
45
|
-
Desugar apply operators in the kernel.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:
|
|
49
|
-
|
|
50
|
-
if not isinstance(node, ApplyAny):
|
|
51
|
-
return RewriteResult()
|
|
52
|
-
|
|
53
|
-
op = node.operator
|
|
54
|
-
qubits = node.qubits
|
|
55
|
-
|
|
56
|
-
if len(qubits) > 1 and all(q.type.is_subseteq(QubitType) for q in qubits):
|
|
57
|
-
(qubits_ilist_stmt := ilist.New(qubits)).insert_before(
|
|
58
|
-
node
|
|
59
|
-
) # qubits is just a tuple of SSAValues
|
|
60
|
-
qubits_ilist = qubits_ilist_stmt.result
|
|
61
|
-
|
|
62
|
-
elif len(qubits) == 1 and qubits[0].type.is_subseteq(QubitType):
|
|
63
|
-
(qubits_ilist_stmt := ilist.New(qubits)).insert_before(node)
|
|
64
|
-
qubits_ilist = qubits_ilist_stmt.result
|
|
65
|
-
|
|
66
|
-
elif len(qubits) == 1 and qubits[0].type.is_subseteq(
|
|
67
|
-
ilist.IListType[QubitType, types.Any]
|
|
68
|
-
):
|
|
69
|
-
qubits_ilist = qubits[0]
|
|
70
|
-
|
|
71
|
-
elif len(qubits) == 1:
|
|
72
|
-
# TODO: remove this elif clause once we're at kirin v0.18
|
|
73
|
-
# NOTE: this is a temporary workaround for kirin#408
|
|
74
|
-
# currently type inference fails here in for loops since the loop var
|
|
75
|
-
# is an IList for some reason
|
|
76
|
-
|
|
77
|
-
if not isinstance(qubits[0], ir.ResultValue):
|
|
78
|
-
return RewriteResult()
|
|
79
|
-
|
|
80
|
-
is_ilist = isinstance(qbit_stmt := qubits[0].stmt, ilist.New)
|
|
81
|
-
|
|
82
|
-
if is_ilist:
|
|
83
|
-
|
|
84
|
-
if not all(
|
|
85
|
-
isinstance(qbit_getindex_result, ir.ResultValue)
|
|
86
|
-
for qbit_getindex_result in qbit_stmt.values
|
|
87
|
-
):
|
|
88
|
-
return RewriteResult()
|
|
89
|
-
|
|
90
|
-
# Get the parent statement that the qubit came from
|
|
91
|
-
# (should be a GetItem instance, see logic below)
|
|
92
|
-
qbit_getindices = [
|
|
93
|
-
qbit_getindex_result.stmt
|
|
94
|
-
for qbit_getindex_result in qbit_stmt.values
|
|
95
|
-
]
|
|
96
|
-
else:
|
|
97
|
-
qbit_getindices = [qubit.stmt for qubit in qubits]
|
|
98
|
-
|
|
99
|
-
if any(
|
|
100
|
-
not isinstance(qbit_getindex, py.indexing.GetItem)
|
|
101
|
-
for qbit_getindex in qbit_getindices
|
|
102
|
-
):
|
|
103
|
-
return RewriteResult()
|
|
104
|
-
|
|
105
|
-
# The GetItem should have been applied on something that returns an IList of Qubits
|
|
106
|
-
if any(
|
|
107
|
-
not qbit_getindex.obj.type.is_subseteq(
|
|
108
|
-
ilist.IListType[QubitType, types.Any]
|
|
109
|
-
)
|
|
110
|
-
for qbit_getindex in qbit_getindices
|
|
111
|
-
):
|
|
112
|
-
return RewriteResult()
|
|
113
|
-
|
|
114
|
-
if is_ilist:
|
|
115
|
-
qubits_ilist = qbit_stmt.result
|
|
116
|
-
else:
|
|
117
|
-
(qubits_ilist_stmt := ilist.New(values=[qubits[0]])).insert_before(node)
|
|
118
|
-
qubits_ilist = qubits_ilist_stmt.result
|
|
119
|
-
else:
|
|
120
|
-
return RewriteResult()
|
|
121
|
-
|
|
122
|
-
stmt = Apply(operator=op, qubits=qubits_ilist)
|
|
123
|
-
node.replace_by(stmt)
|
|
124
|
-
return RewriteResult(has_done_something=True)
|
bloqade/squin/types.py
DELETED
bloqade/squin/wire.py
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
"""A NVIDIA QUAKE-like wire dialect.
|
|
2
|
-
|
|
3
|
-
This dialect is expected to be used in combination with the operator dialect
|
|
4
|
-
as an intermediate representation for analysis and optimization of quantum
|
|
5
|
-
circuits. Thus we do not define wrapping functions for the statements in this
|
|
6
|
-
dialect.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from kirin import ir, types, lowering, exception
|
|
10
|
-
from kirin.decl import info, statement
|
|
11
|
-
from kirin.dialects import func
|
|
12
|
-
from kirin.lowering import wraps
|
|
13
|
-
from kirin.ir.attrs.types import TypeAttribute
|
|
14
|
-
|
|
15
|
-
from bloqade.types import Qubit, QubitType
|
|
16
|
-
|
|
17
|
-
from .types import MeasurementResultType
|
|
18
|
-
from .op.types import Op, OpType
|
|
19
|
-
|
|
20
|
-
# from kirin.lowering import wraps
|
|
21
|
-
|
|
22
|
-
# from .op.types import Op, OpType
|
|
23
|
-
|
|
24
|
-
dialect = ir.Dialect("squin.wire")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
class WireTerminator(ir.StmtTrait):
|
|
28
|
-
pass
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class Wire:
|
|
32
|
-
pass
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
WireType = types.PyClass(Wire)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# no return value for `wrap`
|
|
39
|
-
@statement(dialect=dialect)
|
|
40
|
-
class Wrap(ir.Statement):
|
|
41
|
-
traits = frozenset({lowering.FromPythonCall(), WireTerminator()})
|
|
42
|
-
wire: ir.SSAValue = info.argument(WireType)
|
|
43
|
-
qubit: ir.SSAValue = info.argument(QubitType)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
# "Unwrap the quantum references to expose wires" -> From Quake Dialect documentation
|
|
47
|
-
# Unwrap(Qubit) -> Wire
|
|
48
|
-
@statement(dialect=dialect)
|
|
49
|
-
class Unwrap(ir.Statement):
|
|
50
|
-
traits = frozenset({lowering.FromPythonCall(), ir.Pure()})
|
|
51
|
-
qubit: ir.SSAValue = info.argument(QubitType)
|
|
52
|
-
result: ir.ResultValue = info.result(WireType)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
@statement(dialect=dialect)
|
|
56
|
-
class Wired(ir.Statement):
|
|
57
|
-
traits = frozenset()
|
|
58
|
-
|
|
59
|
-
qubits: tuple[ir.SSAValue, ...] = info.argument(QubitType)
|
|
60
|
-
memory_zone: str = info.attribute()
|
|
61
|
-
body: ir.Region = info.region(multi=True)
|
|
62
|
-
|
|
63
|
-
def __init__(
|
|
64
|
-
self,
|
|
65
|
-
body: ir.Region,
|
|
66
|
-
*qubits: ir.SSAValue,
|
|
67
|
-
memory_zone: str,
|
|
68
|
-
result_types: tuple[TypeAttribute, ...] | None = None,
|
|
69
|
-
):
|
|
70
|
-
if result_types is None:
|
|
71
|
-
for block in body.blocks:
|
|
72
|
-
if isinstance(block.last_stmt, Yield):
|
|
73
|
-
result_types = tuple(arg.type for arg in block.last_stmt.values)
|
|
74
|
-
break
|
|
75
|
-
|
|
76
|
-
if result_types is None:
|
|
77
|
-
result_types = ()
|
|
78
|
-
|
|
79
|
-
super().__init__(
|
|
80
|
-
args=qubits,
|
|
81
|
-
args_slice={
|
|
82
|
-
"qubits": slice(0, None),
|
|
83
|
-
},
|
|
84
|
-
regions=[body],
|
|
85
|
-
attributes={
|
|
86
|
-
"memory_zone": ir.PyAttr(memory_zone)
|
|
87
|
-
}, # body of the wired statement
|
|
88
|
-
result_types=result_types,
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
def check(self):
|
|
92
|
-
entry_block = self.body.blocks[0]
|
|
93
|
-
|
|
94
|
-
if len(entry_block.args) != len(self.qubits):
|
|
95
|
-
raise exception.StaticCheckError(
|
|
96
|
-
f"Expected {len(self.qubits)} arguments, got {len(entry_block.args)}."
|
|
97
|
-
)
|
|
98
|
-
for arg in entry_block.args:
|
|
99
|
-
if not arg.type.is_subseteq(WireType):
|
|
100
|
-
raise exception.StaticCheckError(
|
|
101
|
-
f"Expected argument of type {WireType}, got {arg.type}."
|
|
102
|
-
)
|
|
103
|
-
for block in self.body.blocks:
|
|
104
|
-
last_stmt = block.last_stmt
|
|
105
|
-
if isinstance(last_stmt, func.Return):
|
|
106
|
-
raise exception.StaticCheckError(
|
|
107
|
-
"Return statements are not allowed in the body of a Wired statement."
|
|
108
|
-
)
|
|
109
|
-
elif isinstance(last_stmt, Yield) and len(last_stmt.values) != len(
|
|
110
|
-
self.results
|
|
111
|
-
):
|
|
112
|
-
raise exception.StaticCheckError(
|
|
113
|
-
f"Expected {len(self.results)} return values, got {len(last_stmt.values)}."
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
@statement(dialect=dialect)
|
|
118
|
-
class Yield(ir.Statement):
|
|
119
|
-
traits = frozenset({})
|
|
120
|
-
values: tuple[ir.SSAValue, ...] = info.argument(WireType)
|
|
121
|
-
|
|
122
|
-
def __init__(self, *args: ir.SSAValue):
|
|
123
|
-
super().__init__(
|
|
124
|
-
args=args,
|
|
125
|
-
args_slice={
|
|
126
|
-
"values": slice(0, None),
|
|
127
|
-
},
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
# In Quake, you put a wire in and get a wire out when you "apply" an operator
|
|
132
|
-
# In this case though we just need to indicate that an operator is applied to list[wires]
|
|
133
|
-
@statement(dialect=dialect)
|
|
134
|
-
class Apply(ir.Statement): # apply(op, w1, w2, ...)
|
|
135
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
136
|
-
operator: ir.SSAValue = info.argument(OpType)
|
|
137
|
-
inputs: tuple[ir.SSAValue, ...] = info.argument(WireType)
|
|
138
|
-
|
|
139
|
-
def __init__(self, operator: ir.SSAValue, *args: ir.SSAValue):
|
|
140
|
-
result_types = tuple(WireType for _ in args)
|
|
141
|
-
super().__init__(
|
|
142
|
-
args=(operator,) + args,
|
|
143
|
-
result_types=result_types, # result types of the Apply statement, should all be WireTypes
|
|
144
|
-
args_slice={
|
|
145
|
-
"operator": 0,
|
|
146
|
-
"inputs": slice(1, None),
|
|
147
|
-
}, # pretty printing + syntax sugar
|
|
148
|
-
) # custom lowering required for wrapper to work here
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
# Carry over from Qubit dialect
|
|
152
|
-
@statement(dialect=dialect)
|
|
153
|
-
class Broadcast(ir.Statement):
|
|
154
|
-
traits = frozenset({lowering.FromPythonCall(), ir.Pure()})
|
|
155
|
-
operator: ir.SSAValue = info.argument(OpType)
|
|
156
|
-
inputs: tuple[ir.SSAValue, ...] = info.argument(WireType)
|
|
157
|
-
|
|
158
|
-
def __init__(self, operator: ir.SSAValue, *args: ir.SSAValue):
|
|
159
|
-
result_types = tuple(WireType for _ in args)
|
|
160
|
-
super().__init__(
|
|
161
|
-
args=(operator,) + args,
|
|
162
|
-
result_types=result_types,
|
|
163
|
-
args_slice={
|
|
164
|
-
"operator": 0,
|
|
165
|
-
"inputs": slice(1, None),
|
|
166
|
-
}, # pretty printing + syntax sugar
|
|
167
|
-
) # custom lowering required for wrapper to work here
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
@statement(dialect=dialect)
|
|
171
|
-
class RegionMeasure(ir.Statement):
|
|
172
|
-
traits = frozenset({lowering.FromPythonCall(), WireTerminator()})
|
|
173
|
-
wire: ir.SSAValue = info.argument(WireType)
|
|
174
|
-
result: ir.ResultValue = info.result(MeasurementResultType)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# NOTE: measurement cannot be pure because they will collapse the state
|
|
178
|
-
# of the qubit. The state is a hidden state that is not visible to
|
|
179
|
-
# the user in the wire dialect.
|
|
180
|
-
@statement(dialect=dialect)
|
|
181
|
-
class Measure(ir.Statement):
|
|
182
|
-
traits = frozenset({lowering.FromPythonCall(), WireTerminator()})
|
|
183
|
-
wire: ir.SSAValue = info.argument(WireType)
|
|
184
|
-
qubit: ir.SSAValue = info.argument(QubitType)
|
|
185
|
-
result: ir.ResultValue = info.result(MeasurementResultType)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
@statement(dialect=dialect)
|
|
189
|
-
class LossResolvingMeasure(ir.Statement):
|
|
190
|
-
traits = frozenset({lowering.FromPythonCall()})
|
|
191
|
-
input_wire: ir.SSAValue = info.argument(WireType)
|
|
192
|
-
result: ir.ResultValue = info.result(MeasurementResultType)
|
|
193
|
-
out_wire: ir.ResultValue = info.result(WireType)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@wraps(Unwrap)
|
|
197
|
-
def unwrap(qubit: Qubit) -> Wire: ...
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
@wraps(Apply)
|
|
201
|
-
def apply(op: Op, w: Wire) -> Wire: ...
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from kirin import ir
|
|
2
|
-
from kirin.rewrite.abc import RewriteRule, RewriteResult
|
|
3
|
-
|
|
4
|
-
from bloqade.squin import wire
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class SquinWireIdentityElimination(RewriteRule):
|
|
8
|
-
|
|
9
|
-
def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:
|
|
10
|
-
"""
|
|
11
|
-
Handle the case where an unwrap feeds a wire directly into a wrap,
|
|
12
|
-
equivalent to nothing happening/identity operation
|
|
13
|
-
|
|
14
|
-
w = unwrap(qubit)
|
|
15
|
-
wrap(qubit, w)
|
|
16
|
-
"""
|
|
17
|
-
if isinstance(node, wire.Wrap):
|
|
18
|
-
wire_origin_stmt = node.wire.owner
|
|
19
|
-
if isinstance(wire_origin_stmt, wire.Unwrap):
|
|
20
|
-
node.delete() # get rid of wrap
|
|
21
|
-
wire_origin_stmt.delete() # get rid of the unwrap
|
|
22
|
-
return RewriteResult(has_done_something=True)
|
|
23
|
-
|
|
24
|
-
return RewriteResult()
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
from kirin import ir
|
|
2
|
-
from kirin.rewrite.abc import RewriteRule, RewriteResult
|
|
3
|
-
|
|
4
|
-
from bloqade.squin import op, wire, noise
|
|
5
|
-
from bloqade.stim.rewrite.util import (
|
|
6
|
-
SQUIN_STIM_OP_MAPPING,
|
|
7
|
-
rewrite_Control,
|
|
8
|
-
rewrite_QubitLoss,
|
|
9
|
-
insert_qubit_idx_from_wire_ssa,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class SquinWireToStim(RewriteRule):
|
|
14
|
-
|
|
15
|
-
def rewrite_Statement(self, node: ir.Statement) -> RewriteResult:
|
|
16
|
-
match node:
|
|
17
|
-
case wire.Apply() | wire.Broadcast():
|
|
18
|
-
return self.rewrite_Apply_and_Broadcast(node)
|
|
19
|
-
case _:
|
|
20
|
-
return RewriteResult()
|
|
21
|
-
|
|
22
|
-
def rewrite_Apply_and_Broadcast(
|
|
23
|
-
self, stmt: wire.Apply | wire.Broadcast
|
|
24
|
-
) -> RewriteResult:
|
|
25
|
-
|
|
26
|
-
# this is an SSAValue, need it to be the actual operator
|
|
27
|
-
applied_op = stmt.operator.owner
|
|
28
|
-
|
|
29
|
-
if isinstance(applied_op, noise.stmts.QubitLoss):
|
|
30
|
-
return rewrite_QubitLoss(stmt)
|
|
31
|
-
|
|
32
|
-
assert isinstance(applied_op, op.stmts.Operator)
|
|
33
|
-
|
|
34
|
-
if isinstance(applied_op, op.stmts.Control):
|
|
35
|
-
return rewrite_Control(stmt)
|
|
36
|
-
|
|
37
|
-
stim_1q_op = SQUIN_STIM_OP_MAPPING.get(type(applied_op))
|
|
38
|
-
if stim_1q_op is None:
|
|
39
|
-
return RewriteResult()
|
|
40
|
-
|
|
41
|
-
qubit_idx_ssas = insert_qubit_idx_from_wire_ssa(
|
|
42
|
-
wire_ssas=stmt.inputs, stmt_to_insert_before=stmt
|
|
43
|
-
)
|
|
44
|
-
if qubit_idx_ssas is None:
|
|
45
|
-
return RewriteResult()
|
|
46
|
-
|
|
47
|
-
stim_1q_stmt = stim_1q_op(targets=tuple(qubit_idx_ssas))
|
|
48
|
-
|
|
49
|
-
# Get the wires from the inputs of Apply or Broadcast,
|
|
50
|
-
# then put those as the result of the current stmt
|
|
51
|
-
# before replacing it entirely
|
|
52
|
-
for input_wire, output_wire in zip(stmt.inputs, stmt.results):
|
|
53
|
-
output_wire.replace_by(input_wire)
|
|
54
|
-
|
|
55
|
-
stmt.replace_by(stim_1q_stmt)
|
|
56
|
-
|
|
57
|
-
return RewriteResult(has_done_something=True)
|