P9 (WIP): compiled kernels (rust/simurg-core, PyO3) with bit-identical pure-Python fallback
Rust ports of the hot paths found by profiling: fused per-locus GenCall calling and scoring, BAF/LRR, libm maps with CPython math semantics, both nearest-neighbour searches of the normalization fit, and the BPM LocusEntry parser. Address coverage is memoised per IDAT layout. SIMURG_PURE_PYTHON=1 forces the pure paths. On the Pi: genotype_sample 3.4 s -> 1.1 s, 36-sample GSA run 131 s -> 46 s, BPM read 4x faster; 36 GSA + 6 Omni2.5 GTCs and QC tables byte-identical to the pure path; tests/unit/test_core_kernels.py checks every kernel on edge cases. bench/profile_genotype.py, bench/scaling.py, cloud task p9 (thread scaling). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
1 parent
874aeaef61
commit
5bdd41b048
19 files changed
+1755
-6
No files matched your search
@@ -14,6 +14,7 @@ from dataclasses import dataclass
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
|
||||
from simurg._core import core
|
||||
from simurg.signal.normalization.numerics import (
|
||||
F32,
|
||||
TINY,
|
||||
@@ -86,6 +87,8 @@ def closest_points(xref: FloatArr, yref: FloatArr, x: FloatArr, y: FloatArr) ->
|
||||
|
||||
def _closest_slow(xref: FloatArr, yref: FloatArr, x: FloatArr, y: FloatArr) -> IdxArr:
|
||||
"""Exact search: squared distances in float32; the first minimum wins (C strict ``<``)."""
|
||||
if core is not None and len(xref) and all(a.dtype == np.float32 for a in (xref, yref, x, y)):
|
||||
return core.closest_exact(*(np.ascontiguousarray(a) for a in (xref, yref, x, y))) # type: ignore[no-any-return]
|
||||
dx = x[:, None] - xref[None, :]
|
||||
dy = y[:, None] - yref[None, :]
|
||||
return np.argmin(dx * dx + dy * dy, axis=1).astype(np.int64)
|
||||
@@ -118,6 +121,12 @@ def _closest_binned(raw_x: FloatArr, raw_y: FloatArr, axis_x: FloatArr, axis_y:
|
||||
low = rem < F32(0.25)
|
||||
choice = np.where(mid, k, np.where(low, np.where(k == 0, k, 12 + k - 1), np.where(k == 11, k, 12 + k)))
|
||||
|
||||
if core is not None and all(a.dtype == np.float32 for a in (raw_x, raw_y, axis_x, axis_y)):
|
||||
i64 = np.int64
|
||||
return core.closest_binned( # type: ignore[no-any-return]
|
||||
*(np.ascontiguousarray(a, np.float32) for a in (raw_x, raw_y, axis_x, axis_y)),
|
||||
*(np.ascontiguousarray(a, i64) for a in (choice, eligible, b1, b2)),
|
||||
)
|
||||
out = np.full(len(axis_a), -1, dtype=np.int64)
|
||||
for c in np.unique(choice):
|
||||
cand = eligible[b1 == c] if c < 12 else eligible[b2 == c - 12]
|
||||
|
||||
@@ -90,9 +90,11 @@ def address_coverage(idat: Idat, manifest: Manifest) -> float:
|
||||
return float(found.mean())
|
||||
|
||||
|
||||
def check_coverage(idat: Idat, manifest: Manifest) -> tuple[float, list[tuple[str, str]]]:
|
||||
def check_coverage(
|
||||
idat: Idat, manifest: Manifest, cache: LocusMapCache | None = None
|
||||
) -> tuple[float, list[tuple[str, str]]]:
|
||||
"""IN-027: < 0.99 → E_IDAT_MANIFEST_MISMATCH (raised); < 1 → W_IDAT_LOW_ADDRESS_COVERAGE (returned)."""
|
||||
cov = address_coverage(idat, manifest)
|
||||
cov = cache.coverage(idat) if cache is not None and cache.manifest is manifest else address_coverage(idat, manifest)
|
||||
notes: list[tuple[str, str]] = []
|
||||
if cov < COVERAGE_ERROR:
|
||||
raise SimurgError(
|
||||
@@ -149,10 +151,24 @@ class LocusMapCache:
|
||||
self.manifest = manifest
|
||||
self.caddr = control_addresses(manifest)
|
||||
self._maps: dict[bytes, LocusMap] = {}
|
||||
self._coverage: dict[bytes, float] = {}
|
||||
self.max_entries = max_entries
|
||||
|
||||
@staticmethod
|
||||
def _key(idat: Idat) -> bytes:
|
||||
return hashlib.blake2b(np.ascontiguousarray(idat.address_ids).tobytes(), digest_size=16).digest()
|
||||
|
||||
def coverage(self, idat: Idat) -> float:
|
||||
"""``address_coverage`` memoised per address layout."""
|
||||
key = self._key(idat)
|
||||
if (cov := self._coverage.get(key)) is None:
|
||||
if len(self._coverage) >= self.max_entries:
|
||||
self._coverage.pop(next(iter(self._coverage)))
|
||||
cov = self._coverage[key] = address_coverage(idat, self.manifest)
|
||||
return cov
|
||||
|
||||
def get(self, idat: Idat) -> LocusMap:
|
||||
key = hashlib.blake2b(np.ascontiguousarray(idat.address_ids).tobytes(), digest_size=16).digest()
|
||||
key = self._key(idat)
|
||||
lm = self._maps.get(key)
|
||||
if lm is None:
|
||||
if len(self._maps) >= self.max_entries:
|
||||
|
||||
Reference in new issue
Block a user