1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""Python sampler wrapper around the public ``qdk`` PyPI package.
Exposes the deq Python sampler protocol:
class Sampler:
def __init__(self, circuit_text: str, config: dict): ...
def sample(self) -> str:
'''Return one shot as a length-N string of '0', '1', or '-' chars.'''
The Stim circuit text is compiled to QIR + noise inside ``qdk.stim.compile``
and executed with the loss-aware Clifford simulator
(``qdk.stim.run(..., type="clifford")``). ``qdk.stim.run`` returns
``List[List[Result]]`` where ``Result`` is a Rust-bound enum with members
``Zero``, ``One``, ``Loss``; this adapter converts each shot to a
length-N string of ``'0'``, ``'1'``, ``'-'`` characters before returning
it. The deq Rust sampler then replaces each ``'-'`` with a uniformly random bit
drawn from its deterministic RNG before feeding the shot to the decoder.
The ``config`` dictionary may contain:
* ``seed`` (auto-injected by the Rust sampler): base seed for shot batches.
Each refill uses ``seed + batch_index`` so successive batches draw fresh
shots while remaining reproducible.
* ``skip_shots`` (auto-injected): number of shots to discard at the start.
* ``num_measurements`` (auto-injected): expected shot length (sanity check).
* ``batch_size`` (default: 256): how many shots to draw per ``qdk.stim.run``
call. Larger values amortize Python call overhead at the cost of memory.
* ``type`` (default: ``"clifford"``): forwarded to ``qdk.stim.run``.
Use ``"cpu"`` for non-Clifford circuits.
Invocation options
------------------
**Recommended: the ``@qdk_sampler`` builtin sentinel.** This module is
compiled into the ``deq_runtime`` binary via a small ``builtin_samplers``
registry inside ``python_sampler.rs``, and the ``PythonSampler`` config
field ``sampler`` resolves any value beginning with ``@`` from that
registry instead of the filesystem. So the canonical invocation is::
python -m deq.runtime server \\
--simulator python \\
--simulator-config '{"filepath": "circuit.stim", "sampler": "@qdk_sampler", "py_config": {"batch_size": 1024}}'
**Loading a local copy from disk.** Any ``sampler`` value that does not
start with ``@`` is opened as a filesystem path — useful when hacking
on a customized version of this adapter::
python -m deq.runtime server \\
--simulator python \\
--simulator-config '{"filepath": "circuit.stim", "sampler": "src/simulator/qdk_sampler.py", "py_config": {"batch_size": 1024}}'
**Through a standalone Rust binary (development / tests).** When invoked
via ``cargo run`` or ``cargo test``, the binary is *not* an extension
module: pyo3 must link ``libpython`` itself, and the dynamic loader needs
to find it. Point ``LD_LIBRARY_PATH`` at the conda env's libdir::
LD_LIBRARY_PATH="$(python -c 'import sysconfig; print(sysconfig.get_config_var(\"LIBDIR\"))'):$LD_LIBRARY_PATH" \\
cargo run --bin deq-runtime-cli --features simulator,python -- \\
server \\
--simulator python \\
--simulator-config '{"filepath": "circuit.stim", "sampler": "@qdk_sampler", "py_config": {"batch_size": 1024}}'
"""
# Mapping from qdk Result enum to the single-char alphabet the deq Rust
# sampler expects. `Result` is a PyO3-bound class so hashing/equality
# is fast (just an internal discriminant compare).
: =
return
=
=
=
=
=
=
=
# Compile once so every refill reuses the same QIR + NoiseConfig.
, =
=
=
= 0
: =
# Discard the first `skip_shots` results so the deq simulator's
# skip_shots semantics match what the user would see with the
# built-in stim sampler. `_refill()` leaves `self._buffer` in
# reverse order (so `.pop()` yields shots in their natural order),
# which means "drop the first N shots" == "pop N times from the
# end of the reversed buffer".
=
=
-=
=
+= 1
# Use run_qir (not qdk.stim.run) because we already compiled the Stim
# text to QIR in __init__; qdk.stim.run would recompile every batch.
# The NoiseConfig produced by qdk.stim.compile is passed through verbatim.
# Returned value is List[List[Result]] -- one inner list per shot,
# each holding `num_measurements` Result enum values.
=
=
# Reverse so .pop() yields shots in the order qdk returned them.
=
=
return