tridec 0.1.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.
- tridec/__init__.py +47 -0
- tridec/adapters/__init__.py +258 -0
- tridec/api.py +417 -0
- tridec/backends/__init__.py +11 -0
- tridec/backends/bp_numpy.py +221 -0
- tridec/backends/bp_torch.py +358 -0
- tridec/backends/bp_triton.py +480 -0
- tridec/backends/relay_triton.py +549 -0
- tridec/dem.py +49 -0
- tridec/sinter.py +115 -0
- tridec/tanner.py +48 -0
- tridec/validation/__init__.py +40 -0
- tridec/validation/analysis.py +239 -0
- tridec/validation/harness.py +231 -0
- tridec/validation/stats.py +63 -0
- tridec-0.1.0.dist-info/METADATA +204 -0
- tridec-0.1.0.dist-info/RECORD +20 -0
- tridec-0.1.0.dist-info/WHEEL +5 -0
- tridec-0.1.0.dist-info/licenses/LICENSE +202 -0
- tridec-0.1.0.dist-info/top_level.txt +1 -0
tridec/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""tridec: vendor-portable GPU decoders for quantum LDPC codes.
|
|
2
|
+
|
|
3
|
+
Triton min-sum BP and Relay-BP decoders that consume any stim
|
|
4
|
+
DetectorErrorModel or raw parity-check matrices, with CPU reference
|
|
5
|
+
implementations, validated against the standard CPU references (ldpc,
|
|
6
|
+
relay-bp), running on NVIDIA (CUDA) and AMD (ROCm) GPUs.
|
|
7
|
+
|
|
8
|
+
Quickstart::
|
|
9
|
+
|
|
10
|
+
import stim, tridec
|
|
11
|
+
|
|
12
|
+
circuit = stim.Circuit.from_file("memory.stim")
|
|
13
|
+
dem = circuit.detector_error_model(decompose_errors=False)
|
|
14
|
+
decoder = tridec.from_dem(dem, backend="auto")
|
|
15
|
+
|
|
16
|
+
dets, obs = circuit.compile_detector_sampler(seed=0).sample(
|
|
17
|
+
10_000, separate_observables=True)
|
|
18
|
+
pred = decoder.decode_batch(dets) # (shots, n_obs) bool
|
|
19
|
+
ler = (pred != obs).any(axis=1).mean()
|
|
20
|
+
"""
|
|
21
|
+
from .api import (
|
|
22
|
+
BpDecoder,
|
|
23
|
+
RelayBpDecoder,
|
|
24
|
+
available_backends,
|
|
25
|
+
from_dem,
|
|
26
|
+
from_matrices,
|
|
27
|
+
resolve_backend,
|
|
28
|
+
)
|
|
29
|
+
from .dem import extract
|
|
30
|
+
|
|
31
|
+
# Single-sourced from the installed distribution metadata (pyproject.toml).
|
|
32
|
+
try:
|
|
33
|
+
from importlib.metadata import version as _dist_version
|
|
34
|
+
__version__ = _dist_version("tridec")
|
|
35
|
+
except Exception: # pragma: no cover - uninstalled source tree
|
|
36
|
+
__version__ = "0.1.0"
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"BpDecoder",
|
|
40
|
+
"RelayBpDecoder",
|
|
41
|
+
"available_backends",
|
|
42
|
+
"extract",
|
|
43
|
+
"from_dem",
|
|
44
|
+
"from_matrices",
|
|
45
|
+
"resolve_backend",
|
|
46
|
+
"__version__",
|
|
47
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Optional CPU reference-decoder adapters on a SHARED DEM (import-guarded).
|
|
2
|
+
|
|
3
|
+
These wrap the standard CPU reference implementations — the `ldpc` package's
|
|
4
|
+
BP / BP-OSD / BP-LSD and IBM's `relay-bp` Rust decoder — behind the same
|
|
5
|
+
``decode_batch(dets) -> predicted_observables`` surface as the native
|
|
6
|
+
backends, so a matched harness (``tridec.validation.run_matched``) can
|
|
7
|
+
decode the SAME shots with every decoder (apples-to-apples LER). They are the
|
|
8
|
+
validation targets the GPU kernels are held against.
|
|
9
|
+
|
|
10
|
+
Install with the ``decoders`` extra: ``pip install tridec[decoders]``.
|
|
11
|
+
The module imports without either package; each factory raises (or the
|
|
12
|
+
``*_available()`` probes return False) when its dependency is missing.
|
|
13
|
+
|
|
14
|
+
Interface (every adapter):
|
|
15
|
+
* ``.name`` -- str identifier (e.g. ``"BPOSD-10"``),
|
|
16
|
+
* ``.config`` -- dict of pinned hyperparameters (provenance),
|
|
17
|
+
* ``.dem`` -- the shared ``stim.DetectorErrorModel`` it was built from,
|
|
18
|
+
* ``.tie_break`` -- declared deterministic tie-break (gate G2),
|
|
19
|
+
* ``.decode_batch(dets: bool[shots, n_det]) -> bool[shots, n_obs]``.
|
|
20
|
+
|
|
21
|
+
For an ldpc decoder, each shot's detector syndrome is decoded to an error
|
|
22
|
+
estimate ``e_hat`` (length n_err); predicted observables = ``(Lo @ e_hat) % 2``.
|
|
23
|
+
ldpc 2.4.x exposes only single-shot ``decoder.decode(syndrome)`` (no batched
|
|
24
|
+
entry point), so ldpc adapters loop over shots.
|
|
25
|
+
"""
|
|
26
|
+
import numpy as np
|
|
27
|
+
|
|
28
|
+
from ..dem import extract
|
|
29
|
+
|
|
30
|
+
# Pinned min-sum BP hyperparameters shared across the BP-family adapters
|
|
31
|
+
# (the provenance constants the validation grid committed to).
|
|
32
|
+
_BP_MAX_ITER = 30
|
|
33
|
+
_BP_MS_SCALING = 0.625 # standard normalized-min-sum scaling factor
|
|
34
|
+
_BP_METHOD = "minimum_sum" # min-sum BP (the kernel target)
|
|
35
|
+
_BP_SCHEDULE = "parallel"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def ldpc_available():
|
|
39
|
+
"""True iff the `ldpc` package is importable."""
|
|
40
|
+
try:
|
|
41
|
+
import ldpc # noqa: F401
|
|
42
|
+
except Exception:
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def relay_bp_available():
|
|
48
|
+
"""True iff relay-bp[stim] is importable (import-guarded membership)."""
|
|
49
|
+
try:
|
|
50
|
+
import relay_bp # noqa: F401
|
|
51
|
+
from relay_bp.stim import CheckMatrices # noqa: F401
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _LdpcAdapter:
|
|
58
|
+
"""Base for ldpc-family adapters: build H/Lo/priors from the shared DEM,
|
|
59
|
+
decode each shot's syndrome to an error estimate, map to observables."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, dem, name, config, decoder, tie_break):
|
|
62
|
+
self.dem = dem
|
|
63
|
+
self.name = name
|
|
64
|
+
self.config = dict(config)
|
|
65
|
+
# Declared deterministic tie-break (gate G2). No silent default: the
|
|
66
|
+
# matched harness asserts this is in APPROVED_TIE_BREAKS.
|
|
67
|
+
self.tie_break = tie_break
|
|
68
|
+
self._decoder = decoder
|
|
69
|
+
ex = extract(dem)
|
|
70
|
+
# Lo: (n_obs x n_err) GF2 map from error mechanisms to observables.
|
|
71
|
+
self._Lo = ex["Lo"].toarray().astype(np.uint8)
|
|
72
|
+
self._n_obs = ex["n_obs"]
|
|
73
|
+
self._n_err = ex["n_err"]
|
|
74
|
+
self._n_det = ex["n_det"]
|
|
75
|
+
|
|
76
|
+
def decode_batch(self, dets):
|
|
77
|
+
dets = np.asarray(dets, dtype=bool)
|
|
78
|
+
shots = dets.shape[0]
|
|
79
|
+
out = np.zeros((shots, self._n_obs), dtype=bool)
|
|
80
|
+
syn_u8 = dets.astype(np.uint8)
|
|
81
|
+
Lo = self._Lo
|
|
82
|
+
for i in range(shots):
|
|
83
|
+
e_hat = self._decoder.decode(syn_u8[i])
|
|
84
|
+
# predicted observables = (Lo @ e_hat) % 2
|
|
85
|
+
pred = (Lo @ np.asarray(e_hat, dtype=np.uint8)) & 1
|
|
86
|
+
out[i] = pred.astype(bool)
|
|
87
|
+
return out
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _priors(dem):
|
|
91
|
+
"""Per-mechanism priors from the shared DEM, clipped for ldpc stability."""
|
|
92
|
+
pri = extract(dem)["priors"]
|
|
93
|
+
return list(np.clip(pri, 1e-6, 1 - 1e-6))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def make_bp(dem):
|
|
97
|
+
"""Pure min-sum BP (no post-processing): ldpc.BpDecoder reference."""
|
|
98
|
+
from ldpc import BpDecoder
|
|
99
|
+
|
|
100
|
+
H = extract(dem)["H"]
|
|
101
|
+
cfg = dict(decoder="BpDecoder", bp_method=_BP_METHOD,
|
|
102
|
+
ms_scaling_factor=_BP_MS_SCALING, max_iter=_BP_MAX_ITER,
|
|
103
|
+
schedule=_BP_SCHEDULE)
|
|
104
|
+
dec = BpDecoder(H, error_channel=_priors(dem), max_iter=_BP_MAX_ITER,
|
|
105
|
+
bp_method=_BP_METHOD, ms_scaling_factor=_BP_MS_SCALING,
|
|
106
|
+
schedule=_BP_SCHEDULE)
|
|
107
|
+
return _LdpcAdapter(dem, "BP", cfg, dec, "min_sum_parallel_hard_decision")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def make_bposd0(dem):
|
|
111
|
+
"""BP-OSD order-0 (osd_0): cheapest OSD post-processing."""
|
|
112
|
+
from ldpc import BpOsdDecoder
|
|
113
|
+
|
|
114
|
+
H = extract(dem)["H"]
|
|
115
|
+
cfg = dict(decoder="BpOsdDecoder", bp_method=_BP_METHOD,
|
|
116
|
+
ms_scaling_factor=_BP_MS_SCALING, max_iter=_BP_MAX_ITER,
|
|
117
|
+
schedule=_BP_SCHEDULE, osd_method="osd_0", osd_order=0)
|
|
118
|
+
dec = BpOsdDecoder(H, error_channel=_priors(dem), max_iter=_BP_MAX_ITER,
|
|
119
|
+
bp_method=_BP_METHOD, ms_scaling_factor=_BP_MS_SCALING,
|
|
120
|
+
schedule=_BP_SCHEDULE, osd_method="osd_0", osd_order=0)
|
|
121
|
+
return _LdpcAdapter(dem, "BPOSD-0", cfg, dec, "osd0_reliability_order")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def make_bposd10(dem):
|
|
125
|
+
"""BP-OSD order-10 combination-sweep (osd_cs): the strong classical bar."""
|
|
126
|
+
from ldpc import BpOsdDecoder
|
|
127
|
+
|
|
128
|
+
H = extract(dem)["H"]
|
|
129
|
+
cfg = dict(decoder="BpOsdDecoder", bp_method=_BP_METHOD,
|
|
130
|
+
ms_scaling_factor=_BP_MS_SCALING, max_iter=_BP_MAX_ITER,
|
|
131
|
+
schedule=_BP_SCHEDULE, osd_method="osd_cs", osd_order=10)
|
|
132
|
+
dec = BpOsdDecoder(H, error_channel=_priors(dem), max_iter=_BP_MAX_ITER,
|
|
133
|
+
bp_method=_BP_METHOD, ms_scaling_factor=_BP_MS_SCALING,
|
|
134
|
+
schedule=_BP_SCHEDULE, osd_method="osd_cs", osd_order=10)
|
|
135
|
+
return _LdpcAdapter(dem, "BPOSD-10", cfg, dec, "osd_cs_order10")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def make_bplsd(dem):
|
|
139
|
+
"""BP + Localised-Statistics Decoder (lsd_cs, order 10)."""
|
|
140
|
+
from ldpc import BpLsdDecoder
|
|
141
|
+
|
|
142
|
+
H = extract(dem)["H"]
|
|
143
|
+
lsd_order = 10
|
|
144
|
+
cfg = dict(decoder="BpLsdDecoder", bp_method=_BP_METHOD,
|
|
145
|
+
ms_scaling_factor=_BP_MS_SCALING, max_iter=_BP_MAX_ITER,
|
|
146
|
+
schedule=_BP_SCHEDULE, lsd_method="lsd_cs", lsd_order=lsd_order)
|
|
147
|
+
dec = BpLsdDecoder(H, error_channel=_priors(dem), max_iter=_BP_MAX_ITER,
|
|
148
|
+
bp_method=_BP_METHOD, ms_scaling_factor=_BP_MS_SCALING,
|
|
149
|
+
schedule=_BP_SCHEDULE, lsd_method="lsd_cs",
|
|
150
|
+
lsd_order=lsd_order)
|
|
151
|
+
return _LdpcAdapter(dem, "BPLSD", cfg, dec, "lsd_cs_order10")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# --------------------------------------------------------------------------- #
|
|
155
|
+
# Relay-BP (relay-bp[stim] >= 0.2.2) — IBM's Rust reference decoder. #
|
|
156
|
+
# --------------------------------------------------------------------------- #
|
|
157
|
+
# Construct-from-DEM:
|
|
158
|
+
# from relay_bp.stim import CheckMatrices
|
|
159
|
+
# cm = CheckMatrices.from_dem(dem) # -> .check_matrix (ndet x E csc),
|
|
160
|
+
# # .observables_matrix (nobs x E csc),
|
|
161
|
+
# # .error_priors (E,)
|
|
162
|
+
# dec = relay_bp.RelayDecoderF64(cm.check_matrix, error_priors=cm.error_priors,
|
|
163
|
+
# gamma0=, pre_iter=, num_sets=, set_max_iter=, gamma_dist_interval=,
|
|
164
|
+
# stop_nconv=, stopping_criterion='nconv') # disjoint-relay ensemble
|
|
165
|
+
# runner = relay_bp.ObservableDecoderRunner(dec, cm.observables_matrix,
|
|
166
|
+
# include_decode_result=False)
|
|
167
|
+
# Decode:
|
|
168
|
+
# runner.decode_observables_batch(syndromes uint8 [shots, n_det])
|
|
169
|
+
# -> predicted observables uint8 [shots, n_obs]
|
|
170
|
+
# This is the path relay_bp.stim.SinterDecoder_RelayBP uses internally, minus
|
|
171
|
+
# sinter's bit-packing — the runner is driven directly for a clean decode_batch.
|
|
172
|
+
_RELAY_BP_DEFAULTS = dict(
|
|
173
|
+
gamma0=0.1,
|
|
174
|
+
pre_iter=80,
|
|
175
|
+
num_sets=60,
|
|
176
|
+
set_max_iter=60,
|
|
177
|
+
gamma_dist_interval=(-0.24, 0.66),
|
|
178
|
+
stop_nconv=5,
|
|
179
|
+
stopping_criterion="nconv",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class RelayBPAdapter:
|
|
184
|
+
"""Relay-BP adapter (in-process). Builds the relay-BP decoder from the SAME
|
|
185
|
+
shared DEM via ``relay_bp.stim.CheckMatrices.from_dem`` and decodes a batch
|
|
186
|
+
of syndromes straight to observables. G1 holds trivially: ``.dem is dem``."""
|
|
187
|
+
|
|
188
|
+
def __init__(self, dem, **params):
|
|
189
|
+
import importlib.metadata as _md
|
|
190
|
+
|
|
191
|
+
import relay_bp
|
|
192
|
+
from relay_bp.stim import CheckMatrices
|
|
193
|
+
|
|
194
|
+
self.dem = dem
|
|
195
|
+
self.name = "RelayBP"
|
|
196
|
+
try:
|
|
197
|
+
ver = _md.version("relay-bp")
|
|
198
|
+
except Exception: # pragma: no cover - metadata present once installed
|
|
199
|
+
ver = "unknown"
|
|
200
|
+
cfg = dict(_RELAY_BP_DEFAULTS)
|
|
201
|
+
cfg.update(params)
|
|
202
|
+
self.config = dict(decoder="RelayBP", relay_bp_version=ver, **cfg)
|
|
203
|
+
# Deterministic relay schedule (fixed gamma distribution + nconv stop).
|
|
204
|
+
self.tie_break = "relay_bp_nconv_disjoint_ensemble"
|
|
205
|
+
|
|
206
|
+
cm = CheckMatrices.from_dem(dem)
|
|
207
|
+
self._n_obs = cm.observables_matrix.shape[0]
|
|
208
|
+
decoder = relay_bp.RelayDecoderF64(
|
|
209
|
+
cm.check_matrix,
|
|
210
|
+
error_priors=cm.error_priors,
|
|
211
|
+
**cfg,
|
|
212
|
+
)
|
|
213
|
+
self._runner = relay_bp.ObservableDecoderRunner(
|
|
214
|
+
decoder, cm.observables_matrix, include_decode_result=False)
|
|
215
|
+
|
|
216
|
+
def decode_batch(self, dets):
|
|
217
|
+
dets = np.asarray(dets, dtype=bool)
|
|
218
|
+
pred = np.asarray(
|
|
219
|
+
self._runner.decode_observables_batch(dets.astype(np.uint8)))
|
|
220
|
+
pred = (pred % 2).astype(bool)
|
|
221
|
+
if pred.ndim == 1:
|
|
222
|
+
pred = pred.reshape(-1, 1)
|
|
223
|
+
return pred
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def make_relay_bp(dem, **params):
|
|
227
|
+
return RelayBPAdapter(dem, **params)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
# Registry: name -> factory(dem).
|
|
231
|
+
_FACTORIES = {
|
|
232
|
+
"BPOSD-0": make_bposd0,
|
|
233
|
+
"BPOSD-10": make_bposd10,
|
|
234
|
+
"BPLSD": make_bplsd,
|
|
235
|
+
"BP": make_bp,
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
DEFAULT_DECODERS = ("BPOSD-0", "BPOSD-10", "BPLSD", "BP")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def build_decoders(dem, which=DEFAULT_DECODERS, include_relay=False):
|
|
242
|
+
"""Construct all requested adapters from ONE shared DEM object.
|
|
243
|
+
|
|
244
|
+
Every returned adapter has ``.dem is dem`` (provenance for the matched
|
|
245
|
+
harness). ``which`` selects/orders the ldpc-family adapters by registry
|
|
246
|
+
name. Relay-BP is OPT-IN via ``include_relay=True`` and is added ONLY when
|
|
247
|
+
its package is available (import-guarded), so the core set always builds.
|
|
248
|
+
"""
|
|
249
|
+
decoders = []
|
|
250
|
+
for name in which:
|
|
251
|
+
if name not in _FACTORIES:
|
|
252
|
+
raise KeyError(f"unknown decoder {name!r}; known: {sorted(_FACTORIES)}")
|
|
253
|
+
decoders.append(_FACTORIES[name](dem))
|
|
254
|
+
|
|
255
|
+
if include_relay and relay_bp_available():
|
|
256
|
+
decoders.append(make_relay_bp(dem))
|
|
257
|
+
|
|
258
|
+
return decoders
|