Skip to content

Commit c09c6bd

Browse files
committed
Add some basic dosctrings and usage examples to pyqrack simulator (#254)
1 parent afa414e commit c09c6bd

File tree

3 files changed

+162
-19
lines changed

3 files changed

+162
-19
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: 107 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525

2626
@dataclass
2727
class PyQrackSimulatorBase(AbstractSimulatorDevice[PyQrackSimulatorTask]):
28+
"""PyQrack simulation device base class."""
29+
2830
options: PyQrackOptions = field(default_factory=_default_pyqrack_args)
31+
"""options (PyQrackOptions): options passed into the pyqrack simulator."""
32+
2933
loss_m_result: Measurement = field(default=Measurement.One, kw_only=True)
3034
rng_state: np.random.Generator = field(
3135
default_factory=np.random.default_rng, kw_only=True
@@ -101,7 +105,43 @@ def pauli_expectation(pauli: list[Pauli], qubits: list[PyQrackQubit]) -> float:
101105

102106
@dataclass
103107
class StackMemorySimulator(PyQrackSimulatorBase):
104-
"""PyQrack simulator device with precalculated stack of qubits."""
108+
"""
109+
PyQrack simulator device with preallocated stack of qubits.
110+
111+
This can be used to simulate kernels where the number of qubits is known
112+
ahead of time.
113+
114+
## Usage examples
115+
116+
```
117+
# Define a kernel
118+
@qasm2.main
119+
def main():
120+
q = qasm2.qreg(2)
121+
c = qasm2.creg(2)
122+
123+
qasm2.h(q[0])
124+
qasm2.cx(q[0], q[1])
125+
126+
qasm2.measure(q, c)
127+
return q
128+
129+
# Create the simulator object
130+
sim = StackMemorySimulator(min_qubits=2)
131+
132+
# Execute the kernel
133+
qubits = sim.run(main)
134+
```
135+
136+
You can also obtain other information from it, such as the state vector:
137+
138+
```
139+
ket = sim.state_vector(main)
140+
141+
from pyqrack.pauli import Pauli
142+
expectation_vals = sim.pauli_expectation([Pauli.PauliX, Pauli.PauliI], qubits)
143+
```
144+
"""
105145

106146
min_qubits: int = field(default=0, kw_only=True)
107147

@@ -111,6 +151,20 @@ def task(
111151
args: tuple[Any, ...] = (),
112152
kwargs: dict[str, Any] | None = None,
113153
):
154+
"""
155+
Args:
156+
kernel (ir.Method):
157+
The kernel method to run.
158+
args (tuple[Any, ...]):
159+
Positional arguments to pass to the kernel method.
160+
kwargs (dict[str, Any] | None):
161+
Keyword arguments to pass to the kernel method.
162+
163+
Returns:
164+
PyQrackSimulatorTask:
165+
The task object used to track execution.
166+
167+
"""
114168
if kwargs is None:
115169
kwargs = {}
116170

@@ -136,31 +190,67 @@ def task(
136190

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

141232
def task(
142233
self,
143234
kernel: ir.Method[Params, RetType],
144235
args: tuple[Any, ...] = (),
145236
kwargs: dict[str, Any] | None = None,
146237
):
238+
"""
239+
Args:
240+
kernel (ir.Method):
241+
The kernel method to run.
242+
args (tuple[Any, ...]):
243+
Positional arguments to pass to the kernel method.
244+
kwargs (dict[str, Any] | None):
245+
Keyword arguments to pass to the kernel method.
246+
247+
Returns:
248+
PyQrackSimulatorTask:
249+
The task object used to track execution.
250+
251+
"""
147252
if kwargs is None:
148253
kwargs = {}
149254

150255
memory = DynamicMemory(self.options.copy())
151256
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()

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(

0 commit comments

Comments
 (0)