Skip to content

Numerical backends¤

Numerical backends evaluate binomial expectations and their transposes. They provide the numerical representation used by transitions and evolution methods.

AbstractTransitionBackend

drifft.AbstractTransitionBackend(equinox.Module) [source] ¤

Interface for prepared geometry with a linear expectation operation.

For destination gene-copy count \(M\) and values \(v_j\), backends evaluate the binomial expectation

\[B_M[v](q)=\sum_{j=0}^{M}\binom{M}{j}q^j(1-q)^{M-j}v_j.\]

Implementations provide gene_copies and an expect operation linear in destination values, supporting transposition, JVP, VJP, and batching. Prepare geometry outside JIT; use Transition for compiled application.

expect(self, probabilities: Array, values: Array) -> Array ¤

Compute binomial expectations at the supplied probabilities.

Arguments:

  • probabilities: Float64 vector of finite source probabilities in [0, 1].
  • values: Finite float64 destination vector of length gene_copies + 1.

Returns:

A float64 vector with the same shape as probabilities, linear in values.

Raises:

ValueError for incompatible vector shapes, including during JIT tracing.

Numerical preconditions

Finiteness and probability ranges are unchecked. Outputs are not clipped or normalized; approximation and derivative limitations depend on the concrete backend. Roundoff can vary across devices.


drifft.FFT(drifft.AbstractTransitionBackend) [source] ¤

Binomial expectations through local FFT convolutions.

For destination values \(v_j\) and gene-copy count \(M\), approximate \(B_M[v](q)=\sum_{j=0}^M\binom{M}{j}q^j(1-q)^{M-j}v_j\). On each local log-odds grid, the retained binomial kernel factors as

\[y_r=a_r\sum_j e^{-h(r-(j-j_0))^2/2}\,b_jv_j.\]

Here \(j_0\) is the reference allele count, \(h\) is the log-odds spacing, and \(a_r,b_j\) are diagonal scaling weights. The sum is a convolution, evaluated by zero-padded FFTs; local polynomial interpolation supplies expectations at arbitrary \(q\). Near zero and one, direct binomial sums replace log-odds interpolation.

For offspring probabilities \(q_i\), Transition uses \((Pv)_i=B_M[v](q_i)\) for expectations and the transpose \(P^Tp\) for forward mass. The approximation comes from tail truncation and interpolation; the Gaussian-shaped convolution above is a factorization of the binomial kernel, not a normal approximation to it.

__init__(self, gene_copies: int, *, tail_tol: float = 1e-12, interp_order: int = 12, min_mode_count: int = 128, block_spacing: float = 1.0, balance: bool = True, dtype: DTypeLike = float64) -> None ¤

Plan local binomial FFTs over all success probabilities, independent of selection.

Arguments:

  • gene_copies: Integer gene-copy count, at least two (diploid population size is gene_copies/2).
  • tail_tol: Discarded mass budget in [1e-15, .1), default 1e-12.
  • interp_order: Even polynomial stencil size from 2 through 16, default 12.
  • min_mode_count: Boundary cutoff in expected copies, at least 8, default 128.
  • block_spacing: Center separation in local standard deviations, (0, 4].
  • balance: Whether to recenter the diagonal multipliers, default true.
  • dtype: Must be float64, with jax_enable_x64 already enabled.

Returns:

None. Initializes host-computed quantile windows and cached kernel FFTs. Construction is not JIT-compatible. Core intervals partition log-odds; halos support interpolation. For gene_copies <= 2*min_mode_count all rows use the direct fallback. Tail tolerance does not bound interpolation or roundoff.

Raises:

ValueError for invalid configuration, disabled x64 or nonfinite factors; TypeError if balance is not boolean. Results are deterministic for a fixed environment but need not be bitwise identical across platforms.


drifft.NUFFT(drifft.AbstractTransitionBackend) [source] ¤

Binomial expectations through Chebyshev approximation and NUFFT evaluation.

For destination values \(v_j\) and gene-copy count \(M\), approximate \(B_M[v](q)=\sum_{j=0}^M\binom{M}{j}q^j(1-q)^{M-j}v_j\) by

\[B_M[v](q)\approx\sum_{k=0}^L c_kT_k(1-2q) =\sum_{k=0}^L c_k\cos(k\theta),\qquad\theta=\arccos(1-2q).\]

Here \(T_k\) is the degree-\(k\) Chebyshev polynomial and \(L\) is the prepared approximation degree. The coefficients \(c_k\) come from binomial sampling at fixed Chebyshev–Lobatto nodes followed by a DCT-I. A type-2 NUFFT evaluates the cosine series at arbitrary offspring probabilities. encoding="lobatto" interpolates the samples. encoding="filtered" tapers the modes and adds a linear endpoint correction before evaluation. Endpoint expectations are set directly to \(v_0\) and \(v_M\) to suppress evaluation roundoff. degree always means the output polynomial degree; compressed filtered encoding uses degree + 2 sampling nodes.

For offspring probabilities \(q_i\), Transition uses \((Pv)_i=B_M[v](q_i)\) for expectations and the transpose \(P^Tp\) for forward mass. Polynomial truncation, truncated binomial sampling, and NUFFT evaluation each contribute approximation error.

__init__(self, gene_copies: int, *, accuracy: float = 1e-10, encoding: typing.Literal['lobatto', 'filtered'] = 'lobatto', tail_tol: float | None = None, nufft_tol: float | None = None, batch_size: int = 32, derivative_accuracy: bool = False, dtype: str | type[typing.Any] | numpy.dtype | jax._src.typing.SupportsDType = float64) -> None ¤

Prepare a fixed-degree Bernstein/Chebyshev/NUFFT transition.

Arguments:

  • gene_copies: Destination gene copies, at least two.
  • accuracy: Nominal value-error budget, in [1e-13, 0.1).
  • encoding: "lobatto" (default) interpolates the samples; "filtered" tapers them and corrects endpoints as a polynomial. Both use the exact degree without tapering when compression cannot meet the spectral budget. Neither guarantees positivity.
  • tail_tol: Optional stricter per-node discarded binomial mass tolerance.
  • nufft_tol: Optional stricter FINUFFT tolerance (at least 1e-15).
  • batch_size: Number of sampling nodes processed together.
  • derivative_accuracy: Increase the fixed degree by 50 percent (capped at the exact degree). This is a conservative heuristic, not a derivative bound.
  • dtype: Only float64 is supported; enable JAX x64 before construction.

Returns:

None. Initializes selection-independent geometry. The value budget is split equally between spectral error, sampling error, requested NUFFT tolerance and roundoff. Roundoff and actual NUFFT error must still be measured independently.

Raises:

ValueError for invalid configuration, disabled x64 or unsupported dtype. FFT-specific settings belong to the FFT backend.


drifft.Jacobi(drifft.AbstractTransitionBackend) [source] ¤

Jacobi geometry for binomial expectations and reduced Krylov evolution.

For gene-copy count \(M\) and destination values \(v_j\), the binomial expectation is

\[B_M[v](q)=\sum_{j=0}^M\binom{M}{j}q^j(1-q)^{M-j}v_j.\]

Write \(z=2q-1\). After separating the linear interpolant between \(v_0\) and \(v_M\), approximate the remaining function in the basis \((1-z^2)\phi_k(z)\), \(k=0,\ldots,r-1\). Here \(r\) is rank and \(\phi_k\) is a normalized Jacobi polynomial \(P_k^{(1,1)}\), orthonormal for weight \(3(1-z^2)/4\) on \([-1,1]\). The envelope makes every interior basis function vanish at the count endpoints. Gauss–Jacobi quadrature and binomial stencils map count-space values to these coefficients.

Let \(E\) denote this coefficient map and \(D\) evaluation at the offspring probabilities \(q_i=f(i/M)\). The expectation operator factors as \(P\approx DE\), so forward mass evolves by its transpose. For \(g\geq1\) generations with a fixed frequency map and no immigration,

\[p_g\approx E^T(H^T)^{g-1}D^Tp_0,\qquad H=ED.\]

The reduced operator has \(r\) interior coordinates, plus two endpoint coordinates for full-state evolution. It is assembled using polynomial recurrences and a padded tridiagonal coordinate matrix, without forming the population-sized transition matrix. A separate epoch method computes the power of \(H^T\); representation rank and padding control different approximations from the method's power tolerance.

Transition and Sequential accept arbitrary valid offspring probabilities through the common expectation interface. Reduced Krylov assembly supports neutral and diploid selection, standalone mutation, and one mutation composed with one supported selection rule in either order. Custom maps and longer compositions are not supported by reduced assembly. Recurrent mutation couples the endpoint states to the interior; full-state evolution retains this coupling, while segregating evolution discards endpoint mass each generation. Construct outside JIT with JAX x64 enabled; the prepared geometry is independent of the dynamic process parameters. Reduced epoch domains are checked at runtime, including for zero generations: selection must stay within the configured bound, and mutation rates must be finite and in [0, 1]. In DemographicTransition, size-changing steps use the same backend by default; an explicit FFT or NUFFT boundary_backend can be supplied instead.

__init__(self, gene_copies: int, *, rank: int | None = None, max_strength: float = 0.1, one_step_tolerance: float = 1e-13) -> None ¤

Prepare process-independent geometry outside JIT.

Arguments:

  • gene_copies: Python integer at least four (twice diploid Ne).
  • rank: Interior polynomial count, between one and gene_copies - 1. If omitted, use an ideal one-step polynomial tail bound.
  • max_strength: Static bound in [0, 1) on the absolute values of both diploid fitness reductions, used for reduced Krylov padding. In reduced assembly, Underdominance and Overdominance retain their finite, nonnegative strength domains, including in mutation compositions.
  • one_step_tolerance: Planning tolerance in (0, 1) for polynomial truncation and Jacobi padding separately; not a total error guarantee.

Returns:

None. Runtime geometry consists of JAX arrays; SciPy is used only to prepare Gauss–Jacobi nodes, for which JAX has no equivalent routine.

Raises:

ValueError for invalid counts or tolerances. Enable JAX x64 before constructing this backend. Dense reduced storage costs O(rank²). With RationalKrylov, dense LU factorization costs O(rank³) per power action.

expect(self, probabilities: Array, values: Array) -> Array ¤

Evaluate the binomial expectation at arbitrary offspring probabilities.

Arguments:

  • probabilities: Float64 probability vector, including endpoints.
  • values: Finite real destination values of length gene_copies + 1.

Returns:

One expectation per probability. The operation is linear in values and supports transposition, batching, and polynomial derivatives. Frequency-map and max_strength restrictions apply only to reduced epoch assembly, not this arbitrary-probability operation.

Raises:

ValueError for incompatible input shapes. Probability ranges and finiteness are caller preconditions, as for other transition backends.


drifft.Dense(drifft.AbstractTransitionBackend) [source] ¤

Form binomial transition rows for an independent numerical reference.

For destination gene-copy count \(M\) and source probabilities \(q_i\), construct

\[P_{ij}=\binom{M}{j}q_i^j(1-q_i)^{M-j},\qquad j=0,\ldots,M.\]

Each row is built by repeated Bernoulli convolution, starting at count zero. This avoids factorials, logarithms, and divisions by endpoint probabilities. The calculation is polynomial throughout, so ordinary JAX differentiation gives finite endpoint derivatives without custom rules. It shares no numerical kernels with FFT, NUFFT, or Jacobi.

expect explicitly forms the matrix before multiplying by destination values. With \(N\) source rows, this uses \(O(NM)\) matrix storage and \(O(NM^2)\) arithmetic. Reverse-mode differentiation can retain intermediate rows. Use this backend for small-population checks of transitions, demographic evolution, and gradients; it is not intended for large populations. There is no truncation or interpolation, but floating-point roundoff remains.

__init__(self, gene_copies: int) -> None ¤

Configure the destination count grid outside JIT.

Arguments:

  • gene_copies: Python integer at least two, equal to twice diploid Ne.

Returns:

None. Only the static count is stored; matrices are formed on application.

Raises:

ValueError for invalid counts or if JAX x64 is disabled.

expect(self, probabilities: Array, values: Array) -> Array ¤

Form binomial rows and multiply by destination values.

Arguments:

  • probabilities: Vector of finite source probabilities in [0, 1].
  • values: Finite real destination vector of length gene_copies + 1. Both inputs are converted to float64.

Returns:

One expectation per probability, linear in values. Signed values, transposition, batching, and higher derivatives are supported.

Raises:

ValueError for incompatible vector shapes. Finiteness and probability ranges are caller preconditions; outputs are not clipped or normalized.