bloqade-circuit 0.3.0__py3-none-any.whl → 0.4.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/impls.py +3 -16
- bloqade/pyqrack/__init__.py +1 -1
- bloqade/pyqrack/noise/native.py +8 -8
- bloqade/pyqrack/squin/noise/__init__.py +1 -0
- bloqade/pyqrack/squin/noise/native.py +72 -0
- bloqade/pyqrack/squin/op.py +7 -0
- bloqade/pyqrack/squin/qubit.py +0 -29
- bloqade/pyqrack/squin/runtime.py +18 -0
- bloqade/pyqrack/squin/wire.py +0 -36
- bloqade/{noise/native → qasm2/dialects/noise}/__init__.py +1 -7
- bloqade/qasm2/dialects/noise/_dialect.py +3 -0
- bloqade/{noise → qasm2/dialects/noise}/fidelity.py +2 -2
- bloqade/qasm2/dialects/noise/model.py +278 -0
- bloqade/qasm2/emit/impls/__init__.py +1 -1
- bloqade/qasm2/emit/impls/{noise_native.py → noise.py} +11 -11
- bloqade/qasm2/emit/main.py +2 -4
- bloqade/qasm2/emit/target.py +3 -3
- bloqade/qasm2/groups.py +0 -2
- bloqade/{noise/native/_wrappers.py → qasm2/noise.py} +9 -5
- bloqade/qasm2/passes/glob.py +12 -8
- bloqade/qasm2/passes/noise.py +5 -14
- bloqade/qasm2/rewrite/__init__.py +2 -0
- bloqade/qasm2/rewrite/noise/__init__.py +0 -0
- bloqade/qasm2/rewrite/{heuristic_noise.py → noise/heuristic_noise.py} +31 -53
- bloqade/{noise/native/rewrite.py → qasm2/rewrite/noise/remove_noise.py} +2 -2
- bloqade/qbraid/lowering.py +8 -8
- bloqade/squin/__init__.py +16 -1
- bloqade/squin/analysis/nsites/impls.py +0 -9
- bloqade/squin/cirq/__init__.py +89 -0
- bloqade/squin/cirq/lowering.py +303 -0
- bloqade/squin/groups.py +7 -7
- bloqade/squin/lowering.py +27 -0
- bloqade/squin/noise/__init__.py +3 -1
- bloqade/squin/noise/_wrapper.py +7 -3
- bloqade/squin/noise/rewrite.py +111 -0
- bloqade/squin/noise/stmts.py +21 -16
- bloqade/squin/op/__init__.py +1 -0
- bloqade/squin/op/_wrapper.py +4 -0
- bloqade/squin/op/stmts.py +10 -11
- bloqade/squin/op/types.py +2 -0
- bloqade/squin/qubit.py +32 -37
- bloqade/squin/rewrite/desugar.py +65 -0
- bloqade/squin/rewrite/qubit_to_stim.py +0 -23
- bloqade/squin/rewrite/squin_measure.py +2 -27
- bloqade/squin/rewrite/stim_rewrite_util.py +3 -8
- bloqade/squin/rewrite/wire_to_stim.py +0 -21
- bloqade/squin/wire.py +4 -9
- bloqade/stim/__init__.py +2 -1
- bloqade/stim/_wrappers.py +4 -0
- bloqade/stim/dialects/auxiliary/__init__.py +1 -0
- bloqade/stim/dialects/auxiliary/emit.py +17 -2
- bloqade/stim/dialects/auxiliary/stmts/__init__.py +1 -0
- bloqade/stim/dialects/auxiliary/stmts/annotate.py +8 -0
- bloqade/stim/dialects/collapse/emit_str.py +3 -1
- bloqade/stim/dialects/gate/emit.py +9 -2
- bloqade/stim/dialects/noise/emit.py +32 -1
- bloqade/stim/dialects/noise/stmts.py +29 -0
- bloqade/stim/parse/__init__.py +1 -0
- bloqade/stim/parse/lowering.py +686 -0
- {bloqade_circuit-0.3.0.dist-info → bloqade_circuit-0.4.1.dist-info}/METADATA +3 -1
- {bloqade_circuit-0.3.0.dist-info → bloqade_circuit-0.4.1.dist-info}/RECORD +64 -57
- bloqade/noise/__init__.py +0 -2
- bloqade/noise/native/_dialect.py +0 -3
- bloqade/noise/native/model.py +0 -346
- bloqade/qasm2/dialects/noise.py +0 -48
- bloqade/squin/rewrite/measure_desugar.py +0 -33
- /bloqade/{noise/native → qasm2/dialects/noise}/stmts.py +0 -0
- {bloqade_circuit-0.3.0.dist-info → bloqade_circuit-0.4.1.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.3.0.dist-info → bloqade_circuit-0.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -75,6 +75,7 @@ class PyIndexing(interp.MethodTable):
|
|
|
75
75
|
def getitem(self, interp: AddressAnalysis, frame: interp.Frame, stmt: py.GetItem):
|
|
76
76
|
# Integer index into the thing being indexed
|
|
77
77
|
idx = interp.get_const_value(int, stmt.index)
|
|
78
|
+
|
|
78
79
|
# The object being indexed into
|
|
79
80
|
obj = frame.get(stmt.obj)
|
|
80
81
|
# The `data` attributes holds onto other Address types
|
|
@@ -144,13 +145,13 @@ class Scf(scf.absint.Methods):
|
|
|
144
145
|
# NOTE: we need to actually run iteration in case there are
|
|
145
146
|
# new allocations/re-assign in the loop body.
|
|
146
147
|
for _ in iterable:
|
|
147
|
-
with interp_.
|
|
148
|
+
with interp_.new_frame(stmt) as body_frame:
|
|
148
149
|
body_frame.entries.update(frame.entries)
|
|
149
150
|
body_frame.set_values(
|
|
150
151
|
block_args,
|
|
151
152
|
(NotQubit(),) + loop_vars,
|
|
152
153
|
)
|
|
153
|
-
loop_vars = interp_.run_ssacfg_region(body_frame, stmt.body)
|
|
154
|
+
loop_vars = interp_.run_ssacfg_region(body_frame, stmt.body, ())
|
|
154
155
|
|
|
155
156
|
if loop_vars is None:
|
|
156
157
|
loop_vars = ()
|
|
@@ -206,20 +207,6 @@ class SquinWireMethodTable(interp.MethodTable):
|
|
|
206
207
|
):
|
|
207
208
|
return frame.get_values(stmt.inputs)
|
|
208
209
|
|
|
209
|
-
@interp.impl(squin.wire.MeasureAndReset)
|
|
210
|
-
def measure_and_reset(
|
|
211
|
-
self,
|
|
212
|
-
interp_: AddressAnalysis,
|
|
213
|
-
frame: ForwardFrame[Address],
|
|
214
|
-
stmt: squin.wire.MeasureAndReset,
|
|
215
|
-
):
|
|
216
|
-
|
|
217
|
-
# take the address data from the incoming wire
|
|
218
|
-
# and propagate that forward to the new wire generated.
|
|
219
|
-
# The first entry can safely be NotQubit because
|
|
220
|
-
# it's an integer
|
|
221
|
-
return (NotQubit(), frame.get(stmt.wire))
|
|
222
|
-
|
|
223
210
|
|
|
224
211
|
@squin.qubit.dialect.register(key="qubit.address")
|
|
225
212
|
class SquinQubitMethodTable(interp.MethodTable):
|
bloqade/pyqrack/__init__.py
CHANGED
|
@@ -16,7 +16,7 @@ from .task import PyQrackSimulatorTask as PyQrackSimulatorTask
|
|
|
16
16
|
# NOTE: The following import is for registering the method tables
|
|
17
17
|
from .noise import native as native
|
|
18
18
|
from .qasm2 import uop as uop, core as core, glob as glob, parallel as parallel
|
|
19
|
-
from .squin import op as op, qubit as qubit
|
|
19
|
+
from .squin import op as op, noise as noise, qubit as qubit
|
|
20
20
|
from .device import (
|
|
21
21
|
StackMemorySimulator as StackMemorySimulator,
|
|
22
22
|
DynamicMemorySimulator as DynamicMemorySimulator,
|
bloqade/pyqrack/noise/native.py
CHANGED
|
@@ -2,11 +2,11 @@ from typing import List
|
|
|
2
2
|
|
|
3
3
|
from kirin import interp
|
|
4
4
|
|
|
5
|
-
from bloqade.noise import native
|
|
6
5
|
from bloqade.pyqrack import PyQrackInterpreter, reg
|
|
6
|
+
from bloqade.qasm2.dialects import noise
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
@
|
|
9
|
+
@noise.dialect.register(key="pyqrack")
|
|
10
10
|
class PyQrackMethods(interp.MethodTable):
|
|
11
11
|
def apply_pauli_error(
|
|
12
12
|
self,
|
|
@@ -27,12 +27,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
27
27
|
|
|
28
28
|
getattr(qarg.sim_reg, which)(qarg.addr)
|
|
29
29
|
|
|
30
|
-
@interp.impl(
|
|
30
|
+
@interp.impl(noise.PauliChannel)
|
|
31
31
|
def single_qubit_error_channel(
|
|
32
32
|
self,
|
|
33
33
|
interp: PyQrackInterpreter,
|
|
34
34
|
frame: interp.Frame,
|
|
35
|
-
stmt:
|
|
35
|
+
stmt: noise.PauliChannel,
|
|
36
36
|
):
|
|
37
37
|
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
38
38
|
|
|
@@ -43,12 +43,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
43
43
|
|
|
44
44
|
return ()
|
|
45
45
|
|
|
46
|
-
@interp.impl(
|
|
46
|
+
@interp.impl(noise.CZPauliChannel)
|
|
47
47
|
def cz_pauli_channel(
|
|
48
48
|
self,
|
|
49
49
|
interp: PyQrackInterpreter,
|
|
50
50
|
frame: interp.Frame,
|
|
51
|
-
stmt:
|
|
51
|
+
stmt: noise.CZPauliChannel,
|
|
52
52
|
):
|
|
53
53
|
|
|
54
54
|
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
@@ -80,12 +80,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
80
80
|
|
|
81
81
|
return ()
|
|
82
82
|
|
|
83
|
-
@interp.impl(
|
|
83
|
+
@interp.impl(noise.AtomLossChannel)
|
|
84
84
|
def atom_loss_channel(
|
|
85
85
|
self,
|
|
86
86
|
interp: PyQrackInterpreter,
|
|
87
87
|
frame: interp.Frame,
|
|
88
|
-
stmt:
|
|
88
|
+
stmt: noise.AtomLossChannel,
|
|
89
89
|
):
|
|
90
90
|
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
91
91
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import native as native
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import typing
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from kirin import interp
|
|
6
|
+
from kirin.dialects import ilist
|
|
7
|
+
|
|
8
|
+
from bloqade.pyqrack import QubitState, PyQrackQubit, PyQrackInterpreter
|
|
9
|
+
from bloqade.squin.noise.stmts import QubitLoss, StochasticUnitaryChannel
|
|
10
|
+
from bloqade.squin.noise._dialect import dialect as squin_noise_dialect
|
|
11
|
+
|
|
12
|
+
from ..runtime import OperatorRuntimeABC
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class StochasticUnitaryChannelRuntime(OperatorRuntimeABC):
|
|
17
|
+
operators: ilist.IList[OperatorRuntimeABC, typing.Any]
|
|
18
|
+
probabilities: ilist.IList[float, typing.Any]
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def n_sites(self) -> int:
|
|
22
|
+
n = self.operators[0].n_sites
|
|
23
|
+
for op in self.operators[1:]:
|
|
24
|
+
assert (
|
|
25
|
+
op.n_sites == n
|
|
26
|
+
), "Encountered a stochastic unitary channel with operators of different size!"
|
|
27
|
+
return n
|
|
28
|
+
|
|
29
|
+
def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
|
|
30
|
+
# NOTE: probabilities don't necessarily sum to 1; could be no noise event should occur
|
|
31
|
+
p_no_op = 1 - sum(self.probabilities)
|
|
32
|
+
if random.uniform(0.0, 1.0) < p_no_op:
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
selected_ops = random.choices(self.operators, weights=self.probabilities)
|
|
36
|
+
for op in selected_ops:
|
|
37
|
+
op.apply(*qubits, adjoint=adjoint)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass(frozen=True)
|
|
41
|
+
class QubitLossRuntime(OperatorRuntimeABC):
|
|
42
|
+
p: float
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def n_sites(self) -> int:
|
|
46
|
+
return 1
|
|
47
|
+
|
|
48
|
+
def apply(self, qubit: PyQrackQubit, adjoint: bool = False) -> None:
|
|
49
|
+
if random.uniform(0.0, 1.0) < self.p:
|
|
50
|
+
qubit.state = QubitState.Lost
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@squin_noise_dialect.register(key="pyqrack")
|
|
54
|
+
class PyQrackMethods(interp.MethodTable):
|
|
55
|
+
@interp.impl(StochasticUnitaryChannel)
|
|
56
|
+
def stochastic_unitary_channel(
|
|
57
|
+
self,
|
|
58
|
+
interp: PyQrackInterpreter,
|
|
59
|
+
frame: interp.Frame,
|
|
60
|
+
stmt: StochasticUnitaryChannel,
|
|
61
|
+
):
|
|
62
|
+
operators = frame.get(stmt.operators)
|
|
63
|
+
probabilities = frame.get(stmt.probabilities)
|
|
64
|
+
|
|
65
|
+
return (StochasticUnitaryChannelRuntime(operators, probabilities),)
|
|
66
|
+
|
|
67
|
+
@interp.impl(QubitLoss)
|
|
68
|
+
def qubit_loss(
|
|
69
|
+
self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: QubitLoss
|
|
70
|
+
):
|
|
71
|
+
p = frame.get(stmt.p)
|
|
72
|
+
return (QubitLossRuntime(p),)
|
bloqade/pyqrack/squin/op.py
CHANGED
|
@@ -10,6 +10,7 @@ from .runtime import (
|
|
|
10
10
|
RotRuntime,
|
|
11
11
|
KronRuntime,
|
|
12
12
|
MultRuntime,
|
|
13
|
+
ResetRuntime,
|
|
13
14
|
ScaleRuntime,
|
|
14
15
|
AdjointRuntime,
|
|
15
16
|
ControlRuntime,
|
|
@@ -94,6 +95,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
94
95
|
global_ = isinstance(stmt, op.stmts.PhaseOp)
|
|
95
96
|
return (PhaseOpRuntime(theta, global_=global_),)
|
|
96
97
|
|
|
98
|
+
@interp.impl(op.stmts.Reset)
|
|
99
|
+
def reset(
|
|
100
|
+
self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: op.stmts.Reset
|
|
101
|
+
) -> tuple[OperatorRuntimeABC]:
|
|
102
|
+
return (ResetRuntime(),)
|
|
103
|
+
|
|
97
104
|
@interp.impl(op.stmts.X)
|
|
98
105
|
@interp.impl(op.stmts.Y)
|
|
99
106
|
@interp.impl(op.stmts.Z)
|
bloqade/pyqrack/squin/qubit.py
CHANGED
|
@@ -61,32 +61,3 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
61
61
|
qbit: PyQrackQubit = frame.get(stmt.qubit)
|
|
62
62
|
result = self._measure_qubit(qbit, interp)
|
|
63
63
|
return (result,)
|
|
64
|
-
|
|
65
|
-
@interp.impl(qubit.MeasureAndReset)
|
|
66
|
-
def measure_and_reset(
|
|
67
|
-
self,
|
|
68
|
-
interp: PyQrackInterpreter,
|
|
69
|
-
frame: interp.Frame,
|
|
70
|
-
stmt: qubit.MeasureAndReset,
|
|
71
|
-
):
|
|
72
|
-
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
73
|
-
result = []
|
|
74
|
-
for qbit in qubits:
|
|
75
|
-
qbit_result = self._measure_qubit(qbit, interp)
|
|
76
|
-
|
|
77
|
-
if qbit_result:
|
|
78
|
-
qbit.sim_reg.x(qbit.addr)
|
|
79
|
-
|
|
80
|
-
result.append(qbit_result)
|
|
81
|
-
|
|
82
|
-
return (ilist.IList(result),)
|
|
83
|
-
|
|
84
|
-
@interp.impl(qubit.Reset)
|
|
85
|
-
def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: qubit.Reset):
|
|
86
|
-
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
87
|
-
for qbit in qubits:
|
|
88
|
-
if not qbit.is_active():
|
|
89
|
-
continue
|
|
90
|
-
|
|
91
|
-
if bool(qbit.sim_reg.m(qbit.addr)):
|
|
92
|
-
qbit.sim_reg.x(qbit.addr)
|
bloqade/pyqrack/squin/runtime.py
CHANGED
|
@@ -41,6 +41,24 @@ class OperatorRuntimeABC:
|
|
|
41
41
|
self.apply(*targets, **kwargs)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class ResetRuntime(OperatorRuntimeABC):
|
|
46
|
+
"""Reset the qubit to |0> state"""
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def n_sites(self) -> int:
|
|
50
|
+
return 1
|
|
51
|
+
|
|
52
|
+
def apply(self, *qubits: PyQrackQubit, adjoint: bool = False) -> None:
|
|
53
|
+
for qubit in qubits:
|
|
54
|
+
if not qubit.is_active():
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
res: bool = qubit.sim_reg.m(qubit.addr)
|
|
58
|
+
if res:
|
|
59
|
+
qubit.sim_reg.x(qubit.addr)
|
|
60
|
+
|
|
61
|
+
|
|
44
62
|
@dataclass(frozen=True)
|
|
45
63
|
class OperatorRuntime(OperatorRuntimeABC):
|
|
46
64
|
method_name: str
|
bloqade/pyqrack/squin/wire.py
CHANGED
|
@@ -49,39 +49,3 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
49
49
|
|
|
50
50
|
res: bool = bool(qbit.sim_reg.m(qbit.addr))
|
|
51
51
|
return (res,)
|
|
52
|
-
|
|
53
|
-
@interp.impl(wire.MeasureAndReset)
|
|
54
|
-
def measure_and_reset(
|
|
55
|
-
self,
|
|
56
|
-
interp: PyQrackInterpreter,
|
|
57
|
-
frame: interp.Frame,
|
|
58
|
-
stmt: wire.MeasureAndReset,
|
|
59
|
-
):
|
|
60
|
-
w: PyQrackWire = frame.get(stmt.wire)
|
|
61
|
-
qbit = w.qubit
|
|
62
|
-
|
|
63
|
-
if not qbit.is_active():
|
|
64
|
-
return (w, interp.loss_m_result)
|
|
65
|
-
|
|
66
|
-
res: bool = bool(qbit.sim_reg.m(qbit.addr))
|
|
67
|
-
|
|
68
|
-
if res:
|
|
69
|
-
qbit.sim_reg.x(qbit.addr)
|
|
70
|
-
|
|
71
|
-
# TODO: do we need to rewrap this here? The qbit changed in-place
|
|
72
|
-
new_w = PyQrackWire(qbit)
|
|
73
|
-
return (new_w, res)
|
|
74
|
-
|
|
75
|
-
@interp.impl(wire.Reset)
|
|
76
|
-
def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Reset):
|
|
77
|
-
w: PyQrackWire = frame.get(stmt.wire)
|
|
78
|
-
qbit = w.qubit
|
|
79
|
-
|
|
80
|
-
if not qbit.is_active():
|
|
81
|
-
return (w,)
|
|
82
|
-
|
|
83
|
-
if bool(qbit.sim_reg.m(qbit.addr)):
|
|
84
|
-
qbit.sim_reg.x(qbit.addr)
|
|
85
|
-
|
|
86
|
-
new_w = PyQrackWire(qbit)
|
|
87
|
-
return (new_w,)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
moving towards a more general approach to noise modeling in the future."""
|
|
3
3
|
|
|
4
4
|
from .model import (
|
|
5
|
-
GateNoiseParams as GateNoiseParams,
|
|
6
5
|
TwoRowZoneModel as TwoRowZoneModel,
|
|
7
6
|
MoveNoiseModelABC as MoveNoiseModelABC,
|
|
8
7
|
)
|
|
@@ -11,10 +10,5 @@ from .stmts import (
|
|
|
11
10
|
CZPauliChannel as CZPauliChannel,
|
|
12
11
|
AtomLossChannel as AtomLossChannel,
|
|
13
12
|
)
|
|
14
|
-
from .rewrite import RemoveNoisePass as RemoveNoisePass
|
|
15
13
|
from ._dialect import dialect as dialect
|
|
16
|
-
from .
|
|
17
|
-
pauli_channel as pauli_channel,
|
|
18
|
-
cz_pauli_channel as cz_pauli_channel,
|
|
19
|
-
atom_loss_channel as atom_loss_channel,
|
|
20
|
-
)
|
|
14
|
+
from .fidelity import FidelityMethodTable as FidelityMethodTable
|
|
@@ -4,8 +4,8 @@ from kirin.lattice import EmptyLattice
|
|
|
4
4
|
from bloqade.analysis.address import AddressQubit, AddressTuple
|
|
5
5
|
from bloqade.analysis.fidelity import FidelityAnalysis
|
|
6
6
|
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
7
|
+
from .stmts import PauliChannel, CZPauliChannel, AtomLossChannel
|
|
8
|
+
from ._dialect import dialect
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@dialect.register(key="circuit.fidelity")
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from dataclasses import field, dataclass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class MoveNoiseModelABC(abc.ABC):
|
|
7
|
+
"""Abstract base class for noise based on atom movement.
|
|
8
|
+
|
|
9
|
+
This class defines the interface for a noise model. The gate noise is calculated from the parameters
|
|
10
|
+
provided in this dataclass which can be updated when inheriting from this class. The move error is
|
|
11
|
+
calculated by implementing the parallel_cz_errors method which takes a set of ctrl and qarg qubits
|
|
12
|
+
and returns a noise model for all the qubits. The noise model is a dictionary with the keys being the
|
|
13
|
+
error rates for the qubits and the values being the list of qubits that the error rate applies to.
|
|
14
|
+
|
|
15
|
+
Once implemented the class can be used with the NoisePass to analyze a circuit and apply the noise
|
|
16
|
+
model to the circuit.
|
|
17
|
+
|
|
18
|
+
NOTE: This model is not guaranteed to be supported long-term in bloqade. We will be
|
|
19
|
+
moving towards a more general approach to noise modeling in the future.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# parameters for gate noise
|
|
24
|
+
|
|
25
|
+
local_px: float = field(default=4.102e-04, kw_only=True)
|
|
26
|
+
"""The error probability for a Pauli-X error during a local single qubit gate operation."""
|
|
27
|
+
local_py: float = field(default=4.102e-04, kw_only=True)
|
|
28
|
+
"""The error probability for a Pauli-Y error during a local single qubit gate operation."""
|
|
29
|
+
local_pz: float = field(default=4.112e-04, kw_only=True)
|
|
30
|
+
"""The error probability for a Pauli-Z error during a local single qubit gate operation."""
|
|
31
|
+
local_loss_prob: float = field(default=0.0, kw_only=True)
|
|
32
|
+
"""The error probability for a loss during a local single qubit gate operation."""
|
|
33
|
+
|
|
34
|
+
local_unaddressed_px: float = field(default=2.000e-07, kw_only=True)
|
|
35
|
+
"""The error probability for a Pauli-X error during a local single qubit gate operation when the qubit is not addressed."""
|
|
36
|
+
local_unaddressed_py: float = field(default=2.000e-07, kw_only=True)
|
|
37
|
+
"""The error probability for a Pauli-Y error during a local single qubit gate operation when the qubit is not addressed."""
|
|
38
|
+
local_unaddressed_pz: float = field(default=1.200e-06, kw_only=True)
|
|
39
|
+
"""The error probability for a Pauli-Z error during a local single qubit gate operation when the qubit is not addressed."""
|
|
40
|
+
local_unaddressed_loss_prob: float = field(default=0.0, kw_only=True)
|
|
41
|
+
"""The error probability for a loss during a local single qubit gate operation when the qubit is not addressed."""
|
|
42
|
+
|
|
43
|
+
global_px: float = field(default=6.500e-05, kw_only=True)
|
|
44
|
+
"""The error probability for a Pauli-X error during a global single qubit gate operation."""
|
|
45
|
+
global_py: float = field(default=6.500e-05, kw_only=True)
|
|
46
|
+
"""The error probability for a Pauli-Y error during a global single qubit gate operation."""
|
|
47
|
+
global_pz: float = field(default=6.500e-05, kw_only=True)
|
|
48
|
+
"""The error probability for a Pauli-Z error during a global single qubit gate operation."""
|
|
49
|
+
global_loss_prob: float = field(default=0.0, kw_only=True)
|
|
50
|
+
"""The error probability for a loss during a global single qubit gate operation."""
|
|
51
|
+
|
|
52
|
+
cz_paired_gate_px: float = field(default=6.549e-04, kw_only=True)
|
|
53
|
+
"""The error probability for a Pauli-X error during CZ gate operation when two qubits are within blockade radius."""
|
|
54
|
+
cz_paired_gate_py: float = field(default=6.549e-04, kw_only=True)
|
|
55
|
+
"""The error probability for a Pauli-Y error during CZ gate operation when two qubits are within blockade radius."""
|
|
56
|
+
cz_paired_gate_pz: float = field(default=3.184e-03, kw_only=True)
|
|
57
|
+
"""The error probability for a Pauli-Z error during CZ gate operation when two qubits are within blockade radius."""
|
|
58
|
+
cz_gate_loss_prob: float = field(default=0.0, kw_only=True)
|
|
59
|
+
"""The error probability for a loss during CZ gate operation when two qubits are within blockade radius."""
|
|
60
|
+
|
|
61
|
+
cz_unpaired_gate_px: float = field(default=5.149e-04, kw_only=True)
|
|
62
|
+
"""The error probability for Pauli-X error during CZ gate operation when another qubit is not within blockade radius."""
|
|
63
|
+
cz_unpaired_gate_py: float = field(default=5.149e-04, kw_only=True)
|
|
64
|
+
"""The error probability for Pauli-Y error during CZ gate operation when another qubit is not within blockade radius."""
|
|
65
|
+
cz_unpaired_gate_pz: float = field(default=2.185e-03, kw_only=True)
|
|
66
|
+
"""The error probability for Pauli-Z error during CZ gate operation when another qubit is not within blockade radius."""
|
|
67
|
+
cz_unpaired_loss_prob: float = field(default=0.0, kw_only=True)
|
|
68
|
+
"""The error probability for a loss during CZ gate operation when another qubit is not within blockade radius."""
|
|
69
|
+
|
|
70
|
+
# parameters for move noise
|
|
71
|
+
|
|
72
|
+
mover_px: float = field(default=8.060e-04, kw_only=True)
|
|
73
|
+
"""Probability of X error occurring on a moving qubit during a move operation"""
|
|
74
|
+
mover_py: float = field(default=8.060e-04, kw_only=True)
|
|
75
|
+
"""Probability of Y error occurring on a moving qubit during a move operation"""
|
|
76
|
+
mover_pz: float = field(default=2.458e-03, kw_only=True)
|
|
77
|
+
"""Probability of Z error occurring on a moving qubit during a move operation"""
|
|
78
|
+
move_loss_prob: float = field(default=0.0, kw_only=True)
|
|
79
|
+
"""Probability of loss occurring on a moving qubit during a move operation"""
|
|
80
|
+
|
|
81
|
+
sitter_px: float = field(default=3.066e-04, kw_only=True)
|
|
82
|
+
"""Probability of X error occurring on a stationary qubit during a move operation"""
|
|
83
|
+
sitter_py: float = field(default=3.066e-04, kw_only=True)
|
|
84
|
+
"""Probability of Y error occurring on a stationary qubit during a move operation"""
|
|
85
|
+
sitter_pz: float = field(default=4.639e-04, kw_only=True)
|
|
86
|
+
"""Probability of Z error occurring on a stationary qubit during a move operation"""
|
|
87
|
+
sit_loss_prob: float = field(default=0.0, kw_only=True)
|
|
88
|
+
"""Probability of loss occurring on a stationary qubit during a move operation"""
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def cz_paired_errors(
|
|
92
|
+
self,
|
|
93
|
+
) -> tuple[float, float, float, float]:
|
|
94
|
+
"""Returns the error rates for a CZ gate."""
|
|
95
|
+
return (
|
|
96
|
+
self.cz_paired_gate_px,
|
|
97
|
+
self.cz_paired_gate_py,
|
|
98
|
+
self.cz_paired_gate_pz,
|
|
99
|
+
self.cz_gate_loss_prob,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def cz_unpaired_errors(
|
|
104
|
+
self,
|
|
105
|
+
) -> tuple[float, float, float, float]:
|
|
106
|
+
"""Returns the error rates for a CZ gate."""
|
|
107
|
+
return (
|
|
108
|
+
self.cz_unpaired_gate_px,
|
|
109
|
+
self.cz_unpaired_gate_py,
|
|
110
|
+
self.cz_unpaired_gate_pz,
|
|
111
|
+
self.cz_unpaired_loss_prob,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def local_errors(
|
|
116
|
+
self,
|
|
117
|
+
) -> tuple[float, float, float, float]:
|
|
118
|
+
"""Returns the error rates for a local single qubit gate."""
|
|
119
|
+
return (
|
|
120
|
+
self.local_px,
|
|
121
|
+
self.local_py,
|
|
122
|
+
self.local_pz,
|
|
123
|
+
self.local_loss_prob,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@property
|
|
127
|
+
def local_unaddressed_errors(
|
|
128
|
+
self,
|
|
129
|
+
) -> tuple[float, float, float, float]:
|
|
130
|
+
"""Returns the error rates for a local single qubit gate."""
|
|
131
|
+
return (
|
|
132
|
+
self.local_unaddressed_px,
|
|
133
|
+
self.local_unaddressed_py,
|
|
134
|
+
self.local_unaddressed_pz,
|
|
135
|
+
self.local_unaddressed_loss_prob,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def global_errors(
|
|
140
|
+
self,
|
|
141
|
+
) -> tuple[float, float, float, float]:
|
|
142
|
+
"""Returns the error rates for a global single qubit gate."""
|
|
143
|
+
return (
|
|
144
|
+
self.global_px,
|
|
145
|
+
self.global_py,
|
|
146
|
+
self.global_pz,
|
|
147
|
+
self.global_loss_prob,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def sitter_errors(
|
|
152
|
+
self,
|
|
153
|
+
) -> tuple[float, float, float, float]:
|
|
154
|
+
"""Returns the error rates for a move operation."""
|
|
155
|
+
return (
|
|
156
|
+
self.sitter_px,
|
|
157
|
+
self.sitter_py,
|
|
158
|
+
self.sitter_pz,
|
|
159
|
+
self.sit_loss_prob,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
@abc.abstractmethod
|
|
163
|
+
def parallel_cz_errors(
|
|
164
|
+
self, ctrls: list[int], qargs: list[int], rest: list[int]
|
|
165
|
+
) -> dict[tuple[float, float, float, float], list[int]]:
|
|
166
|
+
"""Takes a set of ctrls and qargs and returns a noise model for all qubits."""
|
|
167
|
+
pass
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def join_binary_probs(cls, p1: float, *args: float) -> float:
|
|
171
|
+
"""Merge the probabilities of an event happening if the event can only happen once.
|
|
172
|
+
|
|
173
|
+
For example, finding the effective probability of losing an atom from multiple sources, since
|
|
174
|
+
a qubit can only be lost once. This is done by using the formula:
|
|
175
|
+
|
|
176
|
+
p = p1 * (1 - p2) + p2 * (1 - p1)
|
|
177
|
+
|
|
178
|
+
applied recursively to all the probabilities in the list.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
p1 (float): The probability of the event happening.
|
|
182
|
+
arg (float): The probabilities of the event happening from other sources.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
float: The effective probability of the event happening.
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
if len(args) == 0:
|
|
189
|
+
return p1
|
|
190
|
+
else:
|
|
191
|
+
p2 = cls.join_binary_probs(*args)
|
|
192
|
+
return p1 * (1 - p2) + p2 * (1 - p1)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dataclass(frozen=True)
|
|
196
|
+
class TwoRowZoneModel(MoveNoiseModelABC):
|
|
197
|
+
"""This model assumes that the qubits are arranged in a single storage row with a row corresponding to a gate zone below it.
|
|
198
|
+
|
|
199
|
+
The CZ gate noise is calculated using the following heuristic: The idle error is calculated by the total duration required
|
|
200
|
+
to do the move and entangle the qubits. Not every pair can be entangled at the same time, so we first deconflict the qargs
|
|
201
|
+
by finding subsets in which both the ctrl and the qarg qubits are in ascending order. This breaks the pairs into
|
|
202
|
+
groups that can be moved and entangled separately. We then take each group and assign each pair to a gate zone slot. The
|
|
203
|
+
slots are allocated by starting from the middle of the atoms and moving outwards making sure to keep the ctrl qubits in
|
|
204
|
+
ascending order. The time to move a group is calculated by finding the maximum travel distance of the qarg and ctrl qubits
|
|
205
|
+
and dviding by the move speed. The total move time is the sum of all the group move times. The error rate for all the qubits
|
|
206
|
+
is then calculated by using the poisson_pauli_prob function. An additional error for the pick operation is calculated by
|
|
207
|
+
joining the binary probabilities of the pick operation and the move operation.
|
|
208
|
+
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def move_errors(
|
|
213
|
+
self,
|
|
214
|
+
) -> tuple[float, float, float, float]:
|
|
215
|
+
"""Returns the error rates for a move operation."""
|
|
216
|
+
return (
|
|
217
|
+
self.mover_px / 2,
|
|
218
|
+
self.mover_py / 2,
|
|
219
|
+
self.mover_pz / 2,
|
|
220
|
+
self.move_loss_prob / 2,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def deconflict(
|
|
224
|
+
self, ctrls: list[int], qargs: list[int]
|
|
225
|
+
) -> list[tuple[tuple[int, ...], tuple[int, ...]]]:
|
|
226
|
+
"""Return a list of groups of ctrl and qarg qubits that can be moved and entangled separately."""
|
|
227
|
+
# sort by ctrl qubit first to guarantee that they will be in ascending order
|
|
228
|
+
sorted_pairs = sorted(zip(ctrls, qargs))
|
|
229
|
+
|
|
230
|
+
groups: list[list[tuple[int, int]]] = []
|
|
231
|
+
# group by qarg only putting it in a group if the qarg is greater than the last qarg in the group
|
|
232
|
+
# thus ensuring that the qargs are in ascending order
|
|
233
|
+
while len(sorted_pairs) > 0:
|
|
234
|
+
ctrl, qarg = sorted_pairs.pop(0)
|
|
235
|
+
|
|
236
|
+
found = False
|
|
237
|
+
for group in groups:
|
|
238
|
+
if group[-1][1] < qarg:
|
|
239
|
+
group.append((ctrl, qarg))
|
|
240
|
+
found = True
|
|
241
|
+
break
|
|
242
|
+
if not found:
|
|
243
|
+
groups.append([(ctrl, qarg)])
|
|
244
|
+
|
|
245
|
+
new_groups: list[tuple[tuple[int, ...], tuple[int, ...]]] = []
|
|
246
|
+
|
|
247
|
+
for group in groups:
|
|
248
|
+
ctrl, qarg = zip(*group)
|
|
249
|
+
ctrl = tuple(ctrl)
|
|
250
|
+
qarg = tuple(qarg)
|
|
251
|
+
new_groups.append((ctrl, qarg))
|
|
252
|
+
|
|
253
|
+
return new_groups
|
|
254
|
+
|
|
255
|
+
def parallel_cz_errors(
|
|
256
|
+
self, ctrls: list[int], qargs: list[int], rest: list[int]
|
|
257
|
+
) -> dict[tuple[float, float, float, float], list[int]]:
|
|
258
|
+
"""Apply parallel gates by moving ctrl qubits to qarg qubits."""
|
|
259
|
+
groups = self.deconflict(ctrls, qargs)
|
|
260
|
+
movers = ctrls + qargs
|
|
261
|
+
num_moves = len(groups)
|
|
262
|
+
# ignore order O(p^2) errors since they are small
|
|
263
|
+
effective_move_errors = (
|
|
264
|
+
self.move_errors[0] + self.sitter_errors[0] * (num_moves - 1),
|
|
265
|
+
self.move_errors[1] + self.sitter_errors[1] * (num_moves - 1),
|
|
266
|
+
self.move_errors[2] + self.sitter_errors[2] * (num_moves - 1),
|
|
267
|
+
self.move_errors[3] + self.sitter_errors[3] * (num_moves - 1),
|
|
268
|
+
)
|
|
269
|
+
effective_sitter_errors = (
|
|
270
|
+
self.sitter_errors[0] * num_moves,
|
|
271
|
+
self.sitter_errors[1] * num_moves,
|
|
272
|
+
self.sitter_errors[2] * num_moves,
|
|
273
|
+
self.sitter_errors[3] * num_moves,
|
|
274
|
+
)
|
|
275
|
+
result = {effective_move_errors: list(movers)}
|
|
276
|
+
result.setdefault(effective_sitter_errors, []).extend(rest)
|
|
277
|
+
|
|
278
|
+
return result
|
|
@@ -1 +1 @@
|
|
|
1
|
-
from . import
|
|
1
|
+
from .noise import NativeNoise as NativeNoise
|