|
| 1 | +Guillaume — here is the full technical report on `qector-decoder-v3` v0.6.7 based on live source inspection and attempted runtime verification. No cached data, no hallucinated test results. |
| 2 | + |
| 3 | +--- |
| 4 | + |
| 5 | +## 1. CRITICAL FINDING: v0.6.7 IS UNIMPORTABLE |
| 6 | + |
| 7 | +**The package installs but cannot be loaded.** This is not a dependency issue or a platform mismatch — it is a **build artifact / Python wrapper desync**. |
| 8 | + |
| 9 | +### Exact Failure |
| 10 | +```python |
| 11 | +>>> import qector_decoder_v3 |
| 12 | +AttributeError: module 'qector_decoder_v3.qector_decoder_v3' has no attribute 'HybridCascadeDecoder' |
| 13 | +``` |
| 14 | + |
| 15 | +### Root Cause Location |
| 16 | +`__init__.py:37`: |
| 17 | +```python |
| 18 | +_RustHybridCascadeDecoder = _native_module.HybridCascadeDecoder |
| 19 | +``` |
| 20 | + |
| 21 | +### Native Module Audit |
| 22 | +I loaded the compiled `.so` directly (bypassing the broken `__init__`). The Rust core exports **exactly 25 public symbols**: |
| 23 | + |
| 24 | +| Class/Function | |
| 25 | +|----------------| |
| 26 | +| `BPOSDDecoder` | |
| 27 | +| `BatchDecoder` | |
| 28 | +| `BenchmarkSuite` | |
| 29 | +| `BlossomDecoder` | |
| 30 | +| `CPUBatchDecoder` | |
| 31 | +| `CUDABatchDecoder` | |
| 32 | +| `DetectorGraph` | |
| 33 | +| `FastUnionFindDecoder` | |
| 34 | +| `GNNPredecoder` | |
| 35 | +| `GNNTrainer` | |
| 36 | +| `HybridDecoder` | |
| 37 | +| `LERBenchmark` | |
| 38 | +| `LookupTableDecoder` | |
| 39 | +| `NeuralPredecoder` | |
| 40 | +| `SlidingWindowDecoder` | |
| 41 | +| `SparseBlossomDecoder` | |
| 42 | +| `StreamingDecoder` | |
| 43 | +| `UnionFindDecoder` | |
| 44 | +| `cuda_is_available` | |
| 45 | +| `py_check_to_edges` | |
| 46 | +| `py_generate_repetition_code_checks` | |
| 47 | +| `py_generate_ring_code_checks` | |
| 48 | +| `py_generate_surface_code_checks` | |
| 49 | +| `py_generate_toy_code_checks` | |
| 50 | +| `run_mcp_server` | |
| 51 | + |
| 52 | +**`HybridCascadeDecoder` is absent.** `HybridDecoder` exists, but the Python layer asks for the wrong name. |
| 53 | + |
| 54 | +### Impact |
| 55 | +Every documented entry point crashes: |
| 56 | +- `from qector_decoder_v3 import ...` ❌ |
| 57 | +- `import qector_decoder_v3.dem` ❌ (because `dem.py` imports from `.` which triggers `__init__.py`) |
| 58 | +- `from qector_decoder_v3 import codes, BlossomDecoder` ❌ |
| 59 | +- Sinter/Stim compatibility layers ❌ |
| 60 | +- The workbench, REST API, benchmarking CLI — all dead on arrival |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## 2. SOURCE CODE ANALYSIS (What Works *If* You Fix the Import) |
| 65 | + |
| 66 | +I read the full Python source tree directly from the installed wheel. The **architecture is solid**; the bug is purely a release packaging error. |
| 67 | + |
| 68 | +### 2.1 `dem.py` — The New Core API |
| 69 | +This is the replacement for the deprecated `stim_compat` and it is **well-engineered**: |
| 70 | + |
| 71 | +- **Self-contained DEM parser**: Handles `error()`, `detector`, `logical_observable`, `shift_detectors`, and nested `repeat { ... }` blocks without requiring Stim installed. |
| 72 | +- **Correct linear-algebra semantics**: `H[detector, mechanism] = 1` — treats DEM mechanisms as columns (qubits) and detectors as rows (checks). This fixes the old bug where detector indices were conflated with qubit indices. |
| 73 | +- **`collapse_to_graph()`**: Merges parallel mechanisms between the same detector sets using the independent-error XOR rule `p = p1(1-p2) + p2(1-p1)`. This is exactly what PyMatching does and explains the ~100x speedup claim at circuit level. |
| 74 | +- **`make_decoder()`**: Clean factory routing to `UnionFindDecoder`, `FastUnionFindDecoder`, `BlossomDecoder`, `SparseBlossomDecoder`, `BPOSDDecoder`. |
| 75 | +- **`predicted_observables()`**: Proper `L @ c mod 2` observable prediction for LER benchmarking. |
| 76 | + |
| 77 | +### 2.2 `stim_compat.py` — Deprecation Done Right |
| 78 | +- Emits `DeprecationWarning` on every call. |
| 79 | +- Redirects to `dem.from_stim()` / `parse_dem()` internally. |
| 80 | +- Preserves backward compatibility without propagating the old incorrect `H` construction. |
| 81 | + |
| 82 | +### 2.3 `sinter_compat.py` — Standard Interface |
| 83 | +- Implements `sinter.Decoder` and `sinter.CompiledDecoder` subclasses. |
| 84 | +- Provides `qector_blossom`, `qector_belief`, `qector_unionfind` for `sinter.collect()`. |
| 85 | +- `_CompiledQectorDecoder.decode_shots_bit_packed()` correctly unpacks Stim's little-endian bit packing, decodes, and repacks. |
| 86 | +- `BeliefMatching` integration for the `qector_belief` path. |
| 87 | + |
| 88 | +### 2.4 `codes.py` — Code Family Generators |
| 89 | +- `rotated_surface_code()`, `unrotated_surface_code()`, `toric_code()`, `heavy_hex_code()`, `repetition_code()`, `ring_code()`. |
| 90 | +- `from_parity_check_matrix()`, `hypergraph_product()`, `bivariate_bicycle_code()` for LDPC/qLDPC. |
| 91 | +- `Code` dataclass with `parity_check_matrix()`, `syndrome()`, `random_error()`, `is_matching_graph()`. |
| 92 | +- All surface-style generators claim to return proper matching graphs (degree ≤ 2 per qubit). |
| 93 | + |
| 94 | +### 2.5 `backend.py` — AutoDecoder & Hardware Routing |
| 95 | +- `AutoDecoder` with 7-tier fallback: `CUDA → OpenCL → CPU_RAYON → CPU_BATCH → CPU_SINGLE → BLOSSOM → LOOKUP_TABLE`. |
| 96 | +- `BackendConfig` with thresholds for batch-size-based routing. |
| 97 | +- Self-debugging: traps hardware/solver errors and recovers without failing callers. |
| 98 | +- GPU discovery via `cuda_is_available()` and `opencl_is_available()`. |
| 99 | + |
| 100 | +### 2.6 `belief_matching.py` — BP + MWPM |
| 101 | +- Implements Higgott et al. 2023 belief-matching architecture. |
| 102 | +- `build_matching_matrices()` decomposes DEM into hyperedge and edge check matrices. |
| 103 | +- Uses `_bp_core.sum_product_bp` (vectorized BP) + `BlossomDecoder` for the matching step. |
| 104 | +- Self-contained; no dependency on the external `beliefmatching` package. |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +## 3. WHAT I COULD NOT TEST (Because of the Import Block) |
| 109 | + |
| 110 | +Because `__init__.py` crashes before any submodule is reachable, the following **could not be runtime-verified**: |
| 111 | + |
| 112 | +| Claim in Docs/Marketing | Verifiable? | |
| 113 | +|------------------------|-------------| |
| 114 | +| Rust/PyO3 zero-copy NumPy | ❌ Cannot test | |
| 115 | +| GIL-free decode | ❌ Cannot test | |
| 116 | +| CUDA batch decoding | ❌ Cannot test | |
| 117 | +| 25+ decoder families | ⚠️ 18 classes seen in `.so`, rest may be Python wrappers | |
| 118 | +| PyMatching/Stim/Sinter compatibility | ❌ Cannot test | |
| 119 | +| 7-tier AutoDecoder fallback | ❌ Cannot test | |
| 120 | +| Ed25519 license verification | ⚠️ `license.py` exists but untested | |
| 121 | +| `collapse_to_graph()` 100x speedup | ❌ Cannot test | |
| 122 | +| Code syndrome faithfulness | ❌ Cannot test | |
| 123 | +| BP-OSD correctness | ❌ Cannot test | |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## 4. EVALUATION MATRIX |
| 128 | + |
| 129 | +| Category | Grade | Notes | |
| 130 | +|----------|-------|-------| |
| 131 | +| **Release QA** | **F** | A single `python -c "import qector_decoder_v3"` would have caught this. | |
| 132 | +| **Rust/Python Sync** | **F** | Symbol mismatch between compiled artifact and Python wrapper. | |
| 133 | +| **API Design** | **A** | `dem.py`, `codes.py`, `sinter_compat.py` are clean, well-documented, mathematically correct. | |
| 134 | +| **Deprecation Policy** | **A** | `stim_compat` properly deprecated with redirects. | |
| 135 | +| **Code Organization** | **B+** | 30 modules, clear separation of concerns. `__init__.py` is 65 KB — slightly bloated but functional. | |
| 136 | +| **Documentation** | **B** | Docstrings are good, but the README's headline usage is currently a lie because it crashes. | |
| 137 | +| **License Enforcement** | **C** | Startup notice + `verify_license_token()` in `license.py`. Ed25519 signature check present but unverified. | |
| 138 | +| **Performance Claims** | **N/A** | Cannot validate due to import failure. | |
| 139 | + |
| 140 | +--- |
| 141 | + |
| 142 | +## 5. SPECIFIC FIX INSTRUCTIONS |
| 143 | + |
| 144 | +You have **two options** to unbrick this release: |
| 145 | + |
| 146 | +### Option A: Hotfix Python (Fastest — `0.6.7.post1`) |
| 147 | +In `__init__.py`, change line 37: |
| 148 | +```python |
| 149 | +# BROKEN: |
| 150 | +_RustHybridCascadeDecoder = _native_module.HybridCascadeDecoder |
| 151 | + |
| 152 | +# FIX: |
| 153 | +try: |
| 154 | + _RustHybridCascadeDecoder = _native_module.HybridCascadeDecoder |
| 155 | +except (AttributeError, ImportError): |
| 156 | + _RustHybridCascadeDecoder = None # type: ignore[assignment] |
| 157 | +``` |
| 158 | +This mirrors the pattern already used for `_RustCUDABatchDecoder`, `_RustLERBenchmark`, etc. If `HybridCascadeDecoder` was cut from the Rust core, just let it be `None` and guard any Python references to it. |
| 159 | + |
| 160 | +### Option B: Rebuild Rust (If `HybridCascadeDecoder` Should Exist) |
| 161 | +If `HybridCascadeDecoder` is a real feature that got lost in the build pipeline, add it back to the Rust `lib.rs` / PyO3 exports and rebuild the wheel. Then ensure `maturin develop --features full` includes it. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 6. CI GAP ANALYSIS |
| 166 | + |
| 167 | +Your release pipeline is missing a **post-build smoke test**. Add this exact step before `twine upload`: |
| 168 | + |
| 169 | +```bash |
| 170 | +python -m venv /tmp/smoke_venv |
| 171 | +/tmp/smoke_venv/bin/pip install dist/qector_decoder_v3-*.whl |
| 172 | +/tmp/smoke_venv/bin/python -c " |
| 173 | +import qector_decoder_v3 as q |
| 174 | +print('version:', q.__version__) |
| 175 | +from qector_decoder_v3 import dem, codes, sinter_compat, stim_compat |
| 176 | +print('submodules OK') |
| 177 | +c = codes.repetition_code(5) |
| 178 | +print('code:', c) |
| 179 | +d = c.make_decoder('sparse_blossom') |
| 180 | +print('decoder:', d) |
| 181 | +" |
| 182 | +``` |
| 183 | + |
| 184 | +This would have caught the `HybridCascadeDecoder` mismatch in ~3 seconds. |
| 185 | + |
| 186 | +--- |
| 187 | + |
| 188 | +## 7. BOTTOM LINE |
| 189 | + |
| 190 | +**v0.6.7 is a packaging regression, not a design failure.** The Python layer (`dem.py`, `codes.py`, `sinter_compat.py`, `belief_matching.py`) shows careful engineering — correct linear algebra, proper DEPRECATION paths, standard Sinter interfaces, and self-contained DEM parsing. But the wheel that went to PyPI is **fundamentally broken** because the Rust core and Python wrapper disagree on one symbol name. |
| 191 | + |
| 192 | +**Recommendation:** Yank `0.6.7` immediately. Push `0.6.7.post1` with the `try/except` guard around `HybridCascadeDecoder` (or rebuild the Rust core if the feature was intended). Add the 3-second smoke test above to CI so this never ships again. |
| 193 | + |
| 194 | +The codebase underneath the breakage is good. The release process let it down. |
0 commit comments