bloqade-circuit 0.1.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.
- bloqade/analysis/__init__.py +0 -0
- bloqade/analysis/address/__init__.py +11 -0
- bloqade/analysis/address/analysis.py +60 -0
- bloqade/analysis/address/impls.py +228 -0
- bloqade/analysis/address/lattice.py +85 -0
- bloqade/noise/__init__.py +1 -0
- bloqade/noise/native/__init__.py +20 -0
- bloqade/noise/native/_dialect.py +3 -0
- bloqade/noise/native/_wrappers.py +34 -0
- bloqade/noise/native/model.py +347 -0
- bloqade/noise/native/rewrite.py +35 -0
- bloqade/noise/native/stmts.py +46 -0
- bloqade/pyqrack/__init__.py +18 -0
- bloqade/pyqrack/base.py +131 -0
- bloqade/pyqrack/noise/__init__.py +0 -0
- bloqade/pyqrack/noise/native.py +100 -0
- bloqade/pyqrack/qasm2/__init__.py +0 -0
- bloqade/pyqrack/qasm2/core.py +79 -0
- bloqade/pyqrack/qasm2/parallel.py +46 -0
- bloqade/pyqrack/qasm2/uop.py +247 -0
- bloqade/pyqrack/reg.py +109 -0
- bloqade/pyqrack/target.py +112 -0
- bloqade/qasm2/__init__.py +19 -0
- bloqade/qasm2/_wrappers.py +674 -0
- bloqade/qasm2/dialects/__init__.py +10 -0
- bloqade/qasm2/dialects/core/__init__.py +3 -0
- bloqade/qasm2/dialects/core/_dialect.py +3 -0
- bloqade/qasm2/dialects/core/_emit.py +68 -0
- bloqade/qasm2/dialects/core/_typeinfer.py +23 -0
- bloqade/qasm2/dialects/core/address.py +38 -0
- bloqade/qasm2/dialects/core/stmts.py +94 -0
- bloqade/qasm2/dialects/expr/__init__.py +3 -0
- bloqade/qasm2/dialects/expr/_dialect.py +3 -0
- bloqade/qasm2/dialects/expr/_emit.py +103 -0
- bloqade/qasm2/dialects/expr/_from_python.py +86 -0
- bloqade/qasm2/dialects/expr/_interp.py +75 -0
- bloqade/qasm2/dialects/expr/stmts.py +262 -0
- bloqade/qasm2/dialects/glob.py +45 -0
- bloqade/qasm2/dialects/indexing.py +64 -0
- bloqade/qasm2/dialects/inline.py +76 -0
- bloqade/qasm2/dialects/noise.py +16 -0
- bloqade/qasm2/dialects/parallel.py +110 -0
- bloqade/qasm2/dialects/uop/__init__.py +4 -0
- bloqade/qasm2/dialects/uop/_dialect.py +3 -0
- bloqade/qasm2/dialects/uop/_emit.py +211 -0
- bloqade/qasm2/dialects/uop/schedule.py +89 -0
- bloqade/qasm2/dialects/uop/stmts.py +325 -0
- bloqade/qasm2/emit/__init__.py +1 -0
- bloqade/qasm2/emit/base.py +72 -0
- bloqade/qasm2/emit/gate.py +102 -0
- bloqade/qasm2/emit/main.py +106 -0
- bloqade/qasm2/emit/target.py +165 -0
- bloqade/qasm2/glob.py +24 -0
- bloqade/qasm2/groups.py +120 -0
- bloqade/qasm2/parallel.py +48 -0
- bloqade/qasm2/parse/__init__.py +37 -0
- bloqade/qasm2/parse/ast.py +235 -0
- bloqade/qasm2/parse/build.py +289 -0
- bloqade/qasm2/parse/lowering.py +553 -0
- bloqade/qasm2/parse/parser.py +5 -0
- bloqade/qasm2/parse/print.py +293 -0
- bloqade/qasm2/parse/qasm2.lark +75 -0
- bloqade/qasm2/parse/visitor.py +16 -0
- bloqade/qasm2/parse/visitor.pyi +39 -0
- bloqade/qasm2/passes/__init__.py +5 -0
- bloqade/qasm2/passes/fold.py +94 -0
- bloqade/qasm2/passes/glob.py +119 -0
- bloqade/qasm2/passes/noise.py +61 -0
- bloqade/qasm2/passes/parallel.py +176 -0
- bloqade/qasm2/passes/py2qasm.py +63 -0
- bloqade/qasm2/passes/qasm2py.py +61 -0
- bloqade/qasm2/rewrite/__init__.py +12 -0
- bloqade/qasm2/rewrite/desugar.py +28 -0
- bloqade/qasm2/rewrite/glob.py +103 -0
- bloqade/qasm2/rewrite/heuristic_noise.py +247 -0
- bloqade/qasm2/rewrite/native_gates.py +447 -0
- bloqade/qasm2/rewrite/parallel_to_uop.py +83 -0
- bloqade/qasm2/rewrite/register.py +45 -0
- bloqade/qasm2/rewrite/uop_to_parallel.py +395 -0
- bloqade/qasm2/types.py +39 -0
- bloqade/qbraid/__init__.py +2 -0
- bloqade/qbraid/lowering.py +324 -0
- bloqade/qbraid/schema.py +252 -0
- bloqade/qbraid/simulation_result.py +99 -0
- bloqade/qbraid/target.py +86 -0
- bloqade/squin/__init__.py +2 -0
- bloqade/squin/analysis/__init__.py +0 -0
- bloqade/squin/analysis/nsites/__init__.py +8 -0
- bloqade/squin/analysis/nsites/analysis.py +52 -0
- bloqade/squin/analysis/nsites/impls.py +69 -0
- bloqade/squin/analysis/nsites/lattice.py +49 -0
- bloqade/squin/analysis/schedule.py +244 -0
- bloqade/squin/groups.py +38 -0
- bloqade/squin/op/__init__.py +132 -0
- bloqade/squin/op/_dialect.py +3 -0
- bloqade/squin/op/complex.py +6 -0
- bloqade/squin/op/stmts.py +220 -0
- bloqade/squin/op/traits.py +43 -0
- bloqade/squin/op/types.py +10 -0
- bloqade/squin/qubit.py +118 -0
- bloqade/squin/wire.py +103 -0
- bloqade/stim/__init__.py +6 -0
- bloqade/stim/_wrappers.py +186 -0
- bloqade/stim/dialects/__init__.py +5 -0
- bloqade/stim/dialects/aux/__init__.py +11 -0
- bloqade/stim/dialects/aux/_dialect.py +3 -0
- bloqade/stim/dialects/aux/emit.py +102 -0
- bloqade/stim/dialects/aux/interp.py +39 -0
- bloqade/stim/dialects/aux/lowering.py +40 -0
- bloqade/stim/dialects/aux/stmts/__init__.py +14 -0
- bloqade/stim/dialects/aux/stmts/annotate.py +47 -0
- bloqade/stim/dialects/aux/stmts/const.py +95 -0
- bloqade/stim/dialects/aux/types.py +19 -0
- bloqade/stim/dialects/collapse/__init__.py +3 -0
- bloqade/stim/dialects/collapse/_dialect.py +3 -0
- bloqade/stim/dialects/collapse/emit.py +68 -0
- bloqade/stim/dialects/collapse/stmts/__init__.py +3 -0
- bloqade/stim/dialects/collapse/stmts/measure.py +45 -0
- bloqade/stim/dialects/collapse/stmts/pp_measure.py +14 -0
- bloqade/stim/dialects/collapse/stmts/reset.py +26 -0
- bloqade/stim/dialects/gate/__init__.py +3 -0
- bloqade/stim/dialects/gate/_dialect.py +3 -0
- bloqade/stim/dialects/gate/emit.py +87 -0
- bloqade/stim/dialects/gate/stmts/__init__.py +14 -0
- bloqade/stim/dialects/gate/stmts/base.py +31 -0
- bloqade/stim/dialects/gate/stmts/clifford_1q.py +53 -0
- bloqade/stim/dialects/gate/stmts/clifford_2q.py +11 -0
- bloqade/stim/dialects/gate/stmts/control_2q.py +21 -0
- bloqade/stim/dialects/gate/stmts/pp.py +15 -0
- bloqade/stim/dialects/noise/__init__.py +3 -0
- bloqade/stim/dialects/noise/_dialect.py +3 -0
- bloqade/stim/dialects/noise/emit.py +66 -0
- bloqade/stim/dialects/noise/stmts.py +77 -0
- bloqade/stim/emit/__init__.py +1 -0
- bloqade/stim/emit/stim.py +54 -0
- bloqade/stim/groups.py +26 -0
- bloqade/test_utils.py +35 -0
- bloqade/types.py +24 -0
- bloqade/visual/__init__.py +1 -0
- bloqade/visual/animation/__init__.py +0 -0
- bloqade/visual/animation/animate.py +267 -0
- bloqade/visual/animation/base.py +346 -0
- bloqade/visual/animation/gate_event.py +24 -0
- bloqade/visual/animation/runtime/__init__.py +0 -0
- bloqade/visual/animation/runtime/aod.py +36 -0
- bloqade/visual/animation/runtime/atoms.py +55 -0
- bloqade/visual/animation/runtime/ppoly.py +50 -0
- bloqade/visual/animation/runtime/qpustate.py +119 -0
- bloqade/visual/animation/runtime/utils.py +43 -0
- bloqade_circuit-0.1.0.dist-info/METADATA +70 -0
- bloqade_circuit-0.1.0.dist-info/RECORD +153 -0
- bloqade_circuit-0.1.0.dist-info/WHEEL +4 -0
- bloqade_circuit-0.1.0.dist-info/licenses/LICENSE +234 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
from typing import Dict, List, Tuple, Sequence
|
|
2
|
+
from dataclasses import field, dataclass
|
|
3
|
+
|
|
4
|
+
from kirin import ir, types, passes
|
|
5
|
+
from kirin.dialects import func, ilist
|
|
6
|
+
|
|
7
|
+
from bloqade import noise, qasm2
|
|
8
|
+
from bloqade.qbraid import schema
|
|
9
|
+
from bloqade.qasm2.dialects import glob, parallel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@ir.dialect_group(
|
|
13
|
+
[func, qasm2.core, qasm2.uop, parallel, glob, qasm2.expr, noise.native, ilist]
|
|
14
|
+
)
|
|
15
|
+
def qbraid_noise(
|
|
16
|
+
self,
|
|
17
|
+
):
|
|
18
|
+
fold_pass = passes.Fold(self)
|
|
19
|
+
typeinfer_pass = passes.TypeInfer(self)
|
|
20
|
+
|
|
21
|
+
def run_pass(
|
|
22
|
+
method: ir.Method,
|
|
23
|
+
*,
|
|
24
|
+
fold: bool = True,
|
|
25
|
+
):
|
|
26
|
+
method.verify()
|
|
27
|
+
|
|
28
|
+
if fold:
|
|
29
|
+
fold_pass(method)
|
|
30
|
+
|
|
31
|
+
typeinfer_pass(method)
|
|
32
|
+
method.code.verify_type()
|
|
33
|
+
|
|
34
|
+
return run_pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class Lowering:
|
|
39
|
+
qubit_list: List[ir.SSAValue] = field(init=False, default_factory=list)
|
|
40
|
+
qubit_id_map: Dict[int, ir.SSAValue] = field(init=False, default_factory=dict)
|
|
41
|
+
bit_id_map: Dict[int, ir.SSAValue] = field(init=False, default_factory=dict)
|
|
42
|
+
block_list: List[ir.Statement] = field(init=False, default_factory=list)
|
|
43
|
+
|
|
44
|
+
def lower(
|
|
45
|
+
self,
|
|
46
|
+
sym_name: str,
|
|
47
|
+
noise_model: schema.NoiseModel,
|
|
48
|
+
return_qreg: bool = False,
|
|
49
|
+
) -> ir.Method:
|
|
50
|
+
"""Lower the noise model to a method.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
name (str): The name of the method to generate.
|
|
54
|
+
return_qreg (bool): Use the quantum register as the return value.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Method: The generated kirin method.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
self.process_noise_model(noise_model, return_qreg)
|
|
61
|
+
block = ir.Block(stmts=self.block_list)
|
|
62
|
+
ret_type = qasm2.types.QRegType if return_qreg else qasm2.types.CRegType
|
|
63
|
+
block.args.append_from(types.MethodType[[], ret_type], name=f"{sym_name}_self")
|
|
64
|
+
region = ir.Region(block)
|
|
65
|
+
func_stmt = func.Function(
|
|
66
|
+
sym_name=sym_name,
|
|
67
|
+
signature=func.Signature(inputs=(), output=qasm2.types.QRegType),
|
|
68
|
+
body=region,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
mt = ir.Method(
|
|
72
|
+
mod=None,
|
|
73
|
+
py_func=None,
|
|
74
|
+
sym_name=sym_name,
|
|
75
|
+
dialects=qbraid_noise,
|
|
76
|
+
code=func_stmt,
|
|
77
|
+
arg_names=[],
|
|
78
|
+
)
|
|
79
|
+
qbraid_noise.run_pass(mt) # type: ignore
|
|
80
|
+
return mt
|
|
81
|
+
|
|
82
|
+
def process_noise_model(self, noise_model: schema.NoiseModel, return_qreg: bool):
|
|
83
|
+
num_qubits = self.lower_number(noise_model.num_qubits)
|
|
84
|
+
|
|
85
|
+
reg = qasm2.core.QRegNew(num_qubits)
|
|
86
|
+
creg = qasm2.core.CRegNew(num_qubits)
|
|
87
|
+
self.block_list.append(reg)
|
|
88
|
+
self.block_list.append(creg)
|
|
89
|
+
|
|
90
|
+
for idx_value, qubit in enumerate(noise_model.all_qubits):
|
|
91
|
+
idx = self.lower_number(idx_value)
|
|
92
|
+
self.block_list.append(qubit_stmt := qasm2.core.QRegGet(reg.result, idx))
|
|
93
|
+
self.block_list.append(bit_stmt := qasm2.core.CRegGet(creg.result, idx))
|
|
94
|
+
|
|
95
|
+
self.qubit_id_map[qubit] = qubit_stmt.result
|
|
96
|
+
self.bit_id_map[qubit] = bit_stmt.result
|
|
97
|
+
self.qubit_list.append(qubit_stmt.result)
|
|
98
|
+
|
|
99
|
+
for gate_event in noise_model.gate_events:
|
|
100
|
+
self.process_gate_event(gate_event)
|
|
101
|
+
|
|
102
|
+
if return_qreg:
|
|
103
|
+
self.block_list.append(func.Return(reg.result))
|
|
104
|
+
else:
|
|
105
|
+
self.block_list.append(func.Return(creg.result))
|
|
106
|
+
|
|
107
|
+
def process_gate_event(self, node: schema.GateEvent):
|
|
108
|
+
self.lower_atom_loss(node.error.survival_prob)
|
|
109
|
+
|
|
110
|
+
if isinstance(node.operation, schema.CZ):
|
|
111
|
+
assert isinstance(node.error, schema.CZError), "Only CZError is supported"
|
|
112
|
+
self.process_cz_pauli_error(node.operation.participants, node.error)
|
|
113
|
+
self.lower_cz_gates(node.operation)
|
|
114
|
+
else:
|
|
115
|
+
|
|
116
|
+
error = node.error
|
|
117
|
+
assert isinstance(
|
|
118
|
+
error, schema.SingleQubitError
|
|
119
|
+
), "Only SingleQubitError is supported"
|
|
120
|
+
self.lower_pauli_errors(error.operator_error)
|
|
121
|
+
|
|
122
|
+
operation = node.operation
|
|
123
|
+
assert isinstance(
|
|
124
|
+
operation,
|
|
125
|
+
(
|
|
126
|
+
schema.GlobalW,
|
|
127
|
+
schema.LocalW,
|
|
128
|
+
schema.GlobalRz,
|
|
129
|
+
schema.LocalRz,
|
|
130
|
+
schema.Measurement,
|
|
131
|
+
),
|
|
132
|
+
), f"Only W and Rz gates are supported, found {type(operation)}.__name__"
|
|
133
|
+
|
|
134
|
+
if isinstance(operation, schema.GlobalW):
|
|
135
|
+
self.lower_w_gates(
|
|
136
|
+
tuple(self.qubit_id_map.keys()), operation.theta, operation.phi
|
|
137
|
+
)
|
|
138
|
+
elif isinstance(operation, schema.LocalW):
|
|
139
|
+
self.lower_w_gates(
|
|
140
|
+
operation.participants, operation.theta, operation.phi
|
|
141
|
+
)
|
|
142
|
+
elif isinstance(operation, schema.GlobalRz):
|
|
143
|
+
self.lower_rz_gates(tuple(self.qubit_id_map.keys()), operation.phi)
|
|
144
|
+
elif isinstance(operation, schema.LocalRz):
|
|
145
|
+
self.lower_rz_gates(operation.participants, operation.phi)
|
|
146
|
+
elif isinstance(operation, schema.Measurement):
|
|
147
|
+
self.lower_measurement(operation)
|
|
148
|
+
|
|
149
|
+
def process_cz_pauli_error(
|
|
150
|
+
self,
|
|
151
|
+
participants: Tuple[Tuple[int] | Tuple[int, int], ...],
|
|
152
|
+
node: schema.CZError[schema.PauliErrorModel],
|
|
153
|
+
):
|
|
154
|
+
|
|
155
|
+
storage_error = node.storage_error
|
|
156
|
+
single_error = node.single_error
|
|
157
|
+
entangled_error = node.entangled_error
|
|
158
|
+
|
|
159
|
+
assert isinstance(
|
|
160
|
+
storage_error, schema.PauliErrorModel
|
|
161
|
+
), "Only PauliErrorModel is supported"
|
|
162
|
+
assert isinstance(
|
|
163
|
+
single_error, schema.PauliErrorModel
|
|
164
|
+
), "Only PauliErrorModel is supported"
|
|
165
|
+
assert isinstance(
|
|
166
|
+
entangled_error, schema.PauliErrorModel
|
|
167
|
+
), "Only PauliErrorModel is supported"
|
|
168
|
+
|
|
169
|
+
self.lower_pauli_errors(storage_error)
|
|
170
|
+
|
|
171
|
+
single_error_dict = dict(single_error.errors)
|
|
172
|
+
entangled_error_dict = dict(entangled_error.errors)
|
|
173
|
+
|
|
174
|
+
single_layers = {}
|
|
175
|
+
paired_layers = {}
|
|
176
|
+
unpaired_layers = {}
|
|
177
|
+
|
|
178
|
+
for participant in participants:
|
|
179
|
+
if len(participant) == 1:
|
|
180
|
+
p_single = single_error_dict[participant[0]]
|
|
181
|
+
single_layers.setdefault(p_single, []).append(participant[0])
|
|
182
|
+
elif len(participant) == 2:
|
|
183
|
+
p_ctrl = entangled_error_dict[participant[0]]
|
|
184
|
+
up_ctrl = single_error_dict[participant[0]]
|
|
185
|
+
p_qarg = entangled_error_dict[participant[1]]
|
|
186
|
+
up_qarg = single_error_dict[participant[1]]
|
|
187
|
+
paired_layers.setdefault((p_ctrl, p_qarg), []).append(participant)
|
|
188
|
+
unpaired_layers.setdefault((up_ctrl, up_qarg), []).append(participant)
|
|
189
|
+
|
|
190
|
+
for (px, py, pz), qubits in single_layers.items():
|
|
191
|
+
self.block_list.append(
|
|
192
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qubits))
|
|
193
|
+
)
|
|
194
|
+
self.block_list.append(
|
|
195
|
+
noise.native.PauliChannel(px=px, py=py, pz=pz, qargs=qargs.result)
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
for (p_ctrl, p_qarg), qubits in paired_layers.items():
|
|
199
|
+
ctrls, qargs = list(zip(*qubits))
|
|
200
|
+
self.block_list.append(
|
|
201
|
+
ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
|
|
202
|
+
)
|
|
203
|
+
self.block_list.append(
|
|
204
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
|
|
205
|
+
)
|
|
206
|
+
self.block_list.append(
|
|
207
|
+
noise.native.CZPauliChannel(
|
|
208
|
+
paired=True,
|
|
209
|
+
px_ctrl=p_ctrl[0],
|
|
210
|
+
py_ctrl=p_ctrl[1],
|
|
211
|
+
pz_ctrl=p_ctrl[2],
|
|
212
|
+
px_qarg=p_qarg[0],
|
|
213
|
+
py_qarg=p_qarg[1],
|
|
214
|
+
pz_qarg=p_qarg[2],
|
|
215
|
+
ctrls=ctrls.result,
|
|
216
|
+
qargs=qargs.result,
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
for (p_ctrl, p_qarg), qubits in unpaired_layers.items():
|
|
221
|
+
ctrls, qargs = list(zip(*qubits))
|
|
222
|
+
self.block_list.append(
|
|
223
|
+
ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
|
|
224
|
+
)
|
|
225
|
+
self.block_list.append(
|
|
226
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
|
|
227
|
+
)
|
|
228
|
+
self.block_list.append(
|
|
229
|
+
noise.native.CZPauliChannel(
|
|
230
|
+
paired=False,
|
|
231
|
+
px_ctrl=p_ctrl[0],
|
|
232
|
+
py_ctrl=p_ctrl[1],
|
|
233
|
+
pz_ctrl=p_ctrl[2],
|
|
234
|
+
px_qarg=p_qarg[0],
|
|
235
|
+
py_qarg=p_qarg[1],
|
|
236
|
+
pz_qarg=p_qarg[2],
|
|
237
|
+
ctrls=ctrls.result,
|
|
238
|
+
qargs=qargs.result,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def lower_cz_gates(self, node: schema.CZ):
|
|
243
|
+
ctrls, qargs = list(zip(*(p for p in node.participants if len(p) == 2)))
|
|
244
|
+
self.block_list.append(
|
|
245
|
+
ctrls := ilist.New(values=tuple(self.qubit_id_map[q] for q in ctrls))
|
|
246
|
+
)
|
|
247
|
+
self.block_list.append(
|
|
248
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qargs))
|
|
249
|
+
)
|
|
250
|
+
self.block_list.append(parallel.CZ(ctrls=ctrls.result, qargs=qargs.result))
|
|
251
|
+
|
|
252
|
+
def lower_w_gates(self, participants: Sequence[int], theta: float, phi: float):
|
|
253
|
+
self.block_list.append(
|
|
254
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in participants))
|
|
255
|
+
)
|
|
256
|
+
self.block_list.append(
|
|
257
|
+
parallel.UGate(
|
|
258
|
+
theta=self.lower_full_turns(theta),
|
|
259
|
+
phi=self.lower_full_turns(phi + 0.5),
|
|
260
|
+
lam=self.lower_full_turns(-(0.5 + phi)),
|
|
261
|
+
qargs=qargs.result,
|
|
262
|
+
)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def lower_rz_gates(self, participants: Tuple[int, ...], phi: float):
|
|
266
|
+
self.block_list.append(
|
|
267
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in participants))
|
|
268
|
+
)
|
|
269
|
+
self.block_list.append(
|
|
270
|
+
parallel.RZ(theta=self.lower_full_turns(phi), qargs=qargs.result)
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def lower_pauli_errors(self, operator_error: schema.PauliErrorModel):
|
|
274
|
+
assert isinstance(
|
|
275
|
+
operator_error, schema.PauliErrorModel
|
|
276
|
+
), "Only PauliErrorModel is supported"
|
|
277
|
+
|
|
278
|
+
layers = {}
|
|
279
|
+
|
|
280
|
+
for qubit_num, pauli_errors in operator_error.errors:
|
|
281
|
+
layers.setdefault(pauli_errors, []).append(qubit_num)
|
|
282
|
+
|
|
283
|
+
for (px, py, pz), qubits in layers.items():
|
|
284
|
+
self.block_list.append(
|
|
285
|
+
qargs := ilist.New(values=tuple(self.qubit_id_map[q] for q in qubits))
|
|
286
|
+
)
|
|
287
|
+
self.block_list.append(
|
|
288
|
+
noise.native.PauliChannel(px=px, py=py, pz=pz, qargs=qargs.result)
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def lower_measurement(self, operation: schema.Measurement):
|
|
292
|
+
for participant in operation.participants:
|
|
293
|
+
qubit = self.qubit_id_map[participant]
|
|
294
|
+
bit = self.bit_id_map[participant]
|
|
295
|
+
self.block_list.append(qasm2.core.Measure(qarg=qubit, carg=bit))
|
|
296
|
+
|
|
297
|
+
def lower_atom_loss(self, survival_probs: Tuple[float, ...]):
|
|
298
|
+
layers = {}
|
|
299
|
+
|
|
300
|
+
for qubit_num, survival_prob in zip(self.qubit_list, survival_probs):
|
|
301
|
+
layers.setdefault(survival_prob, []).append(qubit_num)
|
|
302
|
+
|
|
303
|
+
for survival_prob, qubits in layers.items():
|
|
304
|
+
self.block_list.append(qargs := ilist.New(values=qubits))
|
|
305
|
+
self.block_list.append(
|
|
306
|
+
noise.native.AtomLossChannel(prob=survival_prob, qargs=qargs.result)
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def lower_number(self, value: float | int) -> ir.SSAValue:
|
|
310
|
+
if isinstance(value, int):
|
|
311
|
+
stmt = qasm2.expr.ConstInt(value=value)
|
|
312
|
+
else:
|
|
313
|
+
stmt = qasm2.expr.ConstFloat(value=value)
|
|
314
|
+
|
|
315
|
+
self.block_list.append(stmt)
|
|
316
|
+
return stmt.result
|
|
317
|
+
|
|
318
|
+
def lower_full_turns(self, value: float) -> ir.SSAValue:
|
|
319
|
+
const_pi = qasm2.expr.ConstPI()
|
|
320
|
+
self.block_list.append(const_pi)
|
|
321
|
+
turns = self.lower_number(2 * value)
|
|
322
|
+
mul = qasm2.expr.Mul(const_pi.result, turns)
|
|
323
|
+
self.block_list.append(mul)
|
|
324
|
+
return mul.result
|
bloqade/qbraid/schema.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
from typing import List, Tuple, Union, Generic, Literal, TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic import Field, BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Operation(BaseModel, frozen=True, extra="forbid"):
|
|
7
|
+
"""Base class for operations."""
|
|
8
|
+
|
|
9
|
+
op_type: str = Field(init=False)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CZ(Operation):
|
|
13
|
+
"""A CZ gate operation.
|
|
14
|
+
|
|
15
|
+
Fields:
|
|
16
|
+
op_type (str): The type of operation (Literal["CZ"]).
|
|
17
|
+
participants (Tuple[Union[Tuple[int], Tuple[int, int]], ...]): The qubit indices that are participating in the CZ gate.
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
op_type: Literal["CZ"] = Field(init=False, default="CZ")
|
|
22
|
+
participants: Tuple[Union[Tuple[int], Tuple[int, int]], ...]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GlobalRz(Operation):
|
|
26
|
+
"""GlobalRz operation.
|
|
27
|
+
|
|
28
|
+
Fields:
|
|
29
|
+
op_type (str): The type of operation (Literal["GlobalRz"]).
|
|
30
|
+
phi (float): The angle of rotation.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
op_type: Literal["GlobalRz"] = Field(init=False, default="GlobalRz")
|
|
34
|
+
phi: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class GlobalW(Operation):
|
|
38
|
+
"""GlobalW operation.
|
|
39
|
+
|
|
40
|
+
Fields:
|
|
41
|
+
op_type (str): The type of operation (Literal["GlobalW"]).
|
|
42
|
+
theta (float): The angle of rotation.
|
|
43
|
+
phi (float): The angle of rotation.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
op_type: Literal["GlobalW"] = Field(init=False, default="GlobalW")
|
|
47
|
+
theta: float
|
|
48
|
+
phi: float
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class LocalRz(Operation):
|
|
52
|
+
"""LocalRz operation.
|
|
53
|
+
|
|
54
|
+
Fields:
|
|
55
|
+
op_type (str): The type of operation (Literal["LocalRz"]).
|
|
56
|
+
participants (Tuple[int, ...]): The qubit indices that are participating in the local Rz gate.
|
|
57
|
+
phi (float): The angle of rotation.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
op_type: Literal["LocalRz"] = Field(init=False, default="LocalRz")
|
|
62
|
+
participants: Tuple[int, ...]
|
|
63
|
+
phi: float
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LocalW(Operation):
|
|
67
|
+
"""LocalW operation.
|
|
68
|
+
|
|
69
|
+
Fields:
|
|
70
|
+
op_type (str): The type of operation (Literal["LocalW"]).
|
|
71
|
+
participants (Tuple[int, ...]): The qubit indices that are participating in the local W gate.
|
|
72
|
+
theta (float): The angle of rotation.
|
|
73
|
+
phi (float): The angle of rotation.
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
op_type: Literal["LocalW"] = Field(init=False, default="LocalW")
|
|
78
|
+
participants: Tuple[int, ...]
|
|
79
|
+
theta: float
|
|
80
|
+
phi: float
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Measurement(Operation):
|
|
84
|
+
"""Measurement operation.
|
|
85
|
+
|
|
86
|
+
Fields:
|
|
87
|
+
op_type (str): The type of operation (Literal["Measurement"]).
|
|
88
|
+
measure_tag (str): The tag to use for the measurement.
|
|
89
|
+
participants (Tuple[int, ...]): The qubit indices that are participating in the measurement.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
op_type: Literal["Measurement"] = Field(init=False, default="Measurement")
|
|
94
|
+
measure_tag: str = Field(default="m")
|
|
95
|
+
participants: Tuple[int, ...]
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
OperationType = TypeVar(
|
|
99
|
+
"OperationType", bound=Union[CZ, GlobalRz, GlobalW, LocalRz, LocalW, Measurement]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ErrorModel(BaseModel, frozen=True, extra="forbid"):
|
|
104
|
+
"""Base class for error models."""
|
|
105
|
+
|
|
106
|
+
error_model_type: str = Field(init=False)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class PauliErrorModel(ErrorModel):
|
|
110
|
+
"""Pauli error model.
|
|
111
|
+
|
|
112
|
+
Fields:
|
|
113
|
+
error_model_type (str): The type of error model (Literal["PauliNoise"]).
|
|
114
|
+
errors (Tuple[Tuple[int, Tuple[float, float, float]], ...]): The qubit indices and the error rates for each qubit.
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
error_model_type: Literal["PauliNoise"] = Field(default="PauliNoise", init=False)
|
|
119
|
+
errors: Tuple[Tuple[int, Tuple[float, float, float]], ...] = Field(
|
|
120
|
+
default_factory=tuple
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
ErrorModelType = TypeVar("ErrorModelType", bound=PauliErrorModel)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ErrorOperation(BaseModel, Generic[ErrorModelType], frozen=True, extra="forbid"):
|
|
128
|
+
"""Base class for error operations."""
|
|
129
|
+
|
|
130
|
+
error_type: str = Field(init=False)
|
|
131
|
+
survival_prob: Tuple[float, ...]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class CZError(ErrorOperation[ErrorModelType]):
|
|
135
|
+
"""CZError operation.
|
|
136
|
+
|
|
137
|
+
Fields:
|
|
138
|
+
survival_prob (Tuple[float, ...]): The survival probabilities for each qubit.
|
|
139
|
+
error_type (str): The type of error (Literal["CZError"]).
|
|
140
|
+
storage_error (ErrorModelType): The error model for storage.
|
|
141
|
+
entangled_error (ErrorModelType): The error model for entangled qubits.
|
|
142
|
+
single_error (ErrorModelType): The error model for single qubits.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
error_type: Literal["CZError"] = Field(default="CZError", init=False)
|
|
147
|
+
storage_error: ErrorModelType
|
|
148
|
+
entangled_error: ErrorModelType
|
|
149
|
+
single_error: ErrorModelType
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class SingleQubitError(ErrorOperation[ErrorModelType]):
|
|
153
|
+
"""SingleQubitError operation.
|
|
154
|
+
|
|
155
|
+
Fields:
|
|
156
|
+
survival_prob (Tuple[float, ...]): The survival probabilities for each qubit.
|
|
157
|
+
error_type (str): The type of error (Literal["SingleQubitError"]).
|
|
158
|
+
operator_error (ErrorModelType): The error model for the single qubit.
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
error_type: Literal["SingleQubitError"] = Field(
|
|
163
|
+
default="SingleQubitError", init=False
|
|
164
|
+
)
|
|
165
|
+
operator_error: ErrorModelType
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class GateEvent(BaseModel, Generic[ErrorModelType], frozen=True, extra="forbid"):
|
|
169
|
+
"""A gate event.
|
|
170
|
+
|
|
171
|
+
Fields:
|
|
172
|
+
error (Union[SingleQubitError[ErrorModelType], CZError[ErrorModelType]]): The error model for the gate event.
|
|
173
|
+
operation (OperationType): The operation for the gate event.
|
|
174
|
+
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
error: Union[SingleQubitError[ErrorModelType], CZError[ErrorModelType]] = Field(
|
|
178
|
+
union_mode="left_to_right", discriminator="error_type"
|
|
179
|
+
)
|
|
180
|
+
operation: OperationType = Field(
|
|
181
|
+
union_mode="left_to_right", discriminator="op_type"
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def __pydantic_post_init__(self):
|
|
185
|
+
assert (isinstance(self.operation, CZ) and isinstance(self.error, CZError)) or (
|
|
186
|
+
not isinstance(self.operation, CZ)
|
|
187
|
+
and isinstance(self.error, SingleQubitError)
|
|
188
|
+
), "Operation and error must be of the same type"
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class NoiseModel(BaseModel, Generic[ErrorModelType], extra="forbid"):
|
|
192
|
+
"""Noise model for a circuit.
|
|
193
|
+
|
|
194
|
+
Fields:
|
|
195
|
+
all_qubits (Tuple[int, ...]): The qubit indices for the noise model.
|
|
196
|
+
gate_events (List[GateEvent[ErrorModelType]]): The gate events for the noise model.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
all_qubits: Tuple[int, ...] = Field(default_factory=tuple)
|
|
201
|
+
gate_events: List[GateEvent[ErrorModelType]] = Field(default_factory=list)
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def num_qubits(self) -> int:
|
|
205
|
+
"""Return the number of qubits in the noise model."""
|
|
206
|
+
return len(self.all_qubits)
|
|
207
|
+
|
|
208
|
+
def __add__(self, other: "NoiseModel") -> "NoiseModel":
|
|
209
|
+
if not isinstance(other, NoiseModel):
|
|
210
|
+
raise ValueError(f"Cannot add {type(other)} to Circuit")
|
|
211
|
+
|
|
212
|
+
if self.all_qubits != other.all_qubits:
|
|
213
|
+
raise ValueError("Circuits must have the same number of qubits")
|
|
214
|
+
|
|
215
|
+
return NoiseModel(
|
|
216
|
+
all_qubits=self.all_qubits,
|
|
217
|
+
gate_events=self.gate_events + other.gate_events,
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def lower_noise_model(self, sym_name: str, return_qreg: bool = False):
|
|
221
|
+
"""Lower the noise model to a method.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
sym_name (str): The name of the method to generate.
|
|
225
|
+
return_qreg (bool): Whether to return the quantum register after the method
|
|
226
|
+
has completed execution. Useful for obtaining the full state vector.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Method: The generated kirin method.
|
|
230
|
+
|
|
231
|
+
"""
|
|
232
|
+
from bloqade.qbraid.lowering import Lowering
|
|
233
|
+
|
|
234
|
+
return Lowering().lower(sym_name, self, return_qreg)
|
|
235
|
+
|
|
236
|
+
def decompiled_circuit(self) -> str:
|
|
237
|
+
"""Clean the circuit of noise.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
str: The decompiled circuit from hardware execution.
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
from bloqade.noise import native
|
|
244
|
+
from bloqade.qasm2.emit import QASM2
|
|
245
|
+
from bloqade.qasm2.passes import glob, parallel
|
|
246
|
+
|
|
247
|
+
mt = self.lower_noise_model("method")
|
|
248
|
+
|
|
249
|
+
native.RemoveNoisePass(mt.dialects)(mt)
|
|
250
|
+
parallel.ParallelToUOp(mt.dialects)(mt)
|
|
251
|
+
glob.GlobalToUOP(mt.dialects)(mt)
|
|
252
|
+
return QASM2(qelib1=True).emit_str(mt)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright (c) 2024, QuEra Computing Inc.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
from io import StringIO
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from pandas import DataFrame
|
|
10
|
+
|
|
11
|
+
from bloqade.visual.animation.runtime import qpustate as vis_qpustate
|
|
12
|
+
|
|
13
|
+
from .schema import NoiseModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class QuEraSimulationResult:
|
|
18
|
+
"""Results of the QuEra hardware model simulation.
|
|
19
|
+
|
|
20
|
+
Fields:
|
|
21
|
+
flair_visual_version (str): The version of the Flair Visual package used to generate the simulation result.
|
|
22
|
+
counts (dict[str, int]): The measurement bitstrings of the simulation.
|
|
23
|
+
logs (DataFrame): Grainular logs events of what happened to each atom during the simulation.
|
|
24
|
+
atom_animation_state (vis_qpustate.AnimateQPUState): Object used to play back atom trajectories and events during the simulation.
|
|
25
|
+
noise_model (NoiseModel): The noise model used in the simulation.
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
flair_visual_version: str
|
|
30
|
+
counts: dict[str, int]
|
|
31
|
+
logs: DataFrame
|
|
32
|
+
atom_animation_state: vis_qpustate.AnimateQPUState
|
|
33
|
+
noise_model: NoiseModel
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_json(cls, json: dict) -> "QuEraSimulationResult":
|
|
37
|
+
"""deserialize the object from a JSON serializable dictionary."""
|
|
38
|
+
flair_visual_version = json["flair_visual_version"]
|
|
39
|
+
counts = json["counts"]
|
|
40
|
+
logs = pd.read_csv(StringIO(json["logs"]), index_col=0)
|
|
41
|
+
atom_animation_state = vis_qpustate.AnimateQPUState.from_json(
|
|
42
|
+
json["atom_animation_state"]
|
|
43
|
+
)
|
|
44
|
+
noise_model = NoiseModel(**json["noise_model"])
|
|
45
|
+
|
|
46
|
+
return cls(
|
|
47
|
+
flair_visual_version=flair_visual_version,
|
|
48
|
+
counts=counts,
|
|
49
|
+
logs=logs,
|
|
50
|
+
atom_animation_state=atom_animation_state,
|
|
51
|
+
noise_model=noise_model,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def to_json(self) -> Dict[str, Any]:
|
|
55
|
+
"""Turn the object into a JSON serializable dictionary."""
|
|
56
|
+
return {
|
|
57
|
+
"flair_visual_version": self.flair_visual_version,
|
|
58
|
+
"counts": self.counts,
|
|
59
|
+
"logs": self.logs.to_csv(),
|
|
60
|
+
"atom_animation_state": self.atom_animation_state.to_json(),
|
|
61
|
+
"noise_model": self.noise_model.model_dump(mode="json"),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
def animate(
|
|
65
|
+
self,
|
|
66
|
+
dilation_rate: float = 0.05,
|
|
67
|
+
fps: int = 30,
|
|
68
|
+
gate_display_dilation: float = 1.0,
|
|
69
|
+
save_mpeg: bool = False,
|
|
70
|
+
filename: str = "vqpu_animation",
|
|
71
|
+
start_block: int = 0,
|
|
72
|
+
n_blocks: Optional[int] = None,
|
|
73
|
+
):
|
|
74
|
+
"""animate the qpu state
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
dilation_rate (float): Conversion factor from the qpu time to animation time units. when dilation_rate=1.0, 1 (us) of qpu exec time corresponds to 1 second of animation time.
|
|
78
|
+
fps (int, optional): frame per second. Defaults to 30.
|
|
79
|
+
gate_display_dilation (float, optional): relative dilation rate of a gate event. Defaults to 1. When setting higher value, the gate event will be displayed longer.
|
|
80
|
+
save_mpeg (bool, optional): Save as mpeg. Defaults to False.
|
|
81
|
+
filename (str, optional): The file name of saved mpeg file. Defaults to "vqpu_animation". When `save_mpeg` is False, this argument is ignored.
|
|
82
|
+
start_block (int, optional): The start block to animate. Defaults to 0.
|
|
83
|
+
n_blocks (int, optional): number of blocks to animate. Defaults to None. When None, animate all blocks after `start_block`.
|
|
84
|
+
Returns:
|
|
85
|
+
ani: matplotlib animation object
|
|
86
|
+
"""
|
|
87
|
+
from bloqade.visual.animation.animate import animate_qpu_state
|
|
88
|
+
|
|
89
|
+
ani = animate_qpu_state(
|
|
90
|
+
state=self.atom_animation_state,
|
|
91
|
+
dilation_rate=dilation_rate,
|
|
92
|
+
fps=fps,
|
|
93
|
+
gate_display_dilation=gate_display_dilation,
|
|
94
|
+
start_block=start_block,
|
|
95
|
+
n_blocks=n_blocks,
|
|
96
|
+
save_mpeg=save_mpeg,
|
|
97
|
+
filename=filename,
|
|
98
|
+
)
|
|
99
|
+
return ani
|