Skip to content

Commit 6615ddf

Browse files
authored
Merge branch 'main' into phil/noise-simulator-device
2 parents ca4c049 + df16cbf commit 6615ddf

File tree

24 files changed

+220
-48
lines changed

24 files changed

+220
-48
lines changed

src/bloqade/analysis/fidelity/analysis.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,47 @@
1313
class FidelityAnalysis(Forward):
1414
"""
1515
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+
```
1641
"""
1742

1843
keys = ["circuit.fidelity"]
1944
lattice = EmptyLattice
2045

46+
gate_fidelity: float = 1.0
2147
"""
2248
The fidelity of the gate set described by the analysed program. It reduces whenever a noise channel is encountered.
2349
"""
24-
gate_fidelity: float = 1.0
2550

2651
_current_gate_fidelity: float = field(init=False)
2752

53+
atom_survival_probability: list[float] = field(init=False)
2854
"""
2955
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.
3056
"""
31-
atom_survival_probability: list[float] = field(init=False)
3257

3358
_current_atom_survival_probability: list[float] = field(init=False)
3459

src/bloqade/pyqrack/device.py

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232

3333

3434
@dataclass
35-
class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTaskType]):
35+
class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTask]):
36+
"""PyQrack simulation device base class."""
37+
3638
options: PyQrackOptions = field(default_factory=_default_pyqrack_args)
39+
"""options (PyQrackOptions): options passed into the pyqrack simulator."""
40+
3741
loss_m_result: Measurement = field(default=Measurement.One, kw_only=True)
3842
rng_state: np.random.Generator = field(
3943
default_factory=np.random.default_rng, kw_only=True
@@ -87,8 +91,44 @@ def pauli_expectation(pauli: list[Pauli], qubits: list[PyQrackQubit]) -> float:
8791

8892

8993
@dataclass
90-
class StackMemorySimulator(PyQrackSimulatorBase[PyQrackSimulatorTask]):
91-
"""PyQrack simulator device with precalculated stack of qubits."""
94+
class StackMemorySimulator(PyQrackSimulatorBase):
95+
"""
96+
PyQrack simulator device with preallocated stack of qubits.
97+
98+
This can be used to simulate kernels where the number of qubits is known
99+
ahead of time.
100+
101+
## Usage examples
102+
103+
```
104+
# Define a kernel
105+
@qasm2.main
106+
def main():
107+
q = qasm2.qreg(2)
108+
c = qasm2.creg(2)
109+
110+
qasm2.h(q[0])
111+
qasm2.cx(q[0], q[1])
112+
113+
qasm2.measure(q, c)
114+
return q
115+
116+
# Create the simulator object
117+
sim = StackMemorySimulator(min_qubits=2)
118+
119+
# Execute the kernel
120+
qubits = sim.run(main)
121+
```
122+
123+
You can also obtain other information from it, such as the state vector:
124+
125+
```
126+
ket = sim.state_vector(main)
127+
128+
from pyqrack.pauli import Pauli
129+
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)
130+
```
131+
"""
92132

93133
min_qubits: int = field(default=0, kw_only=True)
94134

@@ -98,6 +138,20 @@ def task(
98138
args: tuple[Any, ...] = (),
99139
kwargs: dict[str, Any] | None = None,
100140
):
141+
"""
142+
Args:
143+
kernel (ir.Method):
144+
The kernel method to run.
145+
args (tuple[Any, ...]):
146+
Positional arguments to pass to the kernel method.
147+
kwargs (dict[str, Any] | None):
148+
Keyword arguments to pass to the kernel method.
149+
150+
Returns:
151+
PyQrackSimulatorTask:
152+
The task object used to track execution.
153+
154+
"""
101155
if kwargs is None:
102156
kwargs = {}
103157

@@ -131,15 +185,66 @@ def task(
131185

132186

133187
@dataclass
134-
class DynamicMemorySimulator(PyQrackSimulatorBase[PyQrackSimulatorTask]):
135-
"""PyQrack simulator device with dynamic qubit allocation."""
188+
class DynamicMemorySimulator(PyQrackSimulatorBase):
189+
"""
190+
191+
PyQrack simulator device with dynamic qubit allocation.
192+
193+
This can be used to simulate kernels where the number of qubits is not known
194+
ahead of time.
195+
196+
## Usage examples
197+
198+
```
199+
# Define a kernel
200+
@qasm2.main
201+
def main():
202+
q = qasm2.qreg(2)
203+
c = qasm2.creg(2)
204+
205+
qasm2.h(q[0])
206+
qasm2.cx(q[0], q[1])
207+
208+
qasm2.measure(q, c)
209+
return q
210+
211+
# Create the simulator object
212+
sim = DynamicMemorySimulator()
213+
214+
# Execute the kernel
215+
qubits = sim.run(main)
216+
```
217+
218+
You can also obtain other information from it, such as the state vector:
219+
220+
```
221+
ket = sim.state_vector(main)
222+
223+
from pyqrack.pauli import Pauli
224+
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)
225+
226+
"""
136227

137228
def task(
138229
self,
139230
kernel: ir.Method[Params, RetType],
140231
args: tuple[Any, ...] = (),
141232
kwargs: dict[str, Any] | None = None,
142233
):
234+
"""
235+
Args:
236+
kernel (ir.Method):
237+
The kernel method to run.
238+
args (tuple[Any, ...]):
239+
Positional arguments to pass to the kernel method.
240+
kwargs (dict[str, Any] | None):
241+
Keyword arguments to pass to the kernel method.
242+
243+
Returns:
244+
PyQrackSimulatorTask:
245+
The task object used to track execution.
246+
247+
"""
143248
if kwargs is None:
144249
kwargs = {}
145250

src/bloqade/qasm2/parse/qasm2.lark

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ version: INT "." INT
99
// stmts
1010
include: "include" STRING ";"
1111
ifstmt: "if" "(" expr "==" expr ")" ifbody
12-
ifbody: qop | "{" qop* "}" // allow multiple qops
12+
ifbody: qop
1313
opaque: "opaque" IDENTIFIER ["(" [params] ")"] qubits ";"
1414
barrier: "barrier" qubits ";"
1515
qreg: "qreg" IDENTIFIER "[" INT "]" ";"

src/bloqade/qasm2/passes/noise.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,34 @@ class NoisePass(Pass):
2121
NOTE: This pass is not guaranteed to be supported long-term in bloqade. We will be
2222
moving towards a more general approach to noise modeling in the future.
2323
24+
## Usage examples
25+
26+
```
27+
from bloqade import qasm2
28+
from bloqade.noise import native
29+
from bloqade.qasm2.passes.noise import NoisePass
30+
31+
noise_main = qasm2.extended.add(native.dialect)
32+
33+
@noise_main
34+
def main():
35+
q = qasm2.qreg(2)
36+
qasm2.h(q[0])
37+
qasm2.cx(q[0], q[1])
38+
return q
39+
40+
# simple IR without any nosie
41+
main.print()
42+
43+
noise_pass = NoisePass(noise_main)
44+
45+
# rewrite stuff in-place
46+
noise_pass.unsafe_run(main)
47+
48+
# now, we do have noise channels in the IR
49+
main.print()
50+
```
51+
2452
"""
2553

2654
noise_model: native.MoveNoiseModelABC = field(

src/bloqade/stim/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .groups import main as main
22
from ._wrappers import * # noqa: F403
3-
from .dialects.aux import * # noqa F403
43
from .dialects.gate import * # noqa F403
54
from .dialects.noise import * # noqa F403
65
from .dialects.collapse import * # noqa F403
6+
from .dialects.auxiliary import * # noqa F403

src/bloqade/stim/_wrappers.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from kirin.lowering import wraps
44

5-
from .dialects import aux, gate, noise, collapse
5+
from .dialects import gate, noise, collapse, auxiliary
66

77

88
# dialect:: gate
@@ -69,32 +69,34 @@ def cz(
6969

7070
## pp
7171
@wraps(gate.SPP)
72-
def spp(targets: tuple[aux.PauliString, ...], dagger=False) -> None: ...
72+
def spp(targets: tuple[auxiliary.PauliString, ...], dagger=False) -> None: ...
7373

7474

7575
# dialect:: aux
76-
@wraps(aux.GetRecord)
77-
def rec(id: int) -> aux.RecordResult: ...
76+
@wraps(auxiliary.GetRecord)
77+
def rec(id: int) -> auxiliary.RecordResult: ...
7878

7979

80-
@wraps(aux.Detector)
80+
@wraps(auxiliary.Detector)
8181
def detector(
82-
coord: tuple[Union[int, float], ...], targets: tuple[aux.RecordResult, ...]
82+
coord: tuple[Union[int, float], ...], targets: tuple[auxiliary.RecordResult, ...]
8383
) -> None: ...
8484

8585

86-
@wraps(aux.ObservableInclude)
87-
def observable_include(idx: int, targets: tuple[aux.RecordResult, ...]) -> None: ...
86+
@wraps(auxiliary.ObservableInclude)
87+
def observable_include(
88+
idx: int, targets: tuple[auxiliary.RecordResult, ...]
89+
) -> None: ...
8890

8991

90-
@wraps(aux.Tick)
92+
@wraps(auxiliary.Tick)
9193
def tick() -> None: ...
9294

9395

94-
@wraps(aux.NewPauliString)
96+
@wraps(auxiliary.NewPauliString)
9597
def pauli_string(
9698
string: tuple[str, ...], flipped: tuple[bool, ...], targets: tuple[int, ...]
97-
) -> aux.PauliString: ...
99+
) -> auxiliary.PauliString: ...
98100

99101

100102
# dialect:: collapse
@@ -123,7 +125,7 @@ def mxx(p: float, targets: tuple[int, ...]) -> None: ...
123125

124126

125127
@wraps(collapse.PPMeasurement)
126-
def mpp(p: float, targets: tuple[aux.PauliString, ...]) -> None: ...
128+
def mpp(p: float, targets: tuple[auxiliary.PauliString, ...]) -> None: ...
127129

128130

129131
@wraps(collapse.RZ)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from . import aux as aux, gate as gate, noise as noise, collapse as collapse
2-
from .aux.stmts import * # noqa F403
1+
from . import gate as gate, noise as noise, collapse as collapse, auxiliary as auxiliary
32
from .gate.stmts import * # noqa F403
43
from .noise.stmts import * # noqa F403
54
from .collapse.stmts import * # noqa F403
5+
from .auxiliary.stmts import * # noqa F403
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)