Implement gamma shape correction for SNP distance calculation

Introduces support for rate heterogeneity via a Poisson-Gamma mixture model. This includes new command-line options (`--gamma-shape`, `--gamma-shape auto`) to enable automatic estimation of the shape parameter $\alpha$ based on substitution counts across partitions. The correction is applied to the distance metric, with logic to disable the correction if variance checks fail.
This commit is contained in:
Eric Coissac
2026-09-12 07:44:39 +02:00
parent e846d35adb
commit 020b391636
11 changed files with 401 additions and 40 deletions
+79 -3
View File
@@ -2249,9 +2249,85 @@ F84/TN93 and is included below. `snp-` prefix on every CLI value.
**`` rate-heterogeneity modifier, applicable to `snp-jc`, `snp-k2p`,
`snp-k81`, `snp-t92`, `snp-f84`, `snp-hky85`, `snp-tn93`** (not `snp-raw`,
nothing to correct; not `snp-logdet`, no standard gamma formulation) — same
formula as the base correction, weighted by a shape parameter `α` supplied
by the user (`--gamma-shape <alpha>`), not estimated by ML. A modifier on
existing values, not a separate enum arm per distance.
formula as the base correction, weighted by a shape parameter `α` either
supplied by the user (`--gamma-shape <alpha>`) or estimated from the data
(`--gamma-shape auto`/`estimate`, method-of-moments — not ML; see
"Automatic α estimation" below). A modifier on existing values, not a
separate enum arm per distance.
### Automatic α estimation (`--gamma-shape auto`)
**Correction (verified against the primary source, 2026-09-11):** Jin &
Nei (1990) itself (*"Limitations of the Evolutionary Parsimony Method of
Phylogenetic Analysis"*, Mol. Biol. Evol. 7(2):82–102 — the paper this
whole `` correction is cited from, confirmed algebraically to match this
codebase's `corrected_log`/`k2p` exactly against their eq. A4, general, and
A8, the `a = 1` case) contains **no** data-driven α-estimation procedure.
Their own recommendation (p. 98) is a fixed default: *"we suggest that the
gamma distance with a = 1 [eq. A8] be used. However, one may choose a
different gamma distance, estimating a from data. Wilson et al. (1989)
recently used a distance with a = 1/2 for restriction-site data of
mitochondrial DNA in hominoids."* — i.e. Jin & Nei explicitly punt
data-driven estimation to a *different* paper (Wilson et al. 1989), not
read/verified here. The estimator below is therefore **not** "Jin & Nei's
method" under any framing — that attribution (present in an earlier
revision of this section) was wrong, not just under-cited.
**Implemented** (`PartitionDispersion`, `obikphylo/src/siblings/algorithms/pairwise.rs`)
as an independent method-of-moments estimator, unrelated to any specific
published procedure: pools substitution counts by **partition** rather
than by genome pair, during the same `reduce_pairwise` pass that builds
`PairwiseTally` (no second scan).
For partition `i`: `n_i` = substitutions pooled over every genome pair,
`L_i` = eligible loci pooled over every genome pair, `R_i = n_i / L_i`.
Modeling among-site rate heterogeneity the same way as the `` correction
itself (a `Gamma(α, α)`-distributed, mean-1, multiplicative rate shared by
every locus in a partition — the classical Poisson-Gamma/negative-binomial
mixture, the general identity behind gamma-rate-heterogeneity corrections,
also behind Uzzell & Corbin's (1971) original observation that substitution
counts across sites/regions are over-dispersed relative to Poisson):
\[
\mathbb{E}[R_i] = \mu \qquad \mathrm{Var}[R_i] = \frac{\mu}{L_i} + \frac{\mu^2}{\alpha}
\]
Weighting each partition's squared deviation by its own `L_i` cancels the
Poisson term before attributing what's left to `α`:
\[
\hat\mu = \frac{\sum_i n_i}{\sum_i L_i} \qquad
V = \frac{\sum_i L_i (R_i-\hat\mu)^2}{\sum_i L_i} \qquad
\hat\alpha = \frac{\hat\mu^2}{V - \hat\mu/\bar L}
\]
where `\bar L` is the mean partition size. Returns "no estimate" (falls
back to the uncorrected formula, warns) when fewer than 2 partitions have
data, `\hat\mu \le 0`, or `V` doesn't exceed the Poisson floor
`\hat\mu/\bar L` — no detectable over-dispersion, `α` would be unbounded.
**Caveat, stated explicitly rather than left implicit**: unlike every
closed-form correction in `snp_distance.rs` (each verified line-by-line
against `ape`'s `dist_dna.c`, and now also against Jin & Nei 1990 directly
for the base `` formula), this estimator is derived from first
principles (the general Poisson-Gamma/negative-binomial identity) with no
primary-source procedure behind it at all — not Jin & Nei's (confirmed
above), and Wilson et al. (1989), the paper they point to instead, hasn't
been read/verified either. Mathematically self-consistent (re-derived and
checked, not guessed), but a from-scratch method, not a literature
implementation. If `--gamma-shape` needs a value with a literature
pedigree rather than an estimated one, Jin & Nei's own stated default,
`α = 1` (`--gamma-shape 1`), is the better-supported choice today.
Deliberately **not** gated by `--sankoff-ratio-ceiling` the way
`base_pair_tally` is (same precedent as `cardinality_tally` — see its own
doc comment): that filter excludes individual saturated *pairs* from a
composition estimate computed once at the very end, from the complete
`PairwiseTally`; the partition axis needed here only exists transiently,
one partition at a time, while `PairwiseTally` is still being built — long
before any pair's final SNP ratio (and thus its ratio_ceiling eligibility)
is known. `--exclude-genome` isn't applied either, matching
`reduce_pairwise`'s own raw per-pair fold.
**Implemented now: `snp-raw`, `snp-jc`, `snp-k2p`, `snp-k81`, `snp-f81`,
`snp-t92`, `snp-tn93`, `snp-tv`, all with `` except `raw`/`tv`** — see