P4 validated on 2141 vendor GTCs; legacy AutoCall 1.6.2 preset

- vendor comparison (1000 Genomes Omni2.5, AutoCall 1.6.2.2): every
  computed field identical in 2138/2141 samples; the 3 others trace back
  to the single-locus raw-intensity rule (K-009); sex rule 1 matches all
- AutoCall 1.6.2.x leaves GC10/GC50/#calls/#no-calls at 0 in GTC v3:
  new `--preset autocall-1.6.2` (1.1.2 normalization, sex rule 1, GTC v3,
  legacy statistics); the sample summary keeps the real statistics
- NaN sign canonicalised to the x86 pattern (K-014); report P4.md
This commit is contained in:
kemal committed 2026-09-28 01:21:49 +01:00
1 parent 7fb3967c3f
commit f469c97917
9 files changed
+792 -29

No files matched your search

+14 -8
View File
@@ -37,8 +37,8 @@ ALIGN = (
_W: dict[str, Any] = {}
def _init(bpm: str, egt_dir: str, norm: str, sex_version: int) -> None:
_W.update(bpm=bpm, egt_dir=egt_dir, norm=norm, sex_version=sex_version, ctx={})
def _init(bpm: str, egt_dir: str, norm: str, sex_version: int, preset: str) -> None:
_W.update(bpm=bpm, egt_dir=egt_dir, norm=norm, sex_version=sex_version, preset=preset, ctx={})
def _ctx(cluster_field: str) -> Any:
@@ -69,7 +69,7 @@ def _canon(a: np.ndarray) -> np.ndarray: # type: ignore[type-arg]
def check(args: tuple[str, str, str]) -> dict[str, Any]:
from simurg.formats.gtc import encode_gtc, read_gtc
from simurg.formats.idat import read_idat
from simurg.genotype.sample import GenotypeParams, genotype_sample
from simurg.genotype.sample import genotype_sample, params_for_preset
from simurg.genotype.sex import SexParams
gtc, grn, red = args
@@ -77,8 +77,12 @@ def check(args: tuple[str, str, str]) -> dict[str, Any]:
try:
ref = read_gtc(gtc, check_write_complete=False)
ctx = _ctx(ref.cluster_file)
params = GenotypeParams(
norm_version=_W["norm"], sex=SexParams(version=_W["sex_version"]), gtc_version=ref.version
params = (
params_for_preset(_W["preset"], gtc_version=ref.version)
if _W["preset"]
else params_for_preset(
"current", norm_version=_W["norm"], sex=SexParams(version=_W["sex_version"]), gtc_version=ref.version
)
)
g = genotype_sample(read_idat(grn), read_idat(red), ctx, params)
res["cluster_file"] = ctx.cluster_file_name
@@ -110,12 +114,13 @@ def check(args: tuple[str, str, str]) -> dict[str, Any]:
"ploidy",
"ploidy_type",
"dx",
"slide_identifier",
):
a, b = getattr(g, k), getattr(ref, k)
same = a == b or (isinstance(a, float) and np.isnan(a) and np.isnan(b))
if not same:
diffs[k] = [repr(a), repr(b)]
if ref.version >= 5 and g.slide_identifier != ref.slide_identifier: # field 1016 exists only in v5
diffs["slide_identifier"] = [g.slide_identifier, ref.slide_identifier]
if "genotype_scores" in diffs:
d = np.abs(g.genotype_scores.astype(np.float64) - ref.genotype_scores.astype(np.float64))
res["max_score_abs_diff"] = float(np.nanmax(d))
@@ -155,6 +160,7 @@ def main() -> int:
ap.add_argument("--out", required=True, type=Path)
ap.add_argument("--norm-version", default="1.1.2")
ap.add_argument("--sex-version", type=int, default=1)
ap.add_argument("--preset", default="autocall-1.6.2", help="genotype preset; '' = use --norm/--sex-version")
ap.add_argument("--workers", type=int, default=os.cpu_count() or 1)
ap.add_argument("--limit", type=int, default=0)
a = ap.parse_args()
@@ -184,7 +190,7 @@ def main() -> int:
a.workers,
mp_context=mp.get_context("spawn"),
initializer=_init,
initargs=(str(a.bpm), str(a.egt_dir), a.norm_version, a.sex_version),
initargs=(str(a.bpm), str(a.egt_dir), a.norm_version, a.sex_version, a.preset),
) as ex:
results = list(ex.map(check, triples, chunksize=2))
@@ -218,7 +224,7 @@ def main() -> int:
},
"ref_sex_counts": {s: sum(r.get("sex_ref") == s for r in results) for s in ("M", "F", "U")},
"examples": [r for r in results if r.get("field_diffs")][:15],
"params": {"norm_version": a.norm_version, "sex_version": a.sex_version},
"params": {"preset": a.preset, "norm_version": a.norm_version, "sex_version": a.sex_version},
"seconds": round(time.time() - t0, 1),
}
summary["passed"] = summary["byte_identical"] == len(results) > 0