Skip to content

D12: Single-pass precision/recall alignment with in-alignment FN dropping#92

Open
TimD1 wants to merge 16 commits into
devfrom
91_D12_td_single-pass-prec-recall
Open

D12: Single-pass precision/recall alignment with in-alignment FN dropping#92
TimD1 wants to merge 16 commits into
devfrom
91_D12_td_single-pass-prec-recall

Conversation

@TimD1

@TimD1 TimD1 commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

Replaces vcfdist's greedy, retry-based false-negative (FN) dropping with a single-pass, in-alignment mechanism, then wraps it in a lightweight outer re-align loop to recover swallowed neighbors.

  • The truth graph becomes a DAG: each truth variant gains a parallel reference-allele bypass node. Taking the bypass = declaring that variant FN, at cost skip_cost = ceil((1 - ct) * len) (len = variant edit size). One weighted alignment therefore identifies the missed variants directly — no per-candidate re-alignment search.
  • The forward pass is now a weighted-wavefront shortest path (Dial's bucket queue); the backtrack labels bypassed variants FN and excludes bypass regions from per-sync-group credit.
  • evaluate_variants keeps an outer loop: after a pass, drop the largest detected FN indel/SV and re-align the rest so its swallowed neighbors get clean credit. This replaces the old inner --max-retries exclusion search (which did up to max_retries alignments per iteration just to pick what to drop) with a single detection alignment per iteration.
  • Removed: the retry/exclusion machinery, --max-dist/-md, and --max-retries/-mr.

Design and implementation notes: docs/superpowers/specs/2026-07-17-single-pass-prec-recall-design.md, docs/superpowers/plans/2026-07-17-single-pass-prec-recall.md.

Behavior vs. dev (chr20, GIAB, default -ct 0.7)

Not bit-identical to dev by design — FN dropping is now always-on and globally optimal for the bypass cost function. Four-way comparison (TRUTH_TP, recall):

Variant SNP INDEL All TP vs full-retry
this PR (single-pass + loop) 80160 (0.9704) 8425 (0.6102) 88585 −10
dev full-retry (--max-retries 5) 80160 (0.9704) 8435 (0.6110) 88595 baseline
dev no-drop (--max-retries 0) 80082 (0.9695) 8430 (0.6106) 88512 −83
pure single-pass (no outer loop) 80128 (0.9700) 8426 (0.6103) 88554 −41
  • SNPs exactly match full-retry; overall within −10 TP (all INDEL), and +73 over no-dropping.
  • The ~10-INDEL residual is a deliberate heuristic trade: this loop drops the largest FN first, whereas full-retry drops the edit-distance-minimizing FN. The remaining entangled-locus credit dilution is the spec §8 residual (characterized, not a bug — exercised by integration scenario E).

Runtime (chr20)

Total wall-clock is a wash — ~1.7s for all variants (dominated by reading ~0.44s + clustering ~0.81s + writing ~0.35s). Isolating the alignment-eval phase: single-pass+loop 0.157s vs full-retry 0.126s vs no-drop 0.114s — i.e. single-pass is marginally slower here, because chr20's small, well-separated variants rarely trigger deep retries and the weighted wavefront costs a bit more per alignment. The expected runtime win only appears where the old loop actually re-aligns many times (SV-dense / large clusters). Validation at that scale (analysis-v3) is still pending a linux-64 environment.

Testing

  • Unit (gtest): skip_cost() including large-len regression; existing suite green (2/2).
  • Integration (pytest-workflow, tests/integration/): new synthetic-reference scenarios — (a) large FN SV adjacent to SNPs → SNPs TP + SV FN (swallowing recovery), (b/c) partial-match TP at ct=0.7 vs FN+FP at ct=1.0, (d) co-located spurious call → truth FN + query FP with no distortion, (e) entangled-overlap residual (characterization). All validated against the real binary.

Disclosures / follow-ups

  • RAM estimate factor changed (src/cluster.cpp): g.max_distg.max_size. max_size defaults to 10000 vs old max_dist 1000, so the per-supercluster memory estimate is ~10× larger. Correctness-safe (only makes thread/RAM bucketing more conservative; never drops work), but may reduce parallelism on genome-wide runs with large superclusters. Follow-up: base the factor on an actual wavefront score bound.
  • Pre-existing bug (not introduced here): -p <prefix> overwrites input VCFs when the prefix collides with an input file's basename (main.cpp:54-55). Worth a separate issue.
  • Broader accuracy validation on analysis-v3 (linux-64) remains outstanding.

Closes #91
Part of #55

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant