Skip to content

Commit ed86501

Browse files
authored
Add some basic dosctrings and usage examples to pyqrack simulator (#254)
1 parent e33e6af commit ed86501

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
@@ -99,7 +103,43 @@ def pauli_expectation(pauli: list[Pauli], qubits: list[PyQrackQubit]) -> float:
99103

100104
@dataclass
101105
class StackMemorySimulator(PyQrackSimulatorBase):
102-
"""PyQrack simulator device with precalculated stack of qubits."""
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+
"""
103143

104144
min_qubits: int = field(default=0, kw_only=True)
105145

@@ -109,6 +149,20 @@ def task(
109149
args: tuple[Any, ...] = (),
110150
kwargs: dict[str, Any] | None = None,
111151
):
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+
"""
112166
if kwargs is None:
113167
kwargs = {}
114168

@@ -134,31 +188,67 @@ def task(
134188

135189
@dataclass
136190
class DynamicMemorySimulator(PyQrackSimulatorBase):
137-
"""PyQrack simulator device with dynamic qubit allocation."""
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+
"""
138229

139230
def task(
140231
self,
141232
kernel: ir.Method[Params, RetType],
142233
args: tuple[Any, ...] = (),
143234
kwargs: dict[str, Any] | None = None,
144235
):
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+
"""
145250
if kwargs is None:
146251
kwargs = {}
147252

148253
memory = DynamicMemory(self.options.copy())
149254
return self.new_task(kernel, args, kwargs, memory)
150-
151-
152-
def test():
153-
from bloqade.qasm2 import extended
154-
155-
@extended
156-
def main():
157-
return 1
158-
159-
@extended
160-
def obs(result: int) -> int:
161-
return result
162-
163-
res = DynamicMemorySimulator().task(main)
164-
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)