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,347 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import math
|
|
3
|
+
from typing import Dict, List, Tuple
|
|
4
|
+
from dataclasses import field, dataclass
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class GateNoiseParams:
|
|
10
|
+
"""Parameters for gate noise."""
|
|
11
|
+
|
|
12
|
+
local_px: float = field(default=1e-3, kw_only=True)
|
|
13
|
+
"""The error probability for a Pauli-X error during a local single qubit gate operation."""
|
|
14
|
+
local_py: float = field(default=1e-3, kw_only=True)
|
|
15
|
+
"""The error probability for a Pauli-Y error during a local single qubit gate operation."""
|
|
16
|
+
local_pz: float = field(default=1e-3, kw_only=True)
|
|
17
|
+
"""The error probability for a Pauli-Z error during a local single qubit gate operation."""
|
|
18
|
+
local_loss_prob: float = field(default=1e-4, kw_only=True)
|
|
19
|
+
"""The error probability for a loss during a local single qubit gate operation."""
|
|
20
|
+
|
|
21
|
+
global_px: float = field(default=1e-3, kw_only=True)
|
|
22
|
+
"""The error probability for a Pauli-X error during a global single qubit gate operation."""
|
|
23
|
+
global_py: float = field(default=1e-3, kw_only=True)
|
|
24
|
+
"""The error probability for a Pauli-Y error during a global single qubit gate operation."""
|
|
25
|
+
global_pz: float = field(default=1e-3, kw_only=True)
|
|
26
|
+
"""The error probability for a Pauli-Z error during a global single qubit gate operation."""
|
|
27
|
+
global_loss_prob: float = field(default=1e-3, kw_only=True)
|
|
28
|
+
"""The error probability for a loss during a global single qubit gate operation."""
|
|
29
|
+
|
|
30
|
+
cz_paired_gate_px: float = field(default=1e-3, kw_only=True)
|
|
31
|
+
"""The error probability for a Pauli-X error during CZ gate operation when two qubits are within blockade radius."""
|
|
32
|
+
cz_paired_gate_py: float = field(default=1e-3, kw_only=True)
|
|
33
|
+
"""The error probability for a Pauli-Y error during CZ gate operation when two qubits are within blockade radius."""
|
|
34
|
+
cz_paired_gate_pz: float = field(default=1e-3, kw_only=True)
|
|
35
|
+
"""The error probability for a Pauli-Z error during CZ gate operation when two qubits are within blockade radius."""
|
|
36
|
+
cz_gate_loss_prob: float = field(default=1e-3, kw_only=True)
|
|
37
|
+
"""The error probability for a loss during CZ gate operation when two qubits are within blockade radius."""
|
|
38
|
+
|
|
39
|
+
cz_unpaired_gate_px: float = field(default=1e-3, kw_only=True)
|
|
40
|
+
"""The error probability for Pauli-X error during CZ gate operation when another qubit is not within blockade radius."""
|
|
41
|
+
cz_unpaired_gate_py: float = field(default=1e-3, kw_only=True)
|
|
42
|
+
"""The error probability for Pauli-Y error during CZ gate operation when another qubit is not within blockade radius."""
|
|
43
|
+
cz_unpaired_gate_pz: float = field(default=1e-3, kw_only=True)
|
|
44
|
+
"""The error probability for Pauli-Z error during CZ gate operation when another qubit is not within blockade radius."""
|
|
45
|
+
cz_unpaired_loss_prob: float = field(default=1e-3, kw_only=True)
|
|
46
|
+
"""The error probability for a loss during CZ gate operation when another qubit is not within blockade radius."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class MoveNoiseParams:
|
|
51
|
+
idle_px_rate: float = field(default=1e-6, kw_only=True)
|
|
52
|
+
"""The error rate (prob/microsecond) for a Pauli-X error during an idle operation."""
|
|
53
|
+
idle_py_rate: float = field(default=1e-6, kw_only=True)
|
|
54
|
+
"""The error rate (prob/microsecond) for a Pauli-Y error during an idle operation."""
|
|
55
|
+
idle_pz_rate: float = field(default=1e-6, kw_only=True)
|
|
56
|
+
"""The error rate (prob/microsecond) for a Pauli-Z error during an idle operation."""
|
|
57
|
+
idle_loss_rate: float = field(default=1e-6, kw_only=True)
|
|
58
|
+
"""The error rate (prob/microsecond) for a loss during an idle operation."""
|
|
59
|
+
|
|
60
|
+
move_px_rate: float = field(default=1e-6, kw_only=True)
|
|
61
|
+
"""The error rate (prob/microsecond) for a Pauli-X error during a move operation."""
|
|
62
|
+
move_py_rate: float = field(default=1e-6, kw_only=True)
|
|
63
|
+
"""The error rate e (prob/microsecond) for a Pauli-Y error during a move operation."""
|
|
64
|
+
move_pz_rate: float = field(default=1e-6, kw_only=True)
|
|
65
|
+
"""The error rate e (prob/microsecond) for a Pauli-Z error during a move operation."""
|
|
66
|
+
move_loss_rate: float = field(default=1e-6, kw_only=True)
|
|
67
|
+
"""The error rate e (prob/microsecond) for a loss during a move operation."""
|
|
68
|
+
|
|
69
|
+
pick_px: float = field(default=1e-3, kw_only=True)
|
|
70
|
+
"""The error rate (prob per pick operation) for a Pauli-X error during a pick operation."""
|
|
71
|
+
pick_py: float = field(default=1e-3, kw_only=True)
|
|
72
|
+
"""The error rate (prob per pick operation) for a Pauli-Y error during a pick operation."""
|
|
73
|
+
pick_pz: float = field(default=1e-3, kw_only=True)
|
|
74
|
+
"""The error rate (prob per pick operation) for a Pauli-Z error during a pick operation."""
|
|
75
|
+
pick_loss_prob: float = field(default=1e-4, kw_only=True)
|
|
76
|
+
"""The error rate for a loss during a pick operation."""
|
|
77
|
+
|
|
78
|
+
move_speed: float = field(default=5e-1, kw_only=True)
|
|
79
|
+
"""Maximum speed of the qubits during a move operation."""
|
|
80
|
+
storage_spacing: float = field(default=4.0, kw_only=True)
|
|
81
|
+
"""Spacing between the qubits in the storage zone."""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class MoveNoiseModelABC(abc.ABC):
|
|
86
|
+
"""Abstract base class for noise based on atom movement.
|
|
87
|
+
|
|
88
|
+
This class defines the interface for a noise model. The gate noise is calculated form the parameters
|
|
89
|
+
provided in this dataclass which can be updated when inheriting from this class. The move error is
|
|
90
|
+
calculated by implementing the parallel_cz_errors method which takes a set of ctrl and qarg qubits
|
|
91
|
+
and returns a noise model for all the qubits. The noise model is a dictionary with the keys being the
|
|
92
|
+
error rates for the qubits and the values being the list of qubits that the error rate applies to.
|
|
93
|
+
|
|
94
|
+
Once implemented the class can be used with the NoisePass to analyze a circuit and apply the noise
|
|
95
|
+
model to the circuit.
|
|
96
|
+
|
|
97
|
+
NOTE: This model is not guaranteed to be supported long-term in bloqade. We will be
|
|
98
|
+
moving towards a more general approach to noise modeling in the future.
|
|
99
|
+
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
params: MoveNoiseParams = field(default_factory=MoveNoiseParams)
|
|
103
|
+
"""Parameters for calculating move noise."""
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
@abc.abstractmethod
|
|
107
|
+
def parallel_cz_errors(
|
|
108
|
+
cls, ctrls: List[int], qargs: List[int], rest: List[int]
|
|
109
|
+
) -> Dict[Tuple[float, float, float, float], List[int]]:
|
|
110
|
+
"""Takes a set of ctrls and qargs and returns a noise model for all qubits."""
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def poisson_pauli_prob(rate: float, duration: float) -> float:
|
|
115
|
+
"""Calculate the number of noise events and their probabilities for a given rate and duration."""
|
|
116
|
+
assert duration >= 0, "Duration must be non-negative"
|
|
117
|
+
assert rate >= 0, "Rate must be non-negative"
|
|
118
|
+
return 0.5 * (1 - math.exp(-2 * rate * duration))
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def join_binary_probs(cls, p1: float, *args: float) -> float:
|
|
122
|
+
"""Merge the probabilities of an event happening if the event can only happen once.
|
|
123
|
+
|
|
124
|
+
For example, finding the effective probability of losing an atom from multiple sources, since
|
|
125
|
+
a qubit can only happen once. This is done by using the formula:
|
|
126
|
+
|
|
127
|
+
p = p1 * (1 - p2) + p2 * (1 - p1)
|
|
128
|
+
|
|
129
|
+
applied recursively to all the probabilities in the list.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
p1 (float): The probability of the event happening.
|
|
133
|
+
arg (float): The probabilities of the event happening from other sources.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
float: The effective probability of the event happening.
|
|
137
|
+
|
|
138
|
+
"""
|
|
139
|
+
if len(args) == 0:
|
|
140
|
+
return p1
|
|
141
|
+
else:
|
|
142
|
+
p2 = cls.join_binary_probs(*args)
|
|
143
|
+
return p1 * (1 - p2) + p2 * (1 - p1)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@dataclass
|
|
147
|
+
class TwoRowZoneModel(MoveNoiseModelABC):
|
|
148
|
+
"""This model assumes that the qubits are arranged in a single storage row with a row corresponding to a gate zone below it.
|
|
149
|
+
|
|
150
|
+
The CZ gate noise is calculated using the following heuristic: The idle error is calculated by the total duration require
|
|
151
|
+
to do the move and entable the qubits. Not every pair can be entangled at the same time, so we first deconflict the qargs
|
|
152
|
+
assuming by finding subsets in which both the ctrl and the qarg qubits are in ascending order. This breaks the pairs into
|
|
153
|
+
groups that can be moved and entangled separately. We then take each group and assign each pair to a gate zone slot. The
|
|
154
|
+
slots are allocated by starting from the middle of the atoms and moving outwards making sure to keep the ctrl qubits in
|
|
155
|
+
ascending order. The time to move a group is calculated by finding the maximum travel distance of the qarg and ctrl qubits
|
|
156
|
+
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
|
|
157
|
+
is then calculated by using the poisson_pauli_prob function. An additional error for the pick operation is calculated by
|
|
158
|
+
joining the binary probabilities of the pick operation and the move operation.
|
|
159
|
+
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
gate_zone_y_offset: float = 20.0
|
|
163
|
+
gate_spacing: float = 20.0
|
|
164
|
+
|
|
165
|
+
def deconflict(
|
|
166
|
+
self, ctrls: List[int], qargs: List[int]
|
|
167
|
+
) -> List[Tuple[Tuple[int, ...], Tuple[int, ...]]]:
|
|
168
|
+
"""Return a list of groups of ctrl and qarg qubits that can be moved and entangled separately."""
|
|
169
|
+
# sort by ctrl qubit first to guarantee that they will be in ascending order
|
|
170
|
+
sorted_pairs = sorted(zip(ctrls, qargs))
|
|
171
|
+
|
|
172
|
+
groups = []
|
|
173
|
+
# group by qarg only putting it in a group if the qarg is greater than the last qarg in the group
|
|
174
|
+
# thus ensuring that the qargs are in ascending order
|
|
175
|
+
while len(sorted_pairs) > 0:
|
|
176
|
+
ctrl, qarg = sorted_pairs.pop(0)
|
|
177
|
+
|
|
178
|
+
found = False
|
|
179
|
+
for group in groups:
|
|
180
|
+
if group[-1][1] < qarg:
|
|
181
|
+
group.append((ctrl, qarg))
|
|
182
|
+
found = True
|
|
183
|
+
break
|
|
184
|
+
if not found:
|
|
185
|
+
groups.append([(ctrl, qarg)])
|
|
186
|
+
|
|
187
|
+
return [tuple(zip(*group)) for group in groups]
|
|
188
|
+
|
|
189
|
+
def assign_gate_slots(
|
|
190
|
+
self, ctrls: Sequence[int], qargs: Sequence[int]
|
|
191
|
+
) -> Dict[int, Tuple[int, int]]:
|
|
192
|
+
"""Allocate slots for the qubits to move to. start from middle of atoms and move outwards
|
|
193
|
+
making sure to keep the ctrl qubits in ascending order.
|
|
194
|
+
|
|
195
|
+
Note that we can do this because the move strategy is to move the ctrl qubits separately
|
|
196
|
+
from the qarg qubits, thus we don't have to worry about qarg qubits crossing the ctrl qubits
|
|
197
|
+
and vice versa. We pick the median of all the atoms because it distributes the qubits
|
|
198
|
+
as evenly as possible over the gate zone.
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
assert len(ctrls) == len(qargs), "Number of ctrls and qargs must be equal"
|
|
202
|
+
addr_pairs = sorted(zip(ctrls, qargs), key=lambda x: x[0])
|
|
203
|
+
# sort by the distance between the ctrl and qarg qubits
|
|
204
|
+
|
|
205
|
+
ctrls, qargs = list(zip(*addr_pairs))
|
|
206
|
+
|
|
207
|
+
n_ctrls = len(ctrls)
|
|
208
|
+
|
|
209
|
+
ctrl_median = (
|
|
210
|
+
ctrls[n_ctrls // 2]
|
|
211
|
+
if n_ctrls % 2 == 1
|
|
212
|
+
else (ctrls[n_ctrls // 2 - 1] + ctrls[n_ctrls // 2]) / 2
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
all_addr = sorted(ctrls + qargs)
|
|
216
|
+
spatial_median = self.params.storage_spacing * (all_addr[0] + all_addr[-1]) / 2
|
|
217
|
+
|
|
218
|
+
addr_pairs.sort(key=lambda x: abs(x[0] - ctrl_median))
|
|
219
|
+
|
|
220
|
+
slots = {}
|
|
221
|
+
med_slot = round(spatial_median / self.gate_spacing)
|
|
222
|
+
|
|
223
|
+
left_slot = med_slot
|
|
224
|
+
right_slot = med_slot
|
|
225
|
+
slots[med_slot] = addr_pairs.pop(0)
|
|
226
|
+
while addr_pairs:
|
|
227
|
+
ctrl, qarg = addr_pairs.pop(0)
|
|
228
|
+
|
|
229
|
+
if ctrl < ctrl_median:
|
|
230
|
+
slots[left_slot := left_slot - 1] = (ctrl, qarg)
|
|
231
|
+
else:
|
|
232
|
+
slots[right_slot := right_slot + 1] = (ctrl, qarg)
|
|
233
|
+
|
|
234
|
+
return slots
|
|
235
|
+
|
|
236
|
+
def calculate_move_duration(self, slots: Dict[int, Tuple[int, int]]) -> float:
|
|
237
|
+
"""Calculate the time it takes to move the qubits from the ctrl to the qarg qubits."""
|
|
238
|
+
|
|
239
|
+
qarg_x_distance = float("-inf")
|
|
240
|
+
ctrl_x_distance = float("-inf")
|
|
241
|
+
|
|
242
|
+
for slot, (ctrl, qarg) in slots.items():
|
|
243
|
+
qarg_x_distance = max(
|
|
244
|
+
qarg_x_distance,
|
|
245
|
+
abs(qarg * self.params.storage_spacing - slot * self.gate_spacing),
|
|
246
|
+
)
|
|
247
|
+
ctrl_x_distance = max(
|
|
248
|
+
ctrl_x_distance,
|
|
249
|
+
abs(ctrl * self.params.storage_spacing - slot * self.gate_spacing),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
qarg_max_distance = math.sqrt(qarg_x_distance**2 + self.gate_zone_y_offset**2)
|
|
253
|
+
ctrl_max_distance = math.sqrt(
|
|
254
|
+
ctrl_x_distance**2 + (self.gate_zone_y_offset - 3) ** 2
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
return (qarg_max_distance + ctrl_max_distance) / self.params.move_speed
|
|
258
|
+
|
|
259
|
+
def parallel_cz_errors(
|
|
260
|
+
self, ctrls: List[int], qargs: List[int], rest: List[int]
|
|
261
|
+
) -> Dict[Tuple[float, float, float, float], List[int]]:
|
|
262
|
+
"""Apply parallel gates by moving ctrl qubits to qarg qubits."""
|
|
263
|
+
groups = self.deconflict(ctrls, qargs)
|
|
264
|
+
slots = [self.assign_gate_slots(*group) for group in groups]
|
|
265
|
+
|
|
266
|
+
move_duration = sum(map(self.calculate_move_duration, slots))
|
|
267
|
+
|
|
268
|
+
px_time = self.poisson_pauli_prob(self.params.move_px_rate, move_duration)
|
|
269
|
+
py_time = self.poisson_pauli_prob(self.params.move_py_rate, move_duration)
|
|
270
|
+
px_time = self.poisson_pauli_prob(self.params.move_pz_rate, move_duration)
|
|
271
|
+
move_p_loss_time = self.poisson_pauli_prob(
|
|
272
|
+
self.params.move_loss_rate, move_duration
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
errors = {(px_time, py_time, px_time, move_p_loss_time): rest}
|
|
276
|
+
|
|
277
|
+
px_moved = self.join_binary_probs(self.params.pick_px, px_time)
|
|
278
|
+
py_moved = self.join_binary_probs(self.params.pick_py, py_time)
|
|
279
|
+
pz_moved = self.join_binary_probs(self.params.pick_pz, px_time)
|
|
280
|
+
p_loss_moved = self.join_binary_probs(
|
|
281
|
+
self.params.pick_loss_prob, move_p_loss_time
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
errors[(px_moved, py_moved, pz_moved, p_loss_moved)] = sorted(ctrls + qargs)
|
|
285
|
+
|
|
286
|
+
return errors
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@dataclass
|
|
290
|
+
class SingleZoneLayoutABC(MoveNoiseModelABC):
|
|
291
|
+
gate_noise_params: GateNoiseParams = field(
|
|
292
|
+
default_factory=GateNoiseParams, kw_only=True
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
@abc.abstractmethod
|
|
296
|
+
def calculate_move_duration(self, ctrls: List[int], qargs: List[int]) -> float:
|
|
297
|
+
"""Calculate the time it takes to reconfigure the atom for executing the CZ gates."""
|
|
298
|
+
|
|
299
|
+
def parallel_cz_errors(
|
|
300
|
+
self, ctrls: List[int], qargs: List[int], rest: List[int]
|
|
301
|
+
) -> Dict[Tuple[float, float, float, float], List[int]]:
|
|
302
|
+
"""Apply parallel gates by moving ctrl qubits to qarg qubits."""
|
|
303
|
+
|
|
304
|
+
move_duration = self.calculate_move_duration(ctrls, qargs)
|
|
305
|
+
|
|
306
|
+
# idle errors during atom moves
|
|
307
|
+
idle_px_time = self.poisson_pauli_prob(self.params.idle_px_rate, move_duration)
|
|
308
|
+
idle_py_time = self.poisson_pauli_prob(self.params.idle_py_rate, move_duration)
|
|
309
|
+
idle_pz_time = self.poisson_pauli_prob(self.params.idle_pz_rate, move_duration)
|
|
310
|
+
idle_p_loss_time = self.poisson_pauli_prob(
|
|
311
|
+
self.params.idle_loss_rate, move_duration
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# even qubits not involved in the gate can still experience unpaired errors
|
|
315
|
+
idle_px = self.join_binary_probs(
|
|
316
|
+
self.gate_noise_params.cz_unpaired_gate_px, idle_px_time
|
|
317
|
+
)
|
|
318
|
+
idle_py = self.join_binary_probs(
|
|
319
|
+
self.gate_noise_params.cz_unpaired_gate_py, idle_py_time
|
|
320
|
+
)
|
|
321
|
+
idle_pz = self.join_binary_probs(
|
|
322
|
+
self.gate_noise_params.cz_unpaired_gate_pz, idle_pz_time
|
|
323
|
+
)
|
|
324
|
+
idle_p_loss = self.join_binary_probs(
|
|
325
|
+
self.gate_noise_params.cz_unpaired_loss_prob, idle_p_loss_time
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
errors = {(idle_px, idle_py, idle_pz, idle_p_loss): rest}
|
|
329
|
+
|
|
330
|
+
# error during the move
|
|
331
|
+
move_px_time = self.poisson_pauli_prob(self.params.move_px_rate, move_duration)
|
|
332
|
+
move_py_time = self.poisson_pauli_prob(self.params.move_py_rate, move_duration)
|
|
333
|
+
move_pz_time = self.poisson_pauli_prob(self.params.move_pz_rate, move_duration)
|
|
334
|
+
move_p_loss_time = self.poisson_pauli_prob(
|
|
335
|
+
self.params.move_loss_rate, move_duration
|
|
336
|
+
)
|
|
337
|
+
# error coming from picking up the qubits
|
|
338
|
+
px_moved = self.join_binary_probs(self.params.pick_px, move_px_time)
|
|
339
|
+
py_moved = self.join_binary_probs(self.params.pick_py, move_py_time)
|
|
340
|
+
pz_moved = self.join_binary_probs(self.params.pick_pz, move_pz_time)
|
|
341
|
+
p_loss_moved = self.join_binary_probs(
|
|
342
|
+
self.params.pick_loss_prob, move_p_loss_time
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
errors[(px_moved, py_moved, pz_moved, p_loss_moved)] = sorted(ctrls + qargs)
|
|
346
|
+
|
|
347
|
+
return errors
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from kirin import ir
|
|
4
|
+
from kirin.rewrite import abc, dce, walk, result, fixpoint
|
|
5
|
+
from kirin.passes.abc import Pass
|
|
6
|
+
|
|
7
|
+
from .stmts import PauliChannel, CZPauliChannel, AtomLossChannel
|
|
8
|
+
from ._dialect import dialect
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RemoveNoiseRewrite(abc.RewriteRule):
|
|
12
|
+
def rewrite_Statement(self, node: ir.Statement) -> result.RewriteResult:
|
|
13
|
+
if isinstance(node, (AtomLossChannel, PauliChannel, CZPauliChannel)):
|
|
14
|
+
node.delete()
|
|
15
|
+
return result.RewriteResult(has_done_something=True)
|
|
16
|
+
|
|
17
|
+
return result.RewriteResult()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class RemoveNoisePass(Pass):
|
|
22
|
+
name = "remove-noise"
|
|
23
|
+
|
|
24
|
+
def unsafe_run(self, mt: ir.Method) -> result.RewriteResult:
|
|
25
|
+
delete_walk = walk.Walk(RemoveNoiseRewrite())
|
|
26
|
+
dce_walk = fixpoint.Fixpoint(walk.Walk(dce.DeadCodeElimination()))
|
|
27
|
+
|
|
28
|
+
result = delete_walk.rewrite(mt.code)
|
|
29
|
+
|
|
30
|
+
mt.dialects = ir.DialectGroup(mt.dialects.data.symmetric_difference([dialect]))
|
|
31
|
+
|
|
32
|
+
if result.has_done_something:
|
|
33
|
+
result = dce_walk.rewrite(mt.code)
|
|
34
|
+
|
|
35
|
+
return result
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from kirin import ir, types, lowering
|
|
2
|
+
from kirin.decl import info, statement
|
|
3
|
+
from kirin.dialects import ilist
|
|
4
|
+
|
|
5
|
+
from bloqade.qasm2.types import QubitType
|
|
6
|
+
|
|
7
|
+
from ._dialect import dialect
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@statement(dialect=dialect)
|
|
11
|
+
class PauliChannel(ir.Statement):
|
|
12
|
+
|
|
13
|
+
traits = frozenset({lowering.FromPythonCall()})
|
|
14
|
+
|
|
15
|
+
px: float = info.attribute(types.Float)
|
|
16
|
+
py: float = info.attribute(types.Float)
|
|
17
|
+
pz: float = info.attribute(types.Float)
|
|
18
|
+
qargs: ir.SSAValue = info.argument(ilist.IListType[QubitType])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
NumQubits = types.TypeVar("NumQubits")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@statement(dialect=dialect)
|
|
25
|
+
class CZPauliChannel(ir.Statement):
|
|
26
|
+
|
|
27
|
+
traits = frozenset({lowering.FromPythonCall()})
|
|
28
|
+
|
|
29
|
+
paired: bool = info.attribute(types.Bool)
|
|
30
|
+
px_ctrl: float = info.attribute(types.Float)
|
|
31
|
+
py_ctrl: float = info.attribute(types.Float)
|
|
32
|
+
pz_ctrl: float = info.attribute(types.Float)
|
|
33
|
+
px_qarg: float = info.attribute(types.Float)
|
|
34
|
+
py_qarg: float = info.attribute(types.Float)
|
|
35
|
+
pz_qarg: float = info.attribute(types.Float)
|
|
36
|
+
ctrls: ir.SSAValue = info.argument(ilist.IListType[QubitType, NumQubits])
|
|
37
|
+
qargs: ir.SSAValue = info.argument(ilist.IListType[QubitType, NumQubits])
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@statement(dialect=dialect)
|
|
41
|
+
class AtomLossChannel(ir.Statement):
|
|
42
|
+
|
|
43
|
+
traits = frozenset({lowering.FromPythonCall()})
|
|
44
|
+
|
|
45
|
+
prob: float = info.attribute(types.Float)
|
|
46
|
+
qargs: ir.SSAValue = info.argument(ilist.IListType[QubitType])
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .reg import (
|
|
2
|
+
CBitRef as CBitRef,
|
|
3
|
+
CRegister as CRegister,
|
|
4
|
+
PyQrackReg as PyQrackReg,
|
|
5
|
+
QubitState as QubitState,
|
|
6
|
+
Measurement as Measurement,
|
|
7
|
+
PyQrackQubit as PyQrackQubit,
|
|
8
|
+
)
|
|
9
|
+
from .base import (
|
|
10
|
+
StackMemory as StackMemory,
|
|
11
|
+
DynamicMemory as DynamicMemory,
|
|
12
|
+
PyQrackInterpreter as PyQrackInterpreter,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# NOTE: The following import is for registering the method tables
|
|
16
|
+
from .noise import native as native
|
|
17
|
+
from .qasm2 import uop as uop, core as core, parallel as parallel
|
|
18
|
+
from .target import PyQrack as PyQrack
|
bloqade/pyqrack/base.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import typing
|
|
3
|
+
from dataclasses import field, dataclass
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from kirin.interp import Interpreter
|
|
8
|
+
from typing_extensions import Self
|
|
9
|
+
from kirin.interp.exceptions import InterpreterError
|
|
10
|
+
|
|
11
|
+
from bloqade.pyqrack.reg import Measurement
|
|
12
|
+
|
|
13
|
+
if typing.TYPE_CHECKING:
|
|
14
|
+
from pyqrack import QrackSimulator
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PyQrackOptions(typing.TypedDict):
|
|
18
|
+
qubitCount: int
|
|
19
|
+
isTensorNetwork: bool
|
|
20
|
+
isSchmidtDecomposeMulti: bool
|
|
21
|
+
isSchmidtDecompose: bool
|
|
22
|
+
isStabilizerHybrid: bool
|
|
23
|
+
isBinaryDecisionTree: bool
|
|
24
|
+
isPaged: bool
|
|
25
|
+
isCpuGpuHybrid: bool
|
|
26
|
+
isOpenCL: bool
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _default_pyqrack_args() -> PyQrackOptions:
|
|
30
|
+
return PyQrackOptions(
|
|
31
|
+
qubitCount=-1,
|
|
32
|
+
isTensorNetwork=False,
|
|
33
|
+
isSchmidtDecomposeMulti=True,
|
|
34
|
+
isSchmidtDecompose=True,
|
|
35
|
+
isStabilizerHybrid=True,
|
|
36
|
+
isBinaryDecisionTree=True,
|
|
37
|
+
isPaged=True,
|
|
38
|
+
isCpuGpuHybrid=True,
|
|
39
|
+
isOpenCL=True,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class MemoryABC(abc.ABC):
|
|
45
|
+
pyqrack_options: PyQrackOptions = field(default_factory=_default_pyqrack_args)
|
|
46
|
+
sim_reg: "QrackSimulator" = field(init=False)
|
|
47
|
+
|
|
48
|
+
@abc.abstractmethod
|
|
49
|
+
def allocate(self, n_qubits: int) -> tuple[int, ...]:
|
|
50
|
+
"""Allocate `n_qubits` qubits and return their ids."""
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
def reset(self):
|
|
54
|
+
"""Reset the memory, releasing all qubits."""
|
|
55
|
+
from pyqrack import QrackSimulator
|
|
56
|
+
|
|
57
|
+
# do not reset the simulator it might be used by
|
|
58
|
+
# results of the simulation
|
|
59
|
+
self.sim_reg = QrackSimulator(**self.pyqrack_options)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class MockMemory(MemoryABC):
|
|
64
|
+
"""Mock memory for testing purposes."""
|
|
65
|
+
|
|
66
|
+
allocated: int = field(init=False, default=0)
|
|
67
|
+
|
|
68
|
+
def allocate(self, n_qubits: int):
|
|
69
|
+
allocated = self.allocated + n_qubits
|
|
70
|
+
result = tuple(range(self.allocated, allocated))
|
|
71
|
+
self.allocated = allocated
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
def reset(self):
|
|
75
|
+
self.allocated = 0
|
|
76
|
+
self.sim_reg = Mock()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class StackMemory(MemoryABC):
|
|
81
|
+
total: int = field(kw_only=True)
|
|
82
|
+
allocated: int = field(init=False, default=0)
|
|
83
|
+
|
|
84
|
+
def allocate(self, n_qubits: int):
|
|
85
|
+
curr_allocated = self.allocated
|
|
86
|
+
self.allocated += n_qubits
|
|
87
|
+
|
|
88
|
+
if self.allocated > self.total:
|
|
89
|
+
raise InterpreterError(
|
|
90
|
+
f"qubit allocation exceeds memory, "
|
|
91
|
+
f"{self.total} qubits, "
|
|
92
|
+
f"{self.allocated} allocated"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return tuple(range(curr_allocated, self.allocated))
|
|
96
|
+
|
|
97
|
+
def reset(self):
|
|
98
|
+
super().reset()
|
|
99
|
+
self.allocated = 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class DynamicMemory(MemoryABC):
|
|
104
|
+
def __post_init__(self):
|
|
105
|
+
self.reset()
|
|
106
|
+
|
|
107
|
+
if self.sim_reg.is_tensor_network:
|
|
108
|
+
raise ValueError("DynamicMemory does not support tensor networks")
|
|
109
|
+
|
|
110
|
+
def allocate(self, n_qubits: int):
|
|
111
|
+
start = self.sim_reg.num_qubits()
|
|
112
|
+
for i in range(start, start + n_qubits):
|
|
113
|
+
self.sim_reg.allocate_qubit(i)
|
|
114
|
+
|
|
115
|
+
return tuple(range(start, start + n_qubits))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass
|
|
119
|
+
class PyQrackInterpreter(Interpreter):
|
|
120
|
+
keys = ["pyqrack", "main"]
|
|
121
|
+
memory: MemoryABC = field(kw_only=True)
|
|
122
|
+
rng_state: np.random.Generator = field(
|
|
123
|
+
default_factory=np.random.default_rng, kw_only=True
|
|
124
|
+
)
|
|
125
|
+
loss_m_result: Measurement = field(default=Measurement.One, kw_only=True)
|
|
126
|
+
"""The value of a measurement result when a qubit is lost."""
|
|
127
|
+
|
|
128
|
+
def initialize(self) -> Self:
|
|
129
|
+
super().initialize()
|
|
130
|
+
self.memory.reset() # reset allocated qubits
|
|
131
|
+
return self
|
|
File without changes
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from kirin import interp
|
|
4
|
+
|
|
5
|
+
from bloqade.noise import native
|
|
6
|
+
from bloqade.pyqrack import PyQrackInterpreter, reg
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@native.dialect.register(key="pyqrack")
|
|
10
|
+
class PyQrackMethods(interp.MethodTable):
|
|
11
|
+
def apply_pauli_error(
|
|
12
|
+
self,
|
|
13
|
+
interp: PyQrackInterpreter,
|
|
14
|
+
qarg: reg.PyQrackQubit,
|
|
15
|
+
px: float,
|
|
16
|
+
py: float,
|
|
17
|
+
pz: float,
|
|
18
|
+
):
|
|
19
|
+
p = [1 - (px + py + pz), px, py, pz]
|
|
20
|
+
|
|
21
|
+
assert all(0 <= x <= 1 for x in p), "Invalid Pauli error probabilities"
|
|
22
|
+
|
|
23
|
+
which = interp.rng_state.choice(["i", "x", "y", "z"], p=p)
|
|
24
|
+
|
|
25
|
+
if which == "i":
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
getattr(qarg.sim_reg, which)(qarg.addr)
|
|
29
|
+
|
|
30
|
+
@interp.impl(native.PauliChannel)
|
|
31
|
+
def single_qubit_error_channel(
|
|
32
|
+
self,
|
|
33
|
+
interp: PyQrackInterpreter,
|
|
34
|
+
frame: interp.Frame,
|
|
35
|
+
stmt: native.PauliChannel,
|
|
36
|
+
):
|
|
37
|
+
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
38
|
+
|
|
39
|
+
active_qubits = (qarg for qarg in qargs if qarg.is_active())
|
|
40
|
+
|
|
41
|
+
for qarg in active_qubits:
|
|
42
|
+
self.apply_pauli_error(interp, qarg, stmt.px, stmt.py, stmt.pz)
|
|
43
|
+
|
|
44
|
+
return ()
|
|
45
|
+
|
|
46
|
+
@interp.impl(native.CZPauliChannel)
|
|
47
|
+
def cz_pauli_channel(
|
|
48
|
+
self,
|
|
49
|
+
interp: PyQrackInterpreter,
|
|
50
|
+
frame: interp.Frame,
|
|
51
|
+
stmt: native.CZPauliChannel,
|
|
52
|
+
):
|
|
53
|
+
|
|
54
|
+
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
55
|
+
ctrls: List[reg.PyQrackQubit] = frame.get(stmt.ctrls)
|
|
56
|
+
|
|
57
|
+
if stmt.paired:
|
|
58
|
+
valid_pairs = (
|
|
59
|
+
(ctrl, qarg)
|
|
60
|
+
for ctrl, qarg in zip(ctrls, qargs)
|
|
61
|
+
if ctrl.is_active() and qarg.is_active()
|
|
62
|
+
)
|
|
63
|
+
else:
|
|
64
|
+
valid_pairs = (
|
|
65
|
+
(ctrl, qarg)
|
|
66
|
+
for ctrl, qarg in zip(ctrls, qargs)
|
|
67
|
+
if ctrl.is_active() ^ qarg.is_active()
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
for ctrl, qarg in valid_pairs:
|
|
71
|
+
if ctrl.is_active():
|
|
72
|
+
self.apply_pauli_error(
|
|
73
|
+
interp, ctrl, stmt.px_ctrl, stmt.py_ctrl, stmt.pz_ctrl
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if qarg.is_active():
|
|
77
|
+
self.apply_pauli_error(
|
|
78
|
+
interp, qarg, stmt.px_qarg, stmt.py_qarg, stmt.pz_qarg
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return ()
|
|
82
|
+
|
|
83
|
+
@interp.impl(native.AtomLossChannel)
|
|
84
|
+
def atom_loss_channel(
|
|
85
|
+
self,
|
|
86
|
+
interp: PyQrackInterpreter,
|
|
87
|
+
frame: interp.Frame,
|
|
88
|
+
stmt: native.AtomLossChannel,
|
|
89
|
+
):
|
|
90
|
+
qargs: List[reg.PyQrackQubit] = frame.get(stmt.qargs)
|
|
91
|
+
|
|
92
|
+
active_qubits = (qarg for qarg in qargs if qarg.is_active())
|
|
93
|
+
|
|
94
|
+
for qarg in active_qubits:
|
|
95
|
+
if interp.rng_state.uniform() <= stmt.prob:
|
|
96
|
+
sim_reg = qarg.ref.sim_reg
|
|
97
|
+
sim_reg.force_m(qarg.addr, 0)
|
|
98
|
+
qarg.drop()
|
|
99
|
+
|
|
100
|
+
return ()
|
|
File without changes
|