Skip to content

[2/5] Faster-mixing samplers: parent-set proposals, Dirichlet allele-frequency move, GuidedPairSampler - #142

Open
m-murphy wants to merge 28 commits into
stack/1-leakfrom
stack/2-samplers
Open

m-murphy wants to merge 28 commits into
stack/1-leakfrom
stack/2-samplers

Conversation

@m-murphy

@m-murphy m-murphy commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Summary

Sampler changes that mix the transmission network faster. Every sampler targets the same posterior as before.

 Sampler set (per Infection)
   RandomAllelesBitSetSampler3   # parent-set genotype proposal
+    parent-set option drawn from the parent-set posterior (10% uniform), four loci per redraw
   ParentRelinkSampler
+    candidate parents weighted by observed-allele coverage
   StrainCountSampler
+    observed-allele toggles
   CloneOrderSwapSampler
+    clone infection-time exchange
 Sampler set (shared)
   SALTSampler (allele frequencies)
+    self-fitting Dirichlet independence move in blocks of 4 alleles; fits freeze when adaptation stops
+  GuidedPairSampler              # new: time exchange for mutual pairs, deterministic pilot, pairs picked uniformly

Tuning changes:

  • The genotype proposals (RandomAllelesBitSetSampler3 and 4, ParentRelinkSampler) read their per-allele rates from the Infection's sampled expected false positives and negatives, not fixed 0.01 rates.
  • RELINK_TRIES 2 → 1: ParentRelinkSampler's multiple-try move is off.
  • INFORMED_JUMP_MAX_TRIES 8 → 4.
  • SALTSampler updates every Sampling iteration, with a full SALT sweep every 30th update and the Dirichlet move every 5th.
  • RandomAllelesBitSetSampler4 updates every 10th iteration at loci with an observed allele (every iteration at unobserved loci), and a path that changes nothing is no longer counted as a proposal.

Other changes:

  • AbstractSampler::countProposal counts one acceptance outcome for an update built from several Proposals.
  • The glossary gains Mutual pair, Clone, Pilot and Dirichlet independence move.

Evidence

  • Before: these proposals didn't exist.
    After: engine tests 311 pass (1 skipped), among them exact-draws invariance tests for GuidedPairSampler, the parent-set proposal with state-dependent option weights and the one-locus redraw, and a test of SALTSampler's Dirichlet move alone that fails without its Hastings correction. plasmotrackr tests 3724 pass.
  • Golden Stores re-recorded: a seed now gives different draws, and the Store gains GuidedPairSampler's acceptance rows. plasmotrackr's hand sums are updated to the new rows.
  • The pilot is deterministic only up to rounding. It reads its gains from the target's running sums and compares them with each other and with a 1e-9 threshold. When two gains tie in exact arithmetic, rounding can send the reverse pilot down another path, and that swap's Hastings ratio is then slightly off.

Merge Danger

Door: two-way

Blast Radius: Runs

  • A seed gives different draws than before.
  • A checkpoint written by an older build has a different sampler list, so it is refused on --resume or --start-from.

…nd locus subsets

RandomAllelesBitSetSampler3 draws a parent-set option from the infection's parent-set posterior (a tenth of
updates stay uniform) and can redraw a subset of loci. The parent-set and relink proposals read the infection's
sampled error rates. Also: one relink try, four informed jump tries, allele frequencies every 10th iteration,
and loci with observed alleles walk the allele path every 10th iteration.
…erved-allele coverage

RandomAllelesBitSetSampler3 redraws four loci per update rather than every locus. ParentRelinkSampler weights
each candidate source by its summed observed-allele coverage of the infection (RELINK_SOURCE_WEIGHT_SCALE).
An independence move for a simplex-valued parameter, proposing from a fitted Dirichlet.
Each allele-frequency sampler now also makes a whole-locus Dirichlet independence move, fitted to its own trajectory
over growing windows of updates (20, 50, 100, 200, 400) and fixed after the last. With the move on, the samplers
update every iteration (updateFrequency 1) and the SALT sweep runs every update until the first fit, then every
SALT_UPDATE_FREQUENCY-th. The fit is saved in AdaptationState::fit and in checkpoints under an optional "fits" key,
so checkpoints without it still restore and fit afresh. The Sampler set's ids are unchanged.
The Dirichlet move moves allele frequencies far more per update than the SALT sweep, at about the same cost to the
rest of the chain, so it runs more often and the sweep less.
With fewer updates in burn-in than the fit windows, the SALT samplers kept refitting their Dirichlet move during
sampling, so what they proposed depended on the trajectory. The Sampler set fixes every fit at the first
non-adapting step.
… and swaps per update from an evaluation budget; checkpointed
For a pair whose observed genotypes differ somewhere, a locus where both are observed alike has nothing to
exchange. The units depend on the observed data only.
… fit window, 2 allele toggles

The allele-frequency Dirichlet move every 5th update with fit windows 5, 45, 100, 200, 400, and two observed-allele
toggles per StrainCountSampler update.
…nd sums follow

The parent-set, relink, allele-frequency, strain-count, clone-order and guided-pair proposals change the draws a seed
gives; the Store's files and columns are unchanged apart from GuidedPairSampler's acceptance rows. plasmotrackr's
tests that sum golden rows by hand (acceptance, swaps, sampling seconds) and its convergence print snapshot are
updated to the new rows.
…e one in use, and its test runs the move alone
…n bound where each was written two or three times

This branch has not been deployed

No deployments
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