P4 (WIP): GenCall-equivalent calling, BAF/LRR, sex, GTC v5, simurg genotype
- genotype/clusters.py: EGT aligned to manifest order (float32 mean R) - genotype/gencall.py: flair, six-case likelihood, priors/posteriors, fuzzy sub-scores and score map, vectorised with the reference's float/double evaluation order and libm transcendentals - genotype/baf_lrr.py, sex.py (rules v1/v2), sample.py (IDAT -> Gtc) - pipeline/inputs.py shared by ingest and genotype - pipeline/genotype.py + CLI: GTC per sample, sample_summary.tsv/json (OUT-060..063), run manifest - GTCs from IDATs are byte-identical to the oracle for 36 GSA samples x 2 normalization versions (NaN sign canonicalised to the x86 pattern) Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01814GKVzZVL3FuF3DeiZXhv
This commit is contained in:
1 parent
0cd7eb8aa0
commit
febdd121ef
12 files changed
+1270
-53
No files matched your search
@@ -0,0 +1,59 @@
|
||||
"""Phase P4 acceptance on P0 data: GTCs produced from IDATs are byte-identical to oracle GTCs (gtc2vcf built without
|
||||
FMA) once the four free-text metadata fields (cluster/manifest file names, AutoCall date and version) are aligned.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from tests.conftest import GSA_BPM, GSA_EGT, GSA_IDATS, ORACLE_GTCS, ORACLE_GTCS_GT2, need
|
||||
|
||||
from simurg.formats.bpm import read_bpm
|
||||
from simurg.formats.egt import read_egt
|
||||
from simurg.formats.gtc import encode_gtc, read_gtc
|
||||
from simurg.formats.idat import read_idat
|
||||
from simurg.genotype.clusters import align_clusters
|
||||
from simurg.genotype.sample import GenotypeParams, genotype_sample, manifest_context
|
||||
|
||||
pytestmark = pytest.mark.data
|
||||
FREE_TEXT = ("cluster_file", "snp_manifest", "autocall_date", "autocall_version")
|
||||
NEG_NAN = np.frombuffer(np.uint32(0xFFC00000).tobytes(), dtype=np.float32)[0]
|
||||
|
||||
|
||||
def canonical_nans(g): # type: ignore[no-untyped-def]
|
||||
"""NaNs computed by libm carry the platform's default sign (+ on aarch64, − on x86-64); SimurgArray always
|
||||
writes 0xFFC00000 like the vendor's x86 build. Compare everything else byte for byte (known_unknowns K-014)."""
|
||||
for k in ("b_allele_freqs", "logr_ratios", "genotype_scores"):
|
||||
a = getattr(g, k).copy()
|
||||
a[np.isnan(a)] = NEG_NAN
|
||||
setattr(g, k, a)
|
||||
return g
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ctx(): # type: ignore[no-untyped-def]
|
||||
need(GSA_BPM, GSA_EGT)
|
||||
m = read_bpm(GSA_BPM)
|
||||
return manifest_context(m, align_clusters(read_egt(GSA_EGT), m))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("folder", "norm"), [(ORACLE_GTCS, "1.2.0"), (ORACLE_GTCS_GT2, "1.1.2")], ids=["1.2.0", "1.1.2"]
|
||||
)
|
||||
def test_gtc_byte_identical(ctx, folder, norm) -> None: # type: ignore[no-untyped-def]
|
||||
need(folder, GSA_IDATS)
|
||||
gtcs = sorted(folder.glob("*.gtc"))
|
||||
assert len(gtcs) >= 18
|
||||
for f in gtcs:
|
||||
sid = f.stem
|
||||
chip = sid.split("_")[0]
|
||||
ref = read_gtc(f)
|
||||
g = genotype_sample(
|
||||
read_idat(GSA_IDATS / chip / f"{sid}_Grn.idat"),
|
||||
read_idat(GSA_IDATS / chip / f"{sid}_Red.idat"),
|
||||
ctx,
|
||||
GenotypeParams(norm_version=norm),
|
||||
)
|
||||
for k in FREE_TEXT:
|
||||
setattr(g, k, getattr(ref, k))
|
||||
assert encode_gtc(g) == encode_gtc(canonical_nans(ref)), sid
|
||||
Reference in new issue
Block a user