bloqade-circuit 0.2.2__py3-none-any.whl → 0.3.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/address/impls.py +14 -0
- bloqade/analysis/fidelity/analysis.py +27 -2
- bloqade/noise/fidelity.py +3 -3
- bloqade/noise/native/_dialect.py +1 -1
- bloqade/noise/native/_wrappers.py +35 -6
- bloqade/noise/native/stmts.py +1 -1
- bloqade/pyqrack/device.py +109 -21
- bloqade/pyqrack/qasm2/core.py +4 -1
- bloqade/pyqrack/squin/qubit.py +16 -9
- bloqade/pyqrack/squin/wire.py +22 -4
- bloqade/pyqrack/task.py +13 -5
- bloqade/qasm2/__init__.py +1 -0
- bloqade/qasm2/_qasm_loading.py +151 -0
- bloqade/qasm2/dialects/core/__init__.py +9 -1
- bloqade/qasm2/dialects/expr/__init__.py +18 -1
- bloqade/qasm2/dialects/noise.py +33 -1
- bloqade/qasm2/dialects/uop/__init__.py +39 -3
- bloqade/qasm2/dialects/uop/schedule.py +1 -1
- bloqade/qasm2/emit/impls/__init__.py +1 -0
- bloqade/qasm2/emit/impls/noise_native.py +89 -0
- bloqade/qasm2/emit/main.py +21 -0
- bloqade/qasm2/emit/target.py +20 -5
- bloqade/qasm2/groups.py +2 -0
- bloqade/qasm2/parse/__init__.py +7 -4
- bloqade/qasm2/parse/lowering.py +20 -130
- bloqade/qasm2/parse/qasm2.lark +1 -1
- bloqade/qasm2/passes/__init__.py +1 -0
- bloqade/qasm2/passes/fold.py +6 -0
- bloqade/qasm2/passes/noise.py +50 -2
- bloqade/qasm2/passes/parallel.py +9 -0
- bloqade/qasm2/passes/unroll_if.py +25 -0
- bloqade/qasm2/rewrite/__init__.py +1 -0
- bloqade/qasm2/rewrite/desugar.py +3 -2
- bloqade/qasm2/rewrite/heuristic_noise.py +1 -9
- bloqade/qasm2/rewrite/native_gates.py +67 -4
- bloqade/qasm2/rewrite/split_ifs.py +66 -0
- bloqade/squin/analysis/nsites/__init__.py +1 -0
- bloqade/squin/analysis/nsites/impls.py +25 -1
- bloqade/squin/noise/__init__.py +7 -26
- bloqade/squin/noise/_wrapper.py +25 -0
- bloqade/squin/op/__init__.py +33 -159
- bloqade/squin/op/_wrapper.py +101 -0
- bloqade/squin/op/stdlib.py +62 -0
- bloqade/squin/passes/__init__.py +1 -0
- bloqade/squin/passes/stim.py +68 -0
- bloqade/squin/rewrite/__init__.py +11 -0
- bloqade/squin/rewrite/qubit_to_stim.py +84 -0
- bloqade/squin/rewrite/squin_measure.py +98 -0
- bloqade/squin/rewrite/stim_rewrite_util.py +158 -0
- bloqade/squin/rewrite/wire_identity_elimination.py +24 -0
- bloqade/squin/rewrite/wire_to_stim.py +73 -0
- bloqade/squin/rewrite/wrap_analysis.py +72 -0
- bloqade/squin/wire.py +1 -13
- bloqade/stim/__init__.py +39 -5
- bloqade/stim/_wrappers.py +14 -12
- bloqade/stim/dialects/__init__.py +1 -5
- bloqade/stim/dialects/{aux → auxiliary}/__init__.py +12 -1
- bloqade/stim/dialects/{aux → auxiliary}/emit.py +1 -1
- bloqade/stim/dialects/collapse/__init__.py +13 -2
- bloqade/stim/dialects/collapse/{emit.py → emit_str.py} +1 -1
- bloqade/stim/dialects/collapse/stmts/pp_measure.py +1 -1
- bloqade/stim/dialects/gate/__init__.py +16 -1
- bloqade/stim/dialects/gate/emit.py +1 -1
- bloqade/stim/dialects/gate/stmts/base.py +1 -1
- bloqade/stim/dialects/gate/stmts/pp.py +1 -1
- bloqade/stim/dialects/noise/emit.py +1 -1
- bloqade/stim/emit/__init__.py +1 -1
- bloqade/stim/groups.py +4 -2
- {bloqade_circuit-0.2.2.dist-info → bloqade_circuit-0.3.0.dist-info}/METADATA +3 -3
- {bloqade_circuit-0.2.2.dist-info → bloqade_circuit-0.3.0.dist-info}/RECORD +80 -64
- /bloqade/stim/dialects/{aux → auxiliary}/_dialect.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/interp.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/lowering.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/stmts/__init__.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/stmts/annotate.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/stmts/const.py +0 -0
- /bloqade/stim/dialects/{aux → auxiliary}/types.py +0 -0
- /bloqade/stim/emit/{stim.py → stim_str.py} +0 -0
- {bloqade_circuit-0.2.2.dist-info → bloqade_circuit-0.3.0.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.2.2.dist-info → bloqade_circuit-0.3.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -206,6 +206,20 @@ class SquinWireMethodTable(interp.MethodTable):
|
|
|
206
206
|
):
|
|
207
207
|
return frame.get_values(stmt.inputs)
|
|
208
208
|
|
|
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
|
+
|
|
209
223
|
|
|
210
224
|
@squin.qubit.dialect.register(key="qubit.address")
|
|
211
225
|
class SquinQubitMethodTable(interp.MethodTable):
|
|
@@ -13,22 +13,47 @@ from ..address import AddressAnalysis
|
|
|
13
13
|
class FidelityAnalysis(Forward):
|
|
14
14
|
"""
|
|
15
15
|
This analysis pass can be used to track the global addresses of qubits and wires.
|
|
16
|
+
|
|
17
|
+
## Usage examples
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
from bloqade import qasm2
|
|
21
|
+
from bloqade.noise import native
|
|
22
|
+
from bloqade.analysis.fidelity import FidelityAnalysis
|
|
23
|
+
from bloqade.qasm2.passes.noise import NoisePass
|
|
24
|
+
|
|
25
|
+
noise_main = qasm2.extended.add(native.dialect)
|
|
26
|
+
|
|
27
|
+
@noise_main
|
|
28
|
+
def main():
|
|
29
|
+
q = qasm2.qreg(2)
|
|
30
|
+
qasm2.x(q[0])
|
|
31
|
+
return q
|
|
32
|
+
|
|
33
|
+
NoisePass(main.dialects)(main)
|
|
34
|
+
|
|
35
|
+
fid_analysis = FidelityAnalysis(main.dialects)
|
|
36
|
+
fid_analysis.run_analysis(main, no_raise=False)
|
|
37
|
+
|
|
38
|
+
gate_fidelity = fid_analysis.gate_fidelity
|
|
39
|
+
atom_survival_probs = fid_analysis.atom_survival_probability
|
|
40
|
+
```
|
|
16
41
|
"""
|
|
17
42
|
|
|
18
43
|
keys = ["circuit.fidelity"]
|
|
19
44
|
lattice = EmptyLattice
|
|
20
45
|
|
|
46
|
+
gate_fidelity: float = 1.0
|
|
21
47
|
"""
|
|
22
48
|
The fidelity of the gate set described by the analysed program. It reduces whenever a noise channel is encountered.
|
|
23
49
|
"""
|
|
24
|
-
gate_fidelity: float = 1.0
|
|
25
50
|
|
|
26
51
|
_current_gate_fidelity: float = field(init=False)
|
|
27
52
|
|
|
53
|
+
atom_survival_probability: list[float] = field(init=False)
|
|
28
54
|
"""
|
|
29
55
|
The probabilities that each of the atoms in the register survive the duration of the analysed program. The order of the list follows the order they are in the register.
|
|
30
56
|
"""
|
|
31
|
-
atom_survival_probability: list[float] = field(init=False)
|
|
32
57
|
|
|
33
58
|
_current_atom_survival_probability: list[float] = field(init=False)
|
|
34
59
|
|
bloqade/noise/fidelity.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from kirin import interp
|
|
2
2
|
from kirin.lattice import EmptyLattice
|
|
3
3
|
|
|
4
|
+
from bloqade.analysis.address import AddressQubit, AddressTuple
|
|
4
5
|
from bloqade.analysis.fidelity import FidelityAnalysis
|
|
5
6
|
|
|
6
|
-
from .native import dialect
|
|
7
|
+
from .native import dialect
|
|
7
8
|
from .native.stmts import PauliChannel, CZPauliChannel, AtomLossChannel
|
|
8
|
-
from ..analysis.address import AddressQubit, AddressTuple
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@
|
|
11
|
+
@dialect.register(key="circuit.fidelity")
|
|
12
12
|
class FidelityMethodTable(interp.MethodTable):
|
|
13
13
|
|
|
14
14
|
@interp.impl(PauliChannel)
|
bloqade/noise/native/_dialect.py
CHANGED
|
@@ -8,21 +8,34 @@ from bloqade.qasm2.types import Qubit
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@wraps(native.AtomLossChannel)
|
|
11
|
-
def atom_loss_channel(
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def atom_loss_channel(qargs: ilist.IList[Qubit, Any] | list, *, prob: float) -> None:
|
|
12
|
+
"""Apply an atom loss channel to a list of qubits.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
qargs (ilist.IList[Qubit, Any] | list): List of qubits to apply the noise to.
|
|
16
|
+
prob (float): The loss probability.
|
|
17
|
+
"""
|
|
18
|
+
...
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
@wraps(native.PauliChannel)
|
|
17
22
|
def pauli_channel(
|
|
18
23
|
qargs: ilist.IList[Qubit, Any] | list, *, px: float, py: float, pz: float
|
|
19
|
-
) -> None:
|
|
24
|
+
) -> None:
|
|
25
|
+
"""Apply a Pauli channel to a list of qubits.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
qargs (ilist.IList[Qubit, Any] | list): List of qubits to apply the noise to.
|
|
29
|
+
px (float): Probability of X error.
|
|
30
|
+
py (float): Probability of Y error.
|
|
31
|
+
pz (float): Probability of Z error.
|
|
32
|
+
"""
|
|
20
33
|
|
|
21
34
|
|
|
22
35
|
@wraps(native.CZPauliChannel)
|
|
23
36
|
def cz_pauli_channel(
|
|
24
37
|
ctrls: ilist.IList[Qubit, Any] | list,
|
|
25
|
-
|
|
38
|
+
qargs: ilist.IList[Qubit, Any] | list,
|
|
26
39
|
*,
|
|
27
40
|
px_ctrl: float,
|
|
28
41
|
py_ctrl: float,
|
|
@@ -31,4 +44,20 @@ def cz_pauli_channel(
|
|
|
31
44
|
py_qarg: float,
|
|
32
45
|
pz_qarg: float,
|
|
33
46
|
paired: bool,
|
|
34
|
-
) -> None:
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Insert noise for a CZ gate with a Pauli channel on qubits.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
ctrls: List of control qubits.
|
|
52
|
+
qarg2: List of target qubits.
|
|
53
|
+
px_ctrl: Probability of X error on control qubits.
|
|
54
|
+
py_ctrl: Probability of Y error on control qubits.
|
|
55
|
+
pz_ctrl: Probability of Z error on control qubits.
|
|
56
|
+
px_qarg: Probability of X error on target qubits.
|
|
57
|
+
py_qarg: Probability of Y error on target qubits.
|
|
58
|
+
pz_qarg: Probability of Z error on target qubits.
|
|
59
|
+
paired: If True, the noise is applied to both control and target qubits
|
|
60
|
+
are not lost otherwise skip this error. If False Apply the noise on
|
|
61
|
+
the whatever qubit is not lost.
|
|
62
|
+
"""
|
|
63
|
+
...
|
bloqade/noise/native/stmts.py
CHANGED
bloqade/pyqrack/device.py
CHANGED
|
@@ -25,7 +25,11 @@ Params = ParamSpec("Params")
|
|
|
25
25
|
|
|
26
26
|
@dataclass
|
|
27
27
|
class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTask]):
|
|
28
|
+
"""PyQrack simulation device base class."""
|
|
29
|
+
|
|
28
30
|
options: PyQrackOptions = field(default_factory=_default_pyqrack_args)
|
|
31
|
+
"""options (PyQrackOptions): options passed into the pyqrack simulator."""
|
|
32
|
+
|
|
29
33
|
loss_m_result: Measurement = field(default=Measurement.One, kw_only=True)
|
|
30
34
|
rng_state: np.random.Generator = field(
|
|
31
35
|
default_factory=np.random.default_rng, kw_only=True
|
|
@@ -60,9 +64,7 @@ class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTask]):
|
|
|
60
64
|
kwargs: dict[str, Any] | None = None,
|
|
61
65
|
) -> list[complex]:
|
|
62
66
|
"""Runs task and returns the state vector."""
|
|
63
|
-
|
|
64
|
-
task.run()
|
|
65
|
-
return task.state.sim_reg.out_ket()
|
|
67
|
+
return self.task(kernel, args, kwargs).state_vector()
|
|
66
68
|
|
|
67
69
|
@staticmethod
|
|
68
70
|
def pauli_expectation(pauli: list[Pauli], qubits: list[PyQrackQubit]) -> float:
|
|
@@ -96,12 +98,48 @@ class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTask]):
|
|
|
96
98
|
if len(qubit_ids) != len(set(qubit_ids)):
|
|
97
99
|
raise ValueError("Qubits must be unique.")
|
|
98
100
|
|
|
99
|
-
return sim_reg.pauli_expectation(
|
|
101
|
+
return sim_reg.pauli_expectation(qubit_ids, pauli)
|
|
100
102
|
|
|
101
103
|
|
|
102
104
|
@dataclass
|
|
103
105
|
class StackMemorySimulator(PyQrackSimulatorBase):
|
|
104
|
-
"""
|
|
106
|
+
"""
|
|
107
|
+
PyQrack simulator device with preallocated stack of qubits.
|
|
108
|
+
|
|
109
|
+
This can be used to simulate kernels where the number of qubits is known
|
|
110
|
+
ahead of time.
|
|
111
|
+
|
|
112
|
+
## Usage examples
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
# Define a kernel
|
|
116
|
+
@qasm2.main
|
|
117
|
+
def main():
|
|
118
|
+
q = qasm2.qreg(2)
|
|
119
|
+
c = qasm2.creg(2)
|
|
120
|
+
|
|
121
|
+
qasm2.h(q[0])
|
|
122
|
+
qasm2.cx(q[0], q[1])
|
|
123
|
+
|
|
124
|
+
qasm2.measure(q, c)
|
|
125
|
+
return q
|
|
126
|
+
|
|
127
|
+
# Create the simulator object
|
|
128
|
+
sim = StackMemorySimulator(min_qubits=2)
|
|
129
|
+
|
|
130
|
+
# Execute the kernel
|
|
131
|
+
qubits = sim.run(main)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
You can also obtain other information from it, such as the state vector:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
ket = sim.state_vector(main)
|
|
138
|
+
|
|
139
|
+
from pyqrack.pauli import Pauli
|
|
140
|
+
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)
|
|
141
|
+
```
|
|
142
|
+
"""
|
|
105
143
|
|
|
106
144
|
min_qubits: int = field(default=0, kw_only=True)
|
|
107
145
|
|
|
@@ -111,6 +149,20 @@ class StackMemorySimulator(PyQrackSimulatorBase):
|
|
|
111
149
|
args: tuple[Any, ...] = (),
|
|
112
150
|
kwargs: dict[str, Any] | None = None,
|
|
113
151
|
):
|
|
152
|
+
"""
|
|
153
|
+
Args:
|
|
154
|
+
kernel (ir.Method):
|
|
155
|
+
The kernel method to run.
|
|
156
|
+
args (tuple[Any, ...]):
|
|
157
|
+
Positional arguments to pass to the kernel method.
|
|
158
|
+
kwargs (dict[str, Any] | None):
|
|
159
|
+
Keyword arguments to pass to the kernel method.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
PyQrackSimulatorTask:
|
|
163
|
+
The task object used to track execution.
|
|
164
|
+
|
|
165
|
+
"""
|
|
114
166
|
if kwargs is None:
|
|
115
167
|
kwargs = {}
|
|
116
168
|
|
|
@@ -136,7 +188,44 @@ class StackMemorySimulator(PyQrackSimulatorBase):
|
|
|
136
188
|
|
|
137
189
|
@dataclass
|
|
138
190
|
class DynamicMemorySimulator(PyQrackSimulatorBase):
|
|
139
|
-
"""
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
PyQrack simulator device with dynamic qubit allocation.
|
|
194
|
+
|
|
195
|
+
This can be used to simulate kernels where the number of qubits is not known
|
|
196
|
+
ahead of time.
|
|
197
|
+
|
|
198
|
+
## Usage examples
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
# Define a kernel
|
|
202
|
+
@qasm2.main
|
|
203
|
+
def main():
|
|
204
|
+
q = qasm2.qreg(2)
|
|
205
|
+
c = qasm2.creg(2)
|
|
206
|
+
|
|
207
|
+
qasm2.h(q[0])
|
|
208
|
+
qasm2.cx(q[0], q[1])
|
|
209
|
+
|
|
210
|
+
qasm2.measure(q, c)
|
|
211
|
+
return q
|
|
212
|
+
|
|
213
|
+
# Create the simulator object
|
|
214
|
+
sim = DynamicMemorySimulator()
|
|
215
|
+
|
|
216
|
+
# Execute the kernel
|
|
217
|
+
qubits = sim.run(main)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
You can also obtain other information from it, such as the state vector:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
ket = sim.state_vector(main)
|
|
224
|
+
|
|
225
|
+
from pyqrack.pauli import Pauli
|
|
226
|
+
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)
|
|
227
|
+
|
|
228
|
+
"""
|
|
140
229
|
|
|
141
230
|
def task(
|
|
142
231
|
self,
|
|
@@ -144,23 +233,22 @@ class DynamicMemorySimulator(PyQrackSimulatorBase):
|
|
|
144
233
|
args: tuple[Any, ...] = (),
|
|
145
234
|
kwargs: dict[str, Any] | None = None,
|
|
146
235
|
):
|
|
236
|
+
"""
|
|
237
|
+
Args:
|
|
238
|
+
kernel (ir.Method):
|
|
239
|
+
The kernel method to run.
|
|
240
|
+
args (tuple[Any, ...]):
|
|
241
|
+
Positional arguments to pass to the kernel method.
|
|
242
|
+
kwargs (dict[str, Any] | None):
|
|
243
|
+
Keyword arguments to pass to the kernel method.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
PyQrackSimulatorTask:
|
|
247
|
+
The task object used to track execution.
|
|
248
|
+
|
|
249
|
+
"""
|
|
147
250
|
if kwargs is None:
|
|
148
251
|
kwargs = {}
|
|
149
252
|
|
|
150
253
|
memory = DynamicMemory(self.options.copy())
|
|
151
254
|
return self.new_task(kernel, args, kwargs, memory)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def test():
|
|
155
|
-
from bloqade.qasm2 import extended
|
|
156
|
-
|
|
157
|
-
@extended
|
|
158
|
-
def main():
|
|
159
|
-
return 1
|
|
160
|
-
|
|
161
|
-
@extended
|
|
162
|
-
def obs(result: int) -> int:
|
|
163
|
-
return result
|
|
164
|
-
|
|
165
|
-
res = DynamicMemorySimulator().task(main)
|
|
166
|
-
return res.run()
|
bloqade/pyqrack/qasm2/core.py
CHANGED
|
@@ -80,7 +80,10 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
80
80
|
@interp.impl(core.Reset)
|
|
81
81
|
def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: core.Reset):
|
|
82
82
|
qarg: PyQrackQubit = frame.get(stmt.qarg)
|
|
83
|
-
|
|
83
|
+
|
|
84
|
+
if bool(qarg.sim_reg.m(qarg.addr)):
|
|
85
|
+
qarg.sim_reg.x(qarg.addr)
|
|
86
|
+
|
|
84
87
|
return ()
|
|
85
88
|
|
|
86
89
|
@interp.impl(core.CRegEq)
|
bloqade/pyqrack/squin/qubit.py
CHANGED
|
@@ -37,9 +37,11 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
37
37
|
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
38
38
|
operator.broadcast_apply(qubits)
|
|
39
39
|
|
|
40
|
-
def _measure_qubit(self, qbit: PyQrackQubit):
|
|
40
|
+
def _measure_qubit(self, qbit: PyQrackQubit, interp: PyQrackInterpreter):
|
|
41
41
|
if qbit.is_active():
|
|
42
42
|
return bool(qbit.sim_reg.m(qbit.addr))
|
|
43
|
+
else:
|
|
44
|
+
return interp.loss_m_result
|
|
43
45
|
|
|
44
46
|
@interp.impl(qubit.MeasureQubitList)
|
|
45
47
|
def measure_qubit_list(
|
|
@@ -49,7 +51,7 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
49
51
|
stmt: qubit.MeasureQubitList,
|
|
50
52
|
):
|
|
51
53
|
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
52
|
-
result = ilist.IList([self._measure_qubit(qbit) for qbit in qubits])
|
|
54
|
+
result = ilist.IList([self._measure_qubit(qbit, interp) for qbit in qubits])
|
|
53
55
|
return (result,)
|
|
54
56
|
|
|
55
57
|
@interp.impl(qubit.MeasureQubit)
|
|
@@ -57,7 +59,7 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
57
59
|
self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: qubit.MeasureQubit
|
|
58
60
|
):
|
|
59
61
|
qbit: PyQrackQubit = frame.get(stmt.qubit)
|
|
60
|
-
result = self._measure_qubit(qbit)
|
|
62
|
+
result = self._measure_qubit(qbit, interp)
|
|
61
63
|
return (result,)
|
|
62
64
|
|
|
63
65
|
@interp.impl(qubit.MeasureAndReset)
|
|
@@ -70,11 +72,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
70
72
|
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
71
73
|
result = []
|
|
72
74
|
for qbit in qubits:
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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)
|
|
78
81
|
|
|
79
82
|
return (ilist.IList(result),)
|
|
80
83
|
|
|
@@ -82,4 +85,8 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
82
85
|
def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: qubit.Reset):
|
|
83
86
|
qubits: ilist.IList[PyQrackQubit, Any] = frame.get(stmt.qubits)
|
|
84
87
|
for qbit in qubits:
|
|
85
|
-
qbit.
|
|
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/wire.py
CHANGED
|
@@ -43,7 +43,11 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
43
43
|
):
|
|
44
44
|
w: PyQrackWire = frame.get(stmt.wire)
|
|
45
45
|
qbit = w.qubit
|
|
46
|
-
|
|
46
|
+
|
|
47
|
+
if not qbit.is_active():
|
|
48
|
+
return (interp.loss_m_result,)
|
|
49
|
+
|
|
50
|
+
res: bool = bool(qbit.sim_reg.m(qbit.addr))
|
|
47
51
|
return (res,)
|
|
48
52
|
|
|
49
53
|
@interp.impl(wire.MeasureAndReset)
|
|
@@ -55,8 +59,16 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
55
59
|
):
|
|
56
60
|
w: PyQrackWire = frame.get(stmt.wire)
|
|
57
61
|
qbit = w.qubit
|
|
58
|
-
|
|
59
|
-
qbit.
|
|
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
|
|
60
72
|
new_w = PyQrackWire(qbit)
|
|
61
73
|
return (new_w, res)
|
|
62
74
|
|
|
@@ -64,6 +76,12 @@ class PyQrackMethods(interp.MethodTable):
|
|
|
64
76
|
def reset(self, interp: PyQrackInterpreter, frame: interp.Frame, stmt: wire.Reset):
|
|
65
77
|
w: PyQrackWire = frame.get(stmt.wire)
|
|
66
78
|
qbit = w.qubit
|
|
67
|
-
|
|
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
|
+
|
|
68
86
|
new_w = PyQrackWire(qbit)
|
|
69
87
|
return (new_w,)
|
bloqade/pyqrack/task.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TypeVar, ParamSpec
|
|
1
|
+
from typing import TypeVar, ParamSpec, cast
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
|
|
4
4
|
from bloqade.task import AbstractSimulatorTask
|
|
@@ -19,12 +19,20 @@ class PyQrackSimulatorTask(AbstractSimulatorTask[Param, RetType, MemoryType]):
|
|
|
19
19
|
pyqrack_interp: PyQrackInterpreter[MemoryType]
|
|
20
20
|
|
|
21
21
|
def run(self) -> RetType:
|
|
22
|
-
return
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
return cast(
|
|
23
|
+
RetType,
|
|
24
|
+
self.pyqrack_interp.run(
|
|
25
|
+
self.kernel,
|
|
26
|
+
args=self.args,
|
|
27
|
+
kwargs=self.kwargs,
|
|
28
|
+
),
|
|
26
29
|
)
|
|
27
30
|
|
|
28
31
|
@property
|
|
29
32
|
def state(self) -> MemoryType:
|
|
30
33
|
return self.pyqrack_interp.memory
|
|
34
|
+
|
|
35
|
+
def state_vector(self) -> list[complex]:
|
|
36
|
+
"""Returns the state vector of the simulator."""
|
|
37
|
+
self.run()
|
|
38
|
+
return self.state.sim_reg.out_ket()
|
bloqade/qasm2/__init__.py
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from kirin import ir, lowering
|
|
7
|
+
from kirin.dialects import func
|
|
8
|
+
|
|
9
|
+
from . import parse
|
|
10
|
+
from .groups import main
|
|
11
|
+
from .parse.lowering import QASM2
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def loads(
|
|
15
|
+
qasm: str,
|
|
16
|
+
*,
|
|
17
|
+
kernel_name: str = "main",
|
|
18
|
+
dialects: ir.DialectGroup | None = None,
|
|
19
|
+
returns: str | None = None,
|
|
20
|
+
globals: dict[str, Any] | None = None,
|
|
21
|
+
file: str | None = None,
|
|
22
|
+
lineno_offset: int = 0,
|
|
23
|
+
col_offset: int = 0,
|
|
24
|
+
compactify: bool = True,
|
|
25
|
+
) -> ir.Method[[], None]:
|
|
26
|
+
"""Loads a QASM2 string and returns the corresponding kernel object.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
qasm (str): The QASM2 string to load.
|
|
30
|
+
|
|
31
|
+
Keyword Args:
|
|
32
|
+
kernel_name (str): The name of the kernel to load. Defaults to "main".
|
|
33
|
+
dialects (ir.DialectGroup | None): The dialects to use. Defaults to `qasm2.main`.
|
|
34
|
+
returns (str | None): The return type of the kernel. Defaults to None.
|
|
35
|
+
globals (dict[str, Any] | None): The global variables to use. Defaults to None.
|
|
36
|
+
file (str | None): The file name for error reporting. Defaults to None.
|
|
37
|
+
lineno_offset (int): The line number offset for error reporting. Defaults to 0.
|
|
38
|
+
col_offset (int): The column number offset for error reporting. Defaults to 0.
|
|
39
|
+
compactify (bool): Whether to compactify the output. Defaults to True.
|
|
40
|
+
|
|
41
|
+
Example:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from bloqade import qasm2
|
|
45
|
+
method = qasm2.loads('''
|
|
46
|
+
OPENQASM 2.0;
|
|
47
|
+
qreg q[2];
|
|
48
|
+
creg c[2];
|
|
49
|
+
h q[0];
|
|
50
|
+
cx q[0], q[1];
|
|
51
|
+
measure q[0] -> c[0];
|
|
52
|
+
''')
|
|
53
|
+
```
|
|
54
|
+
"""
|
|
55
|
+
# TODO: add source info
|
|
56
|
+
stmt = parse.loads(qasm)
|
|
57
|
+
qasm2_lowering = QASM2(dialects or main)
|
|
58
|
+
frame = qasm2_lowering.get_frame(
|
|
59
|
+
stmt,
|
|
60
|
+
source=qasm,
|
|
61
|
+
file=file,
|
|
62
|
+
globals=globals,
|
|
63
|
+
lineno_offset=lineno_offset,
|
|
64
|
+
col_offset=col_offset,
|
|
65
|
+
compactify=compactify,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if returns is not None:
|
|
69
|
+
return_value = frame.get(returns)
|
|
70
|
+
if return_value is None:
|
|
71
|
+
raise lowering.BuildError(f"Cannot find return value {returns}")
|
|
72
|
+
else:
|
|
73
|
+
return_value = func.ConstantNone()
|
|
74
|
+
frame.push(return_value)
|
|
75
|
+
|
|
76
|
+
return_node = frame.push(func.Return(value_or_stmt=return_value))
|
|
77
|
+
|
|
78
|
+
body = frame.curr_region
|
|
79
|
+
code = func.Function(
|
|
80
|
+
sym_name=kernel_name,
|
|
81
|
+
signature=func.Signature((), return_node.value.type),
|
|
82
|
+
body=body,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return ir.Method(
|
|
86
|
+
mod=None,
|
|
87
|
+
py_func=None,
|
|
88
|
+
sym_name=kernel_name,
|
|
89
|
+
arg_names=[],
|
|
90
|
+
dialects=qasm2_lowering.dialects,
|
|
91
|
+
code=code,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def loadfile(
|
|
96
|
+
qasm_file: str | pathlib.Path,
|
|
97
|
+
*,
|
|
98
|
+
kernel_name: str = "main",
|
|
99
|
+
dialects: ir.DialectGroup | None = None,
|
|
100
|
+
returns: str | None = None,
|
|
101
|
+
globals: dict[str, Any] | None = None,
|
|
102
|
+
file: str | None = None,
|
|
103
|
+
lineno_offset: int = 0,
|
|
104
|
+
col_offset: int = 0,
|
|
105
|
+
compactify: bool = True,
|
|
106
|
+
) -> ir.Method[[], None]:
|
|
107
|
+
"""Loads a QASM2 file and returns the corresponding kernel object. See also `loads`.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
qasm_file (str): The QASM2 file to load.
|
|
111
|
+
|
|
112
|
+
Keyword Args:
|
|
113
|
+
kernel_name (str): The name of the kernel to load. Defaults to "main".
|
|
114
|
+
dialects (ir.DialectGroup | None): The dialects to use. Defaults to `qasm2.main`.
|
|
115
|
+
returns (str | None): The return type of the kernel. Defaults to None.
|
|
116
|
+
globals (dict[str, Any] | None): The global variables to use. Defaults to None.
|
|
117
|
+
file (str | None): The file name for error reporting. Defaults to None.
|
|
118
|
+
lineno_offset (int): The line number offset for error reporting. Defaults to 0.
|
|
119
|
+
col_offset (int): The column number offset for error reporting. Defaults to 0.
|
|
120
|
+
compactify (bool): Whether to compactify the output. Defaults to True.
|
|
121
|
+
"""
|
|
122
|
+
if isinstance(file, pathlib.Path):
|
|
123
|
+
qasm_file_: pathlib.Path = qasm_file # type: ignore
|
|
124
|
+
else:
|
|
125
|
+
qasm_file_ = pathlib.Path(*os.path.split(qasm_file))
|
|
126
|
+
|
|
127
|
+
if not qasm_file_.is_file():
|
|
128
|
+
raise FileNotFoundError(f"File {qasm_file_} does not exist")
|
|
129
|
+
|
|
130
|
+
if not qasm_file_.name.endswith(".qasm") or not qasm_file_.name.endswith(".qasm2"):
|
|
131
|
+
logging.warning(
|
|
132
|
+
f"File {qasm_file_} does not end with .qasm or .qasm2. "
|
|
133
|
+
"This may cause issues with loading the file."
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
kernel_name = file.name.replace(".qasm", "") if kernel_name is None else kernel_name
|
|
137
|
+
|
|
138
|
+
with qasm_file_.open("r") as f:
|
|
139
|
+
source = f.read()
|
|
140
|
+
|
|
141
|
+
return loads(
|
|
142
|
+
source,
|
|
143
|
+
kernel_name=kernel_name,
|
|
144
|
+
dialects=dialects,
|
|
145
|
+
returns=returns,
|
|
146
|
+
globals=globals,
|
|
147
|
+
file=file,
|
|
148
|
+
lineno_offset=lineno_offset,
|
|
149
|
+
col_offset=col_offset,
|
|
150
|
+
compactify=compactify,
|
|
151
|
+
)
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
from . import _emit as _emit, address as address, _typeinfer as _typeinfer
|
|
2
|
-
from .stmts import
|
|
2
|
+
from .stmts import (
|
|
3
|
+
Reset as Reset,
|
|
4
|
+
CRegEq as CRegEq,
|
|
5
|
+
CRegGet as CRegGet,
|
|
6
|
+
CRegNew as CRegNew,
|
|
7
|
+
Measure as Measure,
|
|
8
|
+
QRegGet as QRegGet,
|
|
9
|
+
QRegNew as QRegNew,
|
|
10
|
+
)
|
|
3
11
|
from ._dialect import dialect as dialect
|
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
from . import _emit as _emit, _interp as _interp, _from_python as _from_python
|
|
2
|
-
from .stmts import
|
|
2
|
+
from .stmts import (
|
|
3
|
+
Add as Add,
|
|
4
|
+
Cos as Cos,
|
|
5
|
+
Div as Div,
|
|
6
|
+
Exp as Exp,
|
|
7
|
+
Log as Log,
|
|
8
|
+
Mul as Mul,
|
|
9
|
+
Neg as Neg,
|
|
10
|
+
Pow as Pow,
|
|
11
|
+
Sin as Sin,
|
|
12
|
+
Sub as Sub,
|
|
13
|
+
Tan as Tan,
|
|
14
|
+
Sqrt as Sqrt,
|
|
15
|
+
ConstPI as ConstPI,
|
|
16
|
+
ConstInt as ConstInt,
|
|
17
|
+
ConstFloat as ConstFloat,
|
|
18
|
+
GateFunction as GateFunction,
|
|
19
|
+
)
|
|
3
20
|
from ._dialect import dialect as dialect
|