pelagos-decoder 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.
- pelagos/__init__.py +34 -0
- pelagos/_process_worker.py +56 -0
- pelagos/adapter.py +858 -0
- pelagos/autotune.py +793 -0
- pelagos/decoder.py +3891 -0
- pelagos/graph.py +479 -0
- pelagos/kernels.py +2208 -0
- pelagos/process.py +287 -0
- pelagos/stim/__init__.py +11 -0
- pelagos/stim/sinter/__init__.py +11 -0
- pelagos/stim/sinter/check_matrices.py +112 -0
- pelagos/utils.py +995 -0
- pelagos_decoder-0.1.0.dist-info/METADATA +317 -0
- pelagos_decoder-0.1.0.dist-info/RECORD +16 -0
- pelagos_decoder-0.1.0.dist-info/WHEEL +4 -0
- pelagos_decoder-0.1.0.dist-info/licenses/LICENSE +201 -0
pelagos/process.py
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Copyright (c) 2026 Advanced Micro Devices, Inc.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Opt-in process-per-GPU orchestration; the single-GPU decoder is unchanged.
|
|
4
|
+
|
|
5
|
+
Results are owned CPU tensors. Each decoder instance reuses its workers until
|
|
6
|
+
close(). Device indices refer to the caller's visible device list.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import inspect
|
|
11
|
+
import os
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import socket
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
import shutil
|
|
17
|
+
import tempfile
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
21
|
+
from multiprocessing import shared_memory
|
|
22
|
+
from multiprocessing.connection import Connection
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def layout_copy(array):
|
|
28
|
+
"""Return a C-contiguous copy of a matrix.
|
|
29
|
+
|
|
30
|
+
A strided (e.g. transposed) uint8 matrix is copied in blocks of 32 columns, which
|
|
31
|
+
keeps memory access cache-friendly: for syndrome batches this is 3-8x faster than
|
|
32
|
+
``np.ascontiguousarray``. Other inputs fall back to ``np.ascontiguousarray``.
|
|
33
|
+
"""
|
|
34
|
+
array = np.asarray(array)
|
|
35
|
+
if array.ndim != 2 or array.dtype != np.uint8 or array.flags.c_contiguous:
|
|
36
|
+
return np.ascontiguousarray(array)
|
|
37
|
+
result = np.empty(array.shape, dtype=array.dtype)
|
|
38
|
+
for column in range(0, array.shape[1], 32):
|
|
39
|
+
result[:, column:column + 32] = array[:, column:column + 32]
|
|
40
|
+
return result
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def split_batch(batch, workers):
|
|
44
|
+
if batch < 0 or workers < 1:
|
|
45
|
+
raise ValueError("batch must be nonnegative and workers positive")
|
|
46
|
+
boundaries = []
|
|
47
|
+
start = 0
|
|
48
|
+
for rank in range(workers):
|
|
49
|
+
end = start + batch // workers + (rank < batch % workers)
|
|
50
|
+
boundaries.append((start, end))
|
|
51
|
+
start = end
|
|
52
|
+
return boundaries
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def prepare_batch(check_matrix, observables, errors, workers, multiply_mod2):
|
|
56
|
+
"""Prepare disjoint host shards concurrently, preserving original row order."""
|
|
57
|
+
def prepare(bounds):
|
|
58
|
+
start, end = bounds
|
|
59
|
+
shard = errors[start:end]
|
|
60
|
+
obs = multiply_mod2(observables, shard.T).T
|
|
61
|
+
syndromes = np.asarray((check_matrix @ shard.T) % 2, dtype=np.uint8).T
|
|
62
|
+
return obs, syndromes
|
|
63
|
+
|
|
64
|
+
splits = [(start, end) for start, end in split_batch(len(errors), workers) if start < end]
|
|
65
|
+
if not splits:
|
|
66
|
+
raise ValueError("empty error batch")
|
|
67
|
+
if len(splits) == 1:
|
|
68
|
+
obs, syndromes = prepare(splits[0])
|
|
69
|
+
else:
|
|
70
|
+
with ThreadPoolExecutor(max_workers=len(splits)) as executor:
|
|
71
|
+
parts = list(executor.map(prepare, splits))
|
|
72
|
+
obs = np.concatenate([p[0] for p in parts], axis=0)
|
|
73
|
+
syndromes = np.concatenate([p[1] for p in parts], axis=0)
|
|
74
|
+
return obs, layout_copy(syndromes)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def worker_environment(device, hip, environment=None):
|
|
78
|
+
env = dict(os.environ if environment is None else environment)
|
|
79
|
+
key = "ROCR_VISIBLE_DEVICES" if hip else "CUDA_VISIBLE_DEVICES"
|
|
80
|
+
# HIP visibility is relative to ROCR visibility. Resolve it before narrowing.
|
|
81
|
+
visible = env.get(key)
|
|
82
|
+
devices = visible.split(",") if visible is not None else None
|
|
83
|
+
if hip:
|
|
84
|
+
secondary = env.get("HIP_VISIBLE_DEVICES", env.get("CUDA_VISIBLE_DEVICES"))
|
|
85
|
+
if secondary is not None:
|
|
86
|
+
indices = [int(x) for x in secondary.split(",") if x]
|
|
87
|
+
if any(x < 0 for x in indices):
|
|
88
|
+
raise ValueError("No usable visible GPU")
|
|
89
|
+
devices = [devices[x] if devices is not None else str(x) for x in indices]
|
|
90
|
+
if device < 0 or (devices is not None and device >= len(devices)):
|
|
91
|
+
raise ValueError("device is outside the caller's visible GPU list")
|
|
92
|
+
env[key] = devices[device] if devices is not None else str(device)
|
|
93
|
+
if hip:
|
|
94
|
+
env["HIP_VISIBLE_DEVICES"] = "0"
|
|
95
|
+
env["CUDA_VISIBLE_DEVICES"] = "0"
|
|
96
|
+
return env
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def validate_payload(payload):
|
|
100
|
+
for key, array in payload.items():
|
|
101
|
+
if not np.issubdtype(array.dtype, np.inexact):
|
|
102
|
+
continue
|
|
103
|
+
bad = ~np.isfinite(array)
|
|
104
|
+
if not bad.any():
|
|
105
|
+
continue
|
|
106
|
+
valid = payload.get("valid_mask")
|
|
107
|
+
if (key != "weights" or valid is None or valid.shape != array.shape
|
|
108
|
+
or np.any(bad & valid) or not np.all(np.isposinf(array[bad]))):
|
|
109
|
+
raise ValueError(f"nonfinite decoder output: {key}")
|
|
110
|
+
if np.any(payload["iterations"] < 0):
|
|
111
|
+
raise ValueError("negative iteration count")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class ProcessRelayBPDecoder:
|
|
115
|
+
"""Persistent Linux GPU workers with static sharding and shared error output.
|
|
116
|
+
|
|
117
|
+
Constructor arguments match RelayBPDecoder, plus worker_timeout_s. Advanced
|
|
118
|
+
per-call decode overrides and layered scheduling are explicitly unsupported.
|
|
119
|
+
No global monkeypatches, shared pool cache, or autotuning policy changes.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, *args, worker_timeout_s=300, **kwargs):
|
|
123
|
+
import torch
|
|
124
|
+
from .decoder import RelayBPDecoder
|
|
125
|
+
|
|
126
|
+
if os.name != "posix":
|
|
127
|
+
raise RuntimeError("Process backend requires POSIX descriptor passing")
|
|
128
|
+
bound = inspect.signature(RelayBPDecoder).bind(*args, **kwargs)
|
|
129
|
+
bound.apply_defaults()
|
|
130
|
+
ctor = dict(bound.arguments)
|
|
131
|
+
devices = ctor.pop("device")
|
|
132
|
+
if isinstance(devices, int):
|
|
133
|
+
devices = list(range(devices))
|
|
134
|
+
elif isinstance(devices, str):
|
|
135
|
+
devices = [0] if devices in ("auto", "cuda") else [int(x) for x in devices.split(",")]
|
|
136
|
+
self.device_ids = list(devices)
|
|
137
|
+
if not self.device_ids or len(set(self.device_ids)) != len(self.device_ids):
|
|
138
|
+
raise ValueError("device list must be nonempty and unique")
|
|
139
|
+
if any(not isinstance(x, int) or x < 0 for x in self.device_ids):
|
|
140
|
+
raise ValueError("device indices must be nonnegative integers")
|
|
141
|
+
if ctor["scheduling"] != "flood-fill":
|
|
142
|
+
raise ValueError("process backend currently supports flood-fill only")
|
|
143
|
+
if not np.isfinite(worker_timeout_s) or worker_timeout_s <= 0:
|
|
144
|
+
raise ValueError("worker_timeout_s must be finite and positive")
|
|
145
|
+
self.num_gpus = len(self.device_ids)
|
|
146
|
+
self.pre_iter = ctor["pre_iter"]
|
|
147
|
+
self.num_sets = ctor["num_sets"]
|
|
148
|
+
self.fast_max_unconverged_pct = ctor["fast_max_unconverged_pct"]
|
|
149
|
+
self.bitpack_output = ctor["bitpack_output"]
|
|
150
|
+
self.check_permutation = None
|
|
151
|
+
self._shape = ctor["H_csr"].shape
|
|
152
|
+
self._timeout = worker_timeout_s
|
|
153
|
+
self._workers = []
|
|
154
|
+
self._shared = None
|
|
155
|
+
self._shared_array = None
|
|
156
|
+
self._lock = threading.Lock()
|
|
157
|
+
self._closed = False
|
|
158
|
+
self._cache = tempfile.TemporaryDirectory(prefix="pelagos-process-")
|
|
159
|
+
try:
|
|
160
|
+
for device in self.device_ids:
|
|
161
|
+
env = worker_environment(device, torch.version.hip is not None)
|
|
162
|
+
# Preserve the selected checkout when launched without installation.
|
|
163
|
+
root = str(Path(__file__).resolve().parents[1])
|
|
164
|
+
env["PYTHONPATH"] = os.pathsep.join([root, env.get("PYTHONPATH", "")])
|
|
165
|
+
cache = Path(self._cache.name) / str(device)
|
|
166
|
+
cache.mkdir()
|
|
167
|
+
env["TRITON_CACHE_DIR"] = str(cache / "triton")
|
|
168
|
+
tune = env.get("RELAY_TUNE_CACHE")
|
|
169
|
+
if tune and Path(tune).is_file():
|
|
170
|
+
shutil.copyfile(tune, cache / "tune.json")
|
|
171
|
+
env["RELAY_TUNE_CACHE"] = str(cache / "tune.json")
|
|
172
|
+
env["RELAY_TUNE_LOG_CSV"] = str(cache / "autotune.csv")
|
|
173
|
+
left, right = socket.socketpair()
|
|
174
|
+
try:
|
|
175
|
+
proc = subprocess.Popen(
|
|
176
|
+
[sys.executable, "-m", "pelagos._process_worker", str(right.fileno())],
|
|
177
|
+
pass_fds=(right.fileno(),), env=env,
|
|
178
|
+
)
|
|
179
|
+
conn = Connection(left.detach())
|
|
180
|
+
finally:
|
|
181
|
+
left.close()
|
|
182
|
+
right.close()
|
|
183
|
+
self._workers.append((proc, conn))
|
|
184
|
+
conn.send(ctor)
|
|
185
|
+
deadline = time.monotonic() + self._timeout
|
|
186
|
+
for _, conn in self._workers:
|
|
187
|
+
metadata = self._receive(conn, "ready", deadline)
|
|
188
|
+
self.wj = torch.from_numpy(metadata["wj"])
|
|
189
|
+
except BaseException:
|
|
190
|
+
self.close()
|
|
191
|
+
raise
|
|
192
|
+
|
|
193
|
+
def _receive(self, conn, expected, deadline):
|
|
194
|
+
if not conn.poll(max(0, deadline - time.monotonic())):
|
|
195
|
+
raise TimeoutError("process decoder worker timed out")
|
|
196
|
+
try:
|
|
197
|
+
status, payload = conn.recv()
|
|
198
|
+
except EOFError as exc:
|
|
199
|
+
raise RuntimeError("process decoder worker exited") from exc
|
|
200
|
+
if status != expected:
|
|
201
|
+
raise RuntimeError(f"process decoder worker: {status}: {payload}")
|
|
202
|
+
return payload
|
|
203
|
+
|
|
204
|
+
def decode(self, syndromes, **kwargs):
|
|
205
|
+
import torch
|
|
206
|
+
if kwargs:
|
|
207
|
+
raise ValueError("process backend does not support per-call decode overrides")
|
|
208
|
+
if not self._lock.acquire(blocking=False):
|
|
209
|
+
raise RuntimeError("concurrent decode calls are not supported")
|
|
210
|
+
try:
|
|
211
|
+
if self._closed:
|
|
212
|
+
raise RuntimeError("decoder is closed")
|
|
213
|
+
if isinstance(syndromes, torch.Tensor):
|
|
214
|
+
if syndromes.device.type != "cpu":
|
|
215
|
+
raise ValueError("process backend requires host syndromes")
|
|
216
|
+
syndromes = syndromes.detach().numpy()
|
|
217
|
+
array = np.asarray(syndromes)
|
|
218
|
+
if array.ndim != 2 or array.shape[1] != self._shape[0] or not array.shape[0]:
|
|
219
|
+
raise ValueError("expected nonempty [batch, checks] syndromes")
|
|
220
|
+
if not np.all((array == 0) | (array == 1)):
|
|
221
|
+
raise ValueError("syndromes must be binary")
|
|
222
|
+
array = layout_copy(array.astype(np.uint8, copy=False))
|
|
223
|
+
batch = len(array)
|
|
224
|
+
if not self.bitpack_output:
|
|
225
|
+
shape = (batch, self._shape[1])
|
|
226
|
+
if self._shared_array is None or self._shared_array.shape != shape:
|
|
227
|
+
self._release_shared()
|
|
228
|
+
self._shared = shared_memory.SharedMemory(create=True, size=int(np.prod(shape)))
|
|
229
|
+
self._shared_array = np.ndarray(shape, dtype=np.uint8, buffer=self._shared.buf)
|
|
230
|
+
parts = []
|
|
231
|
+
deadline = time.monotonic() + self._timeout
|
|
232
|
+
for (_, conn), (start, end) in zip(self._workers, split_batch(batch, self.num_gpus)):
|
|
233
|
+
if start == end:
|
|
234
|
+
continue
|
|
235
|
+
shared = None if self.bitpack_output else (self._shared.name, self._shared_array.shape, start, end)
|
|
236
|
+
conn.send((array[start:end].copy(), shared))
|
|
237
|
+
parts.append((start, conn))
|
|
238
|
+
results = [(start, self._receive(conn, "ok", deadline)) for start, conn in parts]
|
|
239
|
+
keys = set(results[0][1])
|
|
240
|
+
if any(set(result) != keys for _, result in results):
|
|
241
|
+
raise RuntimeError("inconsistent worker result keys")
|
|
242
|
+
output = {}
|
|
243
|
+
for key in keys:
|
|
244
|
+
chunks = [value[key] + start if key == "unconverged_indices" else value[key]
|
|
245
|
+
for start, value in results]
|
|
246
|
+
output[key] = torch.from_numpy(np.concatenate(chunks, axis=0))
|
|
247
|
+
if not self.bitpack_output:
|
|
248
|
+
output["errors"] = torch.from_numpy(self._shared_array.copy())
|
|
249
|
+
return output
|
|
250
|
+
except (RuntimeError, TimeoutError, EOFError, BrokenPipeError):
|
|
251
|
+
self.close()
|
|
252
|
+
raise
|
|
253
|
+
finally:
|
|
254
|
+
self._lock.release()
|
|
255
|
+
|
|
256
|
+
def _release_shared(self):
|
|
257
|
+
self._shared_array = None
|
|
258
|
+
if self._shared is not None:
|
|
259
|
+
self._shared.close()
|
|
260
|
+
self._shared.unlink()
|
|
261
|
+
self._shared = None
|
|
262
|
+
|
|
263
|
+
def close(self):
|
|
264
|
+
self._closed = True
|
|
265
|
+
for proc, conn in self._workers:
|
|
266
|
+
conn.close()
|
|
267
|
+
if proc.poll() is None:
|
|
268
|
+
proc.terminate()
|
|
269
|
+
try:
|
|
270
|
+
proc.wait(timeout=5)
|
|
271
|
+
except subprocess.TimeoutExpired:
|
|
272
|
+
proc.kill()
|
|
273
|
+
proc.wait()
|
|
274
|
+
self._workers.clear()
|
|
275
|
+
self._release_shared()
|
|
276
|
+
if hasattr(self, "_cache"):
|
|
277
|
+
self._cache.cleanup()
|
|
278
|
+
|
|
279
|
+
def __enter__(self):
|
|
280
|
+
return self
|
|
281
|
+
|
|
282
|
+
def __exit__(self, *exc):
|
|
283
|
+
self.close()
|
|
284
|
+
|
|
285
|
+
def __del__(self):
|
|
286
|
+
if hasattr(self, "_workers"):
|
|
287
|
+
self.close()
|
pelagos/stim/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# (C) Copyright IBM 2026
|
|
2
|
+
#
|
|
3
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
4
|
+
# obtain a copy of this license in the LICENSE file in the root directory
|
|
5
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
#
|
|
7
|
+
# Any modifications or derivative works of this code must retain this
|
|
8
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
9
|
+
# that they have been altered from the originals.
|
|
10
|
+
|
|
11
|
+
"""Namespace package."""
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# (C) Copyright IBM 2026
|
|
2
|
+
#
|
|
3
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
4
|
+
# obtain a copy of this license in the LICENSE file in the root directory
|
|
5
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
#
|
|
7
|
+
# Any modifications or derivative works of this code must retain this
|
|
8
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
9
|
+
# that they have been altered from the originals.
|
|
10
|
+
|
|
11
|
+
"""Namespace package."""
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# (C) Copyright IBM 2025, 2026
|
|
2
|
+
#
|
|
3
|
+
# This code is licensed under the Apache License, Version 2.0. You may
|
|
4
|
+
# obtain a copy of this license in the LICENSE file in the root directory
|
|
5
|
+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
|
|
6
|
+
#
|
|
7
|
+
# Any modifications or derivative works of this code must retain this
|
|
8
|
+
# copyright notice, and modified files need to carry a notice indicating
|
|
9
|
+
# that they have been altered from the originals.
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from scipy.sparse import csc_matrix
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
# We use belief matchings utilities to extract the decoding matrices
|
|
19
|
+
from beliefmatching import detector_error_model_to_check_matrices
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CheckMatrices:
|
|
24
|
+
check_matrix: csc_matrix
|
|
25
|
+
observables_matrix: csc_matrix
|
|
26
|
+
error_priors: np.ndarray
|
|
27
|
+
syndrome_bias: np.ndarray | None = None
|
|
28
|
+
observables_bias: np.ndarray | None = None
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dem(
|
|
32
|
+
cls,
|
|
33
|
+
dem,
|
|
34
|
+
decomposed_hyperedges: bool | None = None,
|
|
35
|
+
prune_decided_errors: bool = True,
|
|
36
|
+
threshold: float = 0.0,
|
|
37
|
+
) -> CheckMatrices:
|
|
38
|
+
"""Get decoding check matrices from dem.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
dem: Detector error model to extract matrices from.
|
|
42
|
+
decomposed_hyperedges: If errors have been decomposed we return only the remaining errors impacting observables.
|
|
43
|
+
If none, we try to detect if decomposable to reduce decoding time.
|
|
44
|
+
prune_decided_errors: Prune decided errors at construction time.
|
|
45
|
+
threshold: A threshold magnitude to clamp probabilities of 0+threshold -> 0 and 1-threshold -> 1 for error pruning.
|
|
46
|
+
"""
|
|
47
|
+
# We use belief matchings utilities to extract the decoding matrices
|
|
48
|
+
try:
|
|
49
|
+
dem_matrices = detector_error_model_to_check_matrices(
|
|
50
|
+
dem,
|
|
51
|
+
allow_undecomposed_hyperedges=decomposed_hyperedges is not None
|
|
52
|
+
and not decomposed_hyperedges,
|
|
53
|
+
)
|
|
54
|
+
decomposed_hyperedges = True
|
|
55
|
+
except ValueError as err:
|
|
56
|
+
# the dem was not CSS, try again allowing for undecomposed errors
|
|
57
|
+
if decomposed_hyperedges is None:
|
|
58
|
+
decomposed_hyperedges = False
|
|
59
|
+
dem_matrices = detector_error_model_to_check_matrices(
|
|
60
|
+
dem, allow_undecomposed_hyperedges=True
|
|
61
|
+
)
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError("The input dem is not decomposable") from err
|
|
64
|
+
|
|
65
|
+
if decomposed_hyperedges:
|
|
66
|
+
observables_matrix = dem_matrices.edge_observables_matrix
|
|
67
|
+
check_matrix = dem_matrices.edge_check_matrix
|
|
68
|
+
error_priors = dem_matrices.hyperedge_to_edge_matrix @ dem_matrices.priors
|
|
69
|
+
else:
|
|
70
|
+
observables_matrix = dem_matrices.observables_matrix
|
|
71
|
+
check_matrix = dem_matrices.check_matrix
|
|
72
|
+
error_priors = dem_matrices.priors
|
|
73
|
+
|
|
74
|
+
check_matrices = cls(
|
|
75
|
+
check_matrix=check_matrix,
|
|
76
|
+
observables_matrix=observables_matrix,
|
|
77
|
+
error_priors=error_priors.astype(np.float64),
|
|
78
|
+
)
|
|
79
|
+
if prune_decided_errors:
|
|
80
|
+
check_matrices = check_matrices.prune_decided_errors(threshold=threshold)
|
|
81
|
+
|
|
82
|
+
return check_matrices
|
|
83
|
+
|
|
84
|
+
def prune_decided_errors(self, threshold: float = 0.0) -> CheckMatrices:
|
|
85
|
+
"""Prune decided errors from the check matrices.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
threshold: A threshold magnitude to clamp probabilities of 0+threshold -> 0 and 1-threshold -> 1 for error pruning.
|
|
89
|
+
"""
|
|
90
|
+
to_zero = np.argwhere(self.error_priors <= 0.0 + threshold).reshape(-1)
|
|
91
|
+
to_one = np.argwhere(self.error_priors >= 1.0 - threshold).reshape(-1)
|
|
92
|
+
|
|
93
|
+
if len(to_one):
|
|
94
|
+
syndrome_bias = np.sum(self.check_matrix[:, to_one], axis=1).reshape(-1) % 2
|
|
95
|
+
observables_bias = np.sum(self.check_matrix[:, to_one], axis=1).reshape(-1) % 2
|
|
96
|
+
else:
|
|
97
|
+
syndrome_bias = None
|
|
98
|
+
observables_bias = None
|
|
99
|
+
|
|
100
|
+
prune = np.concatenate([to_zero, to_one])
|
|
101
|
+
keep = np.where(np.logical_not(np.isin(np.arange(len(self.error_priors)), prune)))[0]
|
|
102
|
+
check_matrix = self.check_matrix[:, keep]
|
|
103
|
+
observables_matrix = self.observables_matrix[:, keep]
|
|
104
|
+
error_priors = self.error_priors[keep]
|
|
105
|
+
|
|
106
|
+
return CheckMatrices(
|
|
107
|
+
check_matrix=check_matrix,
|
|
108
|
+
observables_matrix=observables_matrix,
|
|
109
|
+
error_priors=error_priors,
|
|
110
|
+
syndrome_bias=syndrome_bias,
|
|
111
|
+
observables_bias=observables_bias,
|
|
112
|
+
)
|