Popcorn

The content on this page was written by AI under human supervision.

Popcorn is a Python package for the expected site-frequency spectrum of a DNA sample under natural selection, the quantity that programs for inferring the distribution of fitness effects (polyDFE, dadi and fitdadi, fastDFE) evaluate in double precision inside their likelihoods. Given a sample size, a scaled selection coefficient and a requested number of digits, it returns entries, whole spectra and their derivatives with respect to the selection coefficient. Each value is exact, or an interval guaranteed to contain the true value, or comes with a count of digits on which two independent computations agree. Further modules average the spectrum over a distribution of fitness effects with exact gradients, evaluate it under dominance, and decide in exact rational arithmetic whether a polynomial is nonnegative or a point lies inside a convex hull, returning a certificate either way. Four more modules give exact expected spectra under multiple-merger coalescents, two-locus branch-length moments under recombination, the selected spectrum through a history of population-size changes for large samples, and a join that polarizes variant sites against an ancestral-allele sequence.

What it does

In a sample of $n$ chromosomes, entry $i$ of the site-frequency spectrum is the number of variable sites at which the derived (mutant) allele is carried by exactly $i$ of them. Under the Poisson random field model of a constant-size population, mutations with scaled selection coefficient $S=4N_e s$ ($S \gt 0$ advantageous) give entry $i$ the expectation

$$E_i(S)=\theta\,\frac{n}{i(n-i)}\,\frac{1-{}_1F_1(n-i;\,n;\,-S)}{1-e^{-S}},\qquad i=1,\dots,n-1,$$

with ${}_1F_1$ the confluent hypergeometric function and $\theta$ the scaled mutation rate (Popcorn uses $\theta=1$). Evaluating it is badly conditioned: at moderate $n$ and $|S|$ the intermediate quantities cancel far beyond the 16 digits of double precision (the evaluator bundle linked at the end of this page records a condition number of $10^{85}$ already at $n=100$, $S=\pm 10$), so a double-precision value can be wrong with no warning. Popcorn computes the same object in exact or arbitrary-precision arithmetic with a check attached to every number.

popcorn.sfs works on the whole vector. The values $M_i={}_1F_1(n-i;n;-S)$ obey a three-term recurrence in $i$ with elementary end values, so the vector solves one tridiagonal linear system. For rational $S$ the solution separates as $M_i=\alpha_i+\beta_i e^{-S}$ with $\alpha_i,\beta_i$ exact fractions (solve_M_exact), and each derivative $d^kM/dS^k$ is one more solve of the same system (solve_dM_exact). assemble_M evaluates $\alpha_i+\beta_i e^{-S}$ at a precision chosen from the size of the fractions, so the cancellation cannot reach the requested digits. This exact route is practical to about $n=2000$. Above that, arb_route runs the recurrence in ball arithmeticeach number is a midpoint plus a radius that provably contains the true value (python-flint) and doubles the precision until the worst relative radius meets the target. It has been run to $n=10^5$.

popcorn.dfe integrates the spectrum against polyDFE's "model C" distribution of fitness effects: a reflected gamma density (shape $b$, mean $S_d \lt 0$) for deleterious mutations plus, with weight $p_b$, an exponential (mean $S_b \gt 0$) for beneficial ones. CertifiedKernel computes ball-arithmetic spectra once at fixed quadrature nodes in $\log|S|$; each later evaluation is a weighted sum over that cache, the gradient in $(b,S_d,p_b,S_b)$ reuses it, and each mix call reports the digits on which two Gauss–Legendre degrees agree plus a bound on the neglected tails. popcorn.dominance handles a dominance coefficient $h\neq 1/2$ (genotype fitnesses $1:1+2sh:1+2s$) with E_dominant, adaptive quadrature over a closed-form inner integral, and E_dominant_qseries, an independent series evaluator for cross-checking. Their self-consistency digit counts are strong evidence of accuracy; unlike the exact fractions and ball radii of popcorn.sfs, they are not proofs.

popcorn.certificates collects the exact deciders used to turn such spectra into proven statements about a model. positivity decides whether a sparse polynomial with rational coefficients is nonnegative on $[0,1]$ by two independent routes (Bernstein expansion with subdivision, and certified root isolation with exact sign checks). exact_lp, fast_lp, cone_lp_b and CertHull decide whether a rational point lies in the convex hull of a set of rational points, returning exact weights when it does and an exact separating (Farkas) functional when it does not. region certifies statements over a whole two-parameter box rather than a grid of samples: positivity on the box, a certified supremum of a rational function, a Cauchy–Schwarz lower bound on a $\chi^2$ distance from it, and rational reconstruction with Sturm root counting. Floating point may propose an answer in these modules; only exact rational arithmetic accepts it, and every certificate is re-substituted before it is returned.

Four further modules go beyond the equilibrium selected spectrum. popcorn.lambda_coalescent handles multiple-merger coalescentsgenealogical models in which three or more ancestral lineages may merge in a single event, as under sweepstakes reproduction at constant population size. For the Kingman, Beta$(2-\alpha,\alpha)$ and Dirac$(\psi)$ families it returns the merger rates $\lambda_{b,k}$ and the expected branch length $E[L_i]$ subtending $i$ of $n$ samples (hence the expected neutral unfolded spectrum) as exact fractions. It carries built-in exact identity checks and an exact $n=20$ reference grid. popcorn.twolocus computes the second moments $E[T_i^A T_j^B]$ of the branch lengths subtending $i$ samples at one locus and $j$ at another, under the coalescent with recombination at scaled rate $\rho$ and a piecewise-constant population size; this is the expected two-locus frequency spectrum up to scale. It enumerates the two-locus configuration chain once per $n$ and then solves sparse linear systems, with no simulation, and is practical to $n=8$. popcorn.transient computes the expected spectrum under genic selection $S$ after a piecewise-constant size history, for samples of roughly $n=1000$ to $2500$. It integrates the Kimura forward diffusion on a fixed frequency grid with a banded implicit time-stepper and projects to the sample size only at the end; it also down-samples and folds spectra. Unlike the rest of the package, popcorn.twolocus and popcorn.transient are floating point: double precision, validated by measurement over a stated range and unmeasured outside it. The guide records that range (for popcorn.transient at $n=1000$, agreement with the certified stationary values to $9.4\times10^{-5}$ relative or better for $|S|\le 100$), and their numbers should be labeled as floating point wherever they appear beside certified ones. popcorn.ancestral is a data utility with nothing certified in it. It streams a table of variant sites against an Ensembl EPO ancestral-allele FASTA file and classifies each site by the EPO confidence convention (uppercase base high, lowercase low, N, - or . no call). For biallelic SNVs it orients REF and ALT to ancestral and derived, and it writes a pos ref alt anc conf table with counters and summary rates.

In BootLoops' bootloops-dev repository (GitHub organization BootLoops-ai) the package is one flat directory, tools/popcorn/: the engine modules (sfs_engine.py, arb_route.py, dfe_layer.py, certquad.py, dominance_oracle.py, dominance_qseries.py, lambda_exact.py, twolocus_engine.py, transient_sfs_engine.py, epo_join.py), the certificate modules and a reference/ folder of stored test values sit beside the package files, and the submodules (popcorn.sfs, popcorn.dfe, popcorn.dominance, popcorn.certificates, popcorn.lambda_coalescent, popcorn.twolocus, popcorn.transient, popcorn.ancestral) import them from there. The package stores a SHA-256 digest of every engine and certificate file and of REFERENCE_VALUES.json, and popcorn.verify() names any file that changed. The command-line script re-verifies the files it uses before importing them. On a digest mismatch, a disagreement between its two routes, or a failure to reproduce a stored reference value, it exits with status 3 and an error message instead of printing an unchecked number. The same package is also distributed from this site as an archive (see Requirements and source below).

Limits. The certified modules assume a constant population size at equilibrium. A piecewise-constant size history is available only in floating point (popcorn.transient for the single-locus selected spectrum, popcorn.twolocus for neutral two-locus moments), and popcorn.lambda_coalescent is neutral and constant-size. There is no migration, no multi-population spectrum, no continuous size change and no fitting driver. Spectra are unfolded, at $\theta=1$. $S$ is the $S$ of polyDFE and fastDFE; dadi and fitdadi use $\gamma=S/2$. The exact route of popcorn.sfs, the command-line entry, check and selftest modes and popcorn.dominance need only mpmath; popcorn.dfe and the ball-arithmetic route popcorn.sfs.arb_route (loaded on first use) need python-flint. The certificate modules load on first use: gmpy2 for the linear-programming certificates, python-flint for cone_lp_b and one positivity route, sympy for root counting, numpy and scipy only as proposers. popcorn.twolocus and popcorn.transient need numpy and scipy; popcorn.lambda_coalescent and popcorn.ancestral need only the standard library.

Examples

Run the self-test suite. From the root of the bootloops-dev repository:

python3 tools/popcorn/selftest.py

The suite prints one line per check (LEG <name> PASS, SKIP or FAIL, the seconds taken and a short detail), then a count of passes, skips and failures and exactly one OVERALL PASS or OVERALL FAIL: <names> line, with exit status 0 or 1. The default checks verify the digests, run the command-line self-test and the single-entry check shown next, and compare the exact and ball-arithmetic routes with direct hypergeometric evaluation. They also test the dominance evaluator's collapse to the closed form at $h=1/2$ and the series evaluator against stored reference values, build a small mixing kernel and compare its analytic gradient with a finite difference, and run the certificate modules on toy hulls and polynomials, including deliberately wrong inputs (an exterior point, a negative polynomial) that must be refused. Further checks cover the newer modules: the exact coalescent identities and the $n=20$ reference grid; the two-locus chain against exact Kingman moments and closed forms; the transient engine against the certified stationary values and its self-convergence table; and the ancestral-join fixture through the library and the command line. With every optional dependency installed the twenty default checks take about twenty seconds. A check whose dependency is missing is skipped with a line naming the package to install, and skips do not fail the suite. --full adds three slow checks (minutes each): the two-route comparison up to $n=10007$, the dominance evaluator's full self-test and the $n=20$ kernel self-test. Nothing is written into the package directory.

Compute one entry two independent ways. Here entry $i=50$ of a sample of $n=100$ at $S=-1000$, to 40 digits:

python3 tools/popcorn/certsfs.py check 100 50 -1000 --dps 40

Three lines come back, labeled closed, series and agree: the value from the hypergeometric closed form (mpmath), the value from an exact-rational series that shares no code with it, and the number of digits on which they agree against the target of 40. If agreement falls below dps minus 5, the script still prints both values, then writes an error to standard error and exits with status 3, so a calling script cannot mistake the run for a success. Only mpmath is needed; it finishes in under a second. The other modes (entry, vector, selftest) are listed under Routines.

Mix the spectrum over a distribution of fitness effects, from Python. Put the repository's tools/ directory on PYTHONPATH so that import popcorn resolves. Then check the digests and drive the mixing layer the way the engine's own self-test does:

import popcorn
popcorn.verify()

from popcorn.dfe import CertifiedKernel
n = 20
K = CertifiedKernel(n, dps=40)
K.build(-1)
K.build(1)
Ev, sc, tail = K.mix({'b': '0.4', 'Sd': '-1000', 'pb': 0})
g = K.grad_mix({'b': 0.4, 'Sd': -1000.0, 'pb': 0.02, 'Sb': 10.0})

verify() returns [] on an intact copy and otherwise one string per changed or missing file. build(-1) and build(1) fill the cache for each sign of $S$ (the slow step; python-flint required). mix returns the mixed spectrum as a list indexed by $i$ (here purely deleterious, $p_b=0$), the digits on which the two quadrature degrees agree, and the relative tail bound; grad_mix returns a dictionary keyed 'b', 'Sd', 'pb', 'Sb', each a list of $\partial E_i/\partial(\text{parameter})$.

Exact expected spectrum under a multiple-merger coalescent, from Python. With tools/ on PYTHONPATH as above, the guide's example evaluates a Beta coalescent and a Dirac coalescent for a sample of 50:

import popcorn                       # with tools/ on PYTHONPATH
from fractions import Fraction
L = popcorn.lambda_coalescent        # exact, standard library
h = L.expected_lengths(50, L.beta_rate(Fraction(3, 2)))[50]   # {i: E[L_i]} exact Fractions
x = L.xi_hat(50, L.dirac_rate(Fraction(1, 10)))               # normalized expected SFS, sums to 1 exactly

beta_rate(Fraction(3, 2)) returns the merger-rate function of the Beta$(1/2,3/2)$ coalescent ($\alpha=3/2$) and dirac_rate(Fraction(1, 10)) that of the Dirac coalescent with $\psi=1/10$. expected_lengths(n, lam) returns a dictionary indexed by the number of lineages; its entry [50] maps each $i=1,\dots,49$ to the exact expected total branch length subtending $i$ of the 50 samples, as a Fraction, which is proportional to the expected unfolded spectrum. xi_hat returns that spectrum as a list of $n-1$ fractions normalized to sum to exactly 1. Pass parameters as Fractions or strings such as '3/2', never as binary floats, or the rates are exact for the wrong rational number. The cost is $O(n^3)$ rational operations whose size grows with $n$: the guide measures about 2 seconds at $n=100$ and 17 seconds at $n=200$ for Beta$(3/2)$, about 3 minutes at $n=200$ for Dirac$(1/2)$, and $O(n^2)$ for Kingman. python3 tools/popcorn/lambda_exact.py runs the exact identity checks and prints the $n=20$ reference grid without writing anything.

Routines

Command line and checks (scripts in tools/popcorn/)

Package

popcorn.sfs (engine modules sfs_engine, arb_route; arb_route loads on first use and needs python-flint)

popcorn.dfe (engine modules dfe_layer, certquad)

popcorn.dominance (engine modules dominance_oracle, dominance_qseries)

popcorn.certificates (modules positivity, exact_lp, fast_lp, cone_lp_b, cert_lp, region, each loaded on first use)

Coalescent, two-locus, transient and ancestral-state modules

popcorn.lambda_coalescent (engine module lambda_exact; standard library only; every value an exact Fraction)

popcorn.twolocus (engine module twolocus_engine; numpy and scipy; floating point, validated as described above)

popcorn.transient (engine module transient_sfs_engine; numpy and scipy; floating point, validated as described above)

popcorn.ancestral (engine module epo_join; standard library only; a data utility, nothing certified)

Used on this site

Requirements and source

Python 3 with mpmath. Optional, each enabling the parts that need it: python-flint (the ball-arithmetic route and vector mode above $n=2000$, popcorn.dfe, cone_lp_b, one positivity route), gmpy2 (the linear-programming certificates), sympy (root counting), numpy and scipy (floating-point proposers for the certificates, and required by popcorn.twolocus and popcorn.transient). popcorn.lambda_coalescent and popcorn.ancestral need only the standard library. Quick test: python3 tools/popcorn/certsfs.py selftest --dps 40. Full suite: python3 tools/popcorn/selftest.py (add --full for the slow checks), from the repository root; a check whose optional dependency is missing is skipped by name and does not fail the suite. Source: tools/popcorn/, version 1.0, in the bootloops-dev repository, released under the MIT license. The same package is distributed from this site as an archive, popcorn-package.tar.gz, with the package guide MANUAL.md and a checksum list MANIFEST.sha256 beside it (sha256sum -c MANIFEST.sha256 verifies the downloads). A separate archive, popgen-dfe-release.tar.gz, is the evaluator bundle posted with the first paper on the rare-mutations page: the engine modules as used there with their check driver, the stored outputs of the validation runs and their comparison scripts.

← back to the tools index