Skip to content

Commit e5c41f0

Browse files
committed
fix: resolve all 17 mypy type errors -- bulletproof PyPI release
- bposd.py: annotate n_checks/n_qubits as int; cast H.shape unpacking - sinter_compat.py: type dynamic bases as ype; add type:ignore[misc,valid-type] - predecoder.py: replace lambda dict with explicit if/elif for Union narrowing; add TYPE_CHECKING import for BlossomDecoder/UnionFindDecoder/SparseBlossomDecoder - dem.py: guard None in state dict before arithmetic (max_det / max_obs) - __init__.py: add device_name -> str property to OpenCLBatchDecoder - backend.py: annotate timings dict; annotate gpu_dec as Optional Union; cast n_qubits to int() on n_qubits property - rest_api.py: add Union import; split dec into dec_any + uf_dec so mypy narrows .decode() call correctly in both FastAPI and Flask branches - qiskit_plugin.py: cast get_counts() through dict comprehension -> str/int - pyproject.toml: bump mypy python_version 3.9->3.10 (mypy 2.x dropped 3.9); remove stale numpy.* overrides section (no numpy in mypy env) Result: mypy --ignore-missing-imports: 0 errors in 16 files ruff check: all checks passed ruff format: 139 files unchanged
1 parent 788a81b commit e5c41f0

9 files changed

Lines changed: 56 additions & 43 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,11 @@ filterwarnings = [
111111
]
112112

113113
[tool.mypy]
114-
python_version = "3.9"
114+
python_version = "3.10"
115115
warn_return_any = true
116116
warn_unused_configs = true
117117
ignore_missing_imports = true
118118

119-
# numpy 2.x stubs use `type` statement (Python 3.12+ syntax); ignore stub
120-
# errors from third-party packages so mypy only checks our own code.
121-
[[tool.mypy.overrides]]
122-
module = ["numpy", "numpy.*"]
123-
ignore_errors = true
124-
125119
[tool.ruff]
126120
line-length = 120
127121
target-version = "py39"

python/qector_decoder_v3/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,10 @@ def n_qubits(self):
392392
def n_checks(self):
393393
return self._inner.n_checks
394394

395+
@property
396+
def device_name(self) -> str:
397+
return str(self._inner.device_name)
398+
395399
@property
396400
def consecutive_failures(self):
397401
"""Number of consecutive GPU failures since last success."""
@@ -455,8 +459,8 @@ def n_checks(self):
455459
return self._inner.n_checks
456460

457461
@property
458-
def device_name(self):
459-
return self._inner.device_name
462+
def device_name(self) -> str:
463+
return str(self._inner.device_name)
460464

461465
@property
462466
def compute_capability(self):

python/qector_decoder_v3/backend.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,9 @@ def calibrate(self, sizes=(64, 256, 1024, 4096, 16384), repeats: int = 3, seed:
239239
"""
240240
rng = np.random.default_rng(seed)
241241
n_checks = len(self._c2q)
242-
timings = {"cpu": {}, "gpu": {}}
242+
timings: dict[str, dict[int, float]] = {"cpu": {}, "gpu": {}}
243243
gpu_name = None
244-
gpu_dec = None
244+
gpu_dec: Optional[CUDABatchDecoder | OpenCLBatchDecoder] = None
245245
if self._cuda_ok:
246246
gpu_dec, gpu_name = self._get_cuda(), Backend.CUDA
247247
elif self._opencl_ok:
@@ -308,7 +308,7 @@ def last_backend(self) -> str:
308308

309309
@property
310310
def n_qubits(self) -> int:
311-
return self._get_cpu_single().n_qubits
311+
return int(self._get_cpu_single().n_qubits)
312312

313313
@property
314314
def n_checks(self) -> int:

python/qector_decoder_v3/bposd.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ def __init__(
4949
self.H = _to_dense_binary(H)
5050
if self.H.ndim != 2:
5151
raise ValueError(f"H must be 2D, got {self.H.shape}")
52-
self.n_checks, self.n_qubits = self.H.shape
52+
self.n_checks: int
53+
self.n_qubits: int
54+
self.n_checks, self.n_qubits = (int(x) for x in self.H.shape)
5355
self.ic, self.ie = build_incidence(self.H)
5456
if priors is None:
5557
p = np.full(self.n_qubits, float(error_rate), dtype=np.float64)

python/qector_decoder_v3/dem.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ def parse_dem(text: str) -> DemModel:
265265
coords: dict = {}
266266
state = {"det_offset": 0, "coord_offset": None, "max_det": -1, "max_obs": -1}
267267
_exec_block(tokens, 0, len(tokens), state, errors, coords)
268-
num_detectors = state["max_det"] + 1
269-
num_observables = state["max_obs"] + 1
268+
num_detectors = (state["max_det"] if state["max_det"] is not None else -1) + 1
269+
num_observables = (state["max_obs"] if state["max_obs"] is not None else -1) + 1
270270
return DemModel(
271271
errors=errors,
272272
num_detectors=max(num_detectors, 0),

python/qector_decoder_v3/predecoder.py

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,13 @@
2323

2424
from __future__ import annotations
2525

26-
from typing import List
26+
from typing import TYPE_CHECKING, List, Union
2727

2828
import numpy as np
2929

30+
if TYPE_CHECKING:
31+
from . import BlossomDecoder, SparseBlossomDecoder, UnionFindDecoder
32+
3033
__all__ = ["PredecodedDecoder", "quantize_weights"]
3134

3235

@@ -73,15 +76,18 @@ def __init__(self, check_to_qubits, n_qubits=None, backend: str = "blossom"):
7376
key = (min(chk), max(chk))
7477
self._pair_edge.setdefault(key, q)
7578

76-
builders = {
77-
"blossom": lambda: BlossomDecoder(self._c2q, self.n_qubits),
78-
"union_find": lambda: UnionFindDecoder(self._c2q, self.n_qubits),
79-
"sparse_blossom": lambda: SparseBlossomDecoder(self._c2q, self.n_qubits),
80-
}
81-
if backend not in builders:
82-
raise ValueError(f"backend must be one of {list(builders)}")
79+
_valid_backends = ("blossom", "union_find", "sparse_blossom")
80+
if backend not in _valid_backends:
81+
raise ValueError(f"backend must be one of {list(_valid_backends)}")
8382
self.backend = backend
84-
self._residual = builders[backend]()
83+
residual: Union[BlossomDecoder, UnionFindDecoder, SparseBlossomDecoder]
84+
if backend == "blossom":
85+
residual = BlossomDecoder(self._c2q, self.n_qubits)
86+
elif backend == "union_find":
87+
residual = UnionFindDecoder(self._c2q, self.n_qubits)
88+
else:
89+
residual = SparseBlossomDecoder(self._c2q, self.n_qubits)
90+
self._residual: Union[BlossomDecoder, UnionFindDecoder, SparseBlossomDecoder] = residual
8591
self.last_predecoded = 0 # number of defects resolved by the predecoder
8692

8793
def _predecode(self, syndrome: np.ndarray):

python/qector_decoder_v3/qiskit_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _normalize_counts(result: Any) -> Dict[str, int]:
5050

5151
if _HAS_QISKIT and isinstance(result, _QiskitResult):
5252
# get_counts() retourne un dict {bitstring: count}
53-
return result.get_counts()
53+
return {str(k): int(v) for k, v in result.get_counts().items()}
5454

5555
raise TypeError(f"result must be a dict or qiskit.result.Result, got {type(result).__name__}")
5656

python/qector_decoder_v3/rest_api.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
from __future__ import annotations
2525

26-
from typing import Any, Dict, List, Optional
26+
from typing import Any, Dict, List, Optional, Union
2727

2828
import numpy as np
2929

@@ -89,21 +89,23 @@ async def decode_endpoint(req: DecodeRequest) -> Dict[str, Any]: # type: ignore
8989
raise HTTPException(status_code=400, detail="check_to_qubits must be non-empty")
9090

9191
try:
92+
dec_any: Union[BatchDecoder, UnionFindDecoder]
9293
if req.use_batch:
93-
dec = BatchDecoder(req.check_to_qubits, req.n_qubits)
94+
dec_any = BatchDecoder(req.check_to_qubits, req.n_qubits)
9495
syndrome_arr = np.array([req.syndrome], dtype=np.uint8)
95-
correction = dec.parallel_batch_decode(syndrome_arr)[0]
96+
correction = dec_any.parallel_batch_decode(syndrome_arr)[0]
9697
else:
97-
dec = UnionFindDecoder(req.check_to_qubits, req.n_qubits)
98+
uf_dec = UnionFindDecoder(req.check_to_qubits, req.n_qubits)
99+
dec_any = uf_dec
98100
syndrome_arr = np.array(req.syndrome, dtype=np.uint8)
99-
correction = dec.decode(syndrome_arr)
101+
correction = uf_dec.decode(syndrome_arr)
100102
except Exception as exc:
101103
raise HTTPException(status_code=500, detail=f"Decode error: {exc}")
102104

103105
return {
104106
"correction": correction.tolist(),
105-
"n_qubits": dec.n_qubits,
106-
"n_checks": dec.n_checks,
107+
"n_qubits": dec_any.n_qubits,
108+
"n_checks": dec_any.n_checks,
107109
"version": __version__,
108110
}
109111

@@ -142,22 +144,24 @@ def decode_endpoint() -> Any:
142144
return jsonify({"error": "check_to_qubits must be non-empty"}), 400
143145

144146
try:
147+
dec_any: Union[BatchDecoder, UnionFindDecoder]
145148
if use_batch:
146-
dec = BatchDecoder(c2q, n_qubits)
149+
dec_any = BatchDecoder(c2q, n_qubits)
147150
syndrome_arr = np.array([syndrome], dtype=np.uint8)
148-
correction = dec.parallel_batch_decode(syndrome_arr)[0]
151+
correction = dec_any.parallel_batch_decode(syndrome_arr)[0]
149152
else:
150-
dec = UnionFindDecoder(c2q, n_qubits)
153+
uf_dec = UnionFindDecoder(c2q, n_qubits)
154+
dec_any = uf_dec
151155
syndrome_arr = np.array(syndrome, dtype=np.uint8)
152-
correction = dec.decode(syndrome_arr)
156+
correction = uf_dec.decode(syndrome_arr)
153157
except Exception as exc:
154158
return jsonify({"error": f"Decode error: {exc}"}), 500
155159

156160
return jsonify(
157161
{
158162
"correction": correction.tolist(),
159-
"n_qubits": dec.n_qubits,
160-
"n_checks": dec.n_checks,
163+
"n_qubits": dec_any.n_qubits,
164+
"n_checks": dec_any.n_checks,
161165
"version": __version__,
162166
}
163167
)

python/qector_decoder_v3/sinter_compat.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,28 @@
2424

2525
from __future__ import annotations
2626

27-
from typing import Dict
27+
from typing import TYPE_CHECKING, Dict
2828

2929
import numpy as np
3030

31+
if TYPE_CHECKING:
32+
pass # sinter types used only at runtime
33+
3134
__all__ = ["QectorSinterDecoder", "qector_sinter_decoders"]
3235

3336
try:
3437
import sinter
3538

36-
_SINTER_BASE = sinter.Decoder
37-
_COMPILED_BASE = sinter.CompiledDecoder
39+
_SINTER_BASE: type = sinter.Decoder
40+
_COMPILED_BASE: type = sinter.CompiledDecoder
3841
_HAS_SINTER = True
3942
except Exception: # pragma: no cover - sinter optional
4043
_SINTER_BASE = object
4144
_COMPILED_BASE = object
4245
_HAS_SINTER = False
4346

4447

45-
class _CompiledQectorDecoder(_COMPILED_BASE):
48+
class _CompiledQectorDecoder(_COMPILED_BASE): # type: ignore[misc,valid-type]
4649
"""A compiled QECTOR decoder bound to one detector error model."""
4750

4851
def __init__(self, matcher, num_detectors: int, num_observables: int):
@@ -70,7 +73,7 @@ def decode_shots_bit_packed(self, *, bit_packed_detection_event_data: np.ndarray
7073
return np.packbits(preds, axis=1, bitorder="little")
7174

7275

73-
class QectorSinterDecoder(_SINTER_BASE):
76+
class QectorSinterDecoder(_SINTER_BASE): # type: ignore[misc,valid-type]
7477
"""A Sinter ``Decoder`` backed by QECTOR.
7578
7679
``kind`` selects the backend: ``"blossom"`` (weighted exact MWPM),

0 commit comments

Comments
 (0)