cointf.fastlik: single-meiosis likelihood¶
Fast exact likelihood for the Housworth-Stahl model.
Same model and recursion as cointf.naive, organised for speed:
\(f^*\), \(S^* = 1 - F^*\) and the integrated tail \(G\) are gamma series. For each distance only the terms that matter are evaluated: an index window is found by bisection with explicit error bounds, and the terms above it are summed in closed form. Typically about 10 of the terms need an incomplete gamma or log-gamma evaluation.
The recursion needs \(\log f^*\) only at gaps between crossovers and \(\log S^*\) only at the distances to the chromosome ends, so each is evaluated only where it is used.
Meioses are grouped by crossover count and the forward recursion runs once per group on a dense array, so cost is a few numpy calls per crossover index and memory scales with the meioses present.
All kernel evaluations needed for one likelihood call are made in a single vectorised call, so the kernel can be swapped without touching the recursion.
The interference parameter \(\nu\) and escape fraction \(p\) may be scalars (a pooled fit) or arrays with one value per meiosis (e.g. when they depend on covariates).
Notes¶
Interference crossovers form a stationary renewal process with gap density \(q f^*(qx; \nu)\), \(q = 1 - p\), where
Escape crossovers form an independent Poisson process of rate \(p\) (per Morgan). Observed crossovers are the superposition, with pathway labels unobserved; the likelihood sums over all labelings with an \(O(n^2)\) forward recursion per meiosis with \(n\) crossovers.
Truncation. With \(z = 2\nu x\) and shape \(a = k\nu\), the series
terms are bounded with the Chernoff exponent
\(e(a, z) = z - a + a \log(a / z)\): \(Q(a, z) \le e^{-e}\) for
\(a < z\) and \(1 - Q(a, z) \le e^{-e}\) for \(a > z\), where
\(Q\) is the regularised upper incomplete gamma function. Terms are
dropped, or replaced by their limit \(Q = 1\), only where the total
error is below \(e^{-40}\) relative to a lower bound on the result. The
log terms of \(f^*\) are concave in \(k\), so its window is the set
of terms within a fixed margin of the largest one. Series indices are
capped at max_series_terms.
- class cointf.fastlik.Meioses(data, L)¶
Crossover data prepared for repeated likelihood evaluation.
Validates and sorts the positions, groups meioses by crossover count and precomputes every distance the likelihood needs. Build it once and pass it to
loglik_fast()for each parameter value.- Parameters:
- datasequence of array_like
Crossover positions in Morgans, one 1-D sequence per meiosis (a meiosis here is one chromosome in one gamete). Positions must be finite, lie in
[0, L]and be distinct within a meiosis; order does not matter. Forcointf.quartet.loglik_quartet()each entry is instead one quartet, the pooled crossovers of two meioses of the same chromosome, and “meiosis” in the attributes below means one such entry.- Lfloat or array_like
Chromosome length in Morgans,
> 0: a scalar shared by all meioses or one value per meiosis.
- Attributes:
- Mint
Number of meioses.
- Lnumpy.ndarray
Chromosome length per meiosis, shape
(M,).- countsnumpy.ndarray
Number of crossovers per meiosis, shape
(M,).- groupslist of tuple
One
(n, idx, iu, ju, end_bounds, gap_bounds)per crossover countn >= 1: meiosis indices, upper-triangle pair indices, and slice bounds into x_end and x_gap.- x_endnumpy.ndarray
Distances from each crossover to the chromosome start and end, where \(S^*\) is needed, before scaling by \(q\).
- row_endnumpy.ndarray
Meiosis index of each entry of x_end.
- x_gapnumpy.ndarray
Distances between pairs of crossovers, where \(f^*\) is needed, before scaling by \(q\).
- row_gapnumpy.ndarray
Meiosis index of each entry of x_gap.
- no_crossovernumpy.ndarray
Indices of meioses without crossovers.
- Raises:
- ValueError
If L has the wrong shape or is not finite and positive, or a meiosis has non-finite, out-of-range or duplicated positions.
Examples
>>> from cointf.fastlik import Meioses, loglik_fast >>> mz = Meioses([[0.2, 0.9], [], [0.5]], L=1.2) >>> mz.M, mz.counts.tolist() (3, [2, 0, 1]) >>> ll = loglik_fast(mz, None, nu=4.0, p=0.05)
- cointf.fastlik.log_fstar_fast(x, nu)¶
Log density of the unit-rate inter-crossover distance.
- Parameters:
- xarray_like
Distances in Morgans,
>= 0. Flattened to 1-D.- nufloat or array_like
Interference parameter,
> 0. A scalar, or an array with the same number of elements as x (one value per distance).
- Returns:
- numpy.ndarray
\(\log f^*(x; \nu)\), shape
(x.size,). Atx = 0this is-inffornu > 1,0fornu = 1and+inffornu < 1.
See also
cointf.naive.log_fstarSlower 200-term reference implementation.
Examples
>>> from cointf.fastlik import log_fstar_fast >>> log_fstar_fast([0.5, 1.0], nu=1.0) # nu = 1 is Exp(1) array([-0.5, -1. ])
- cointf.fastlik.log_sstar_fast(x, nu)¶
Log survival function of the inter-crossover distance.
Evaluates \(\log S^*(x; \nu) = \log(1 - F^*(x; \nu))\).
- Parameters:
- xarray_like
Distances in Morgans,
>= 0. Flattened to 1-D.- nufloat or array_like
Interference parameter,
> 0. A scalar, or an array with the same number of elements as x.
- Returns:
- numpy.ndarray
\(\log S^*(x; \nu)\), shape
(x.size,);0atx = 0.
See also
cointf.naive.log_sstarSlower 200-term reference implementation.
- cointf.fastlik.log_tail_fast(x, nu)¶
Log integrated survival function of the inter-crossover distance.
Evaluates \(\log G(x; \nu)\) with \(G(x) = \int_x^\infty S^*(u; \nu)\, du\), using the closed form \(\int_x^\infty S_k(u)\, du = (a/b) Q(a + 1, bx) - x Q(a, bx)\) for each gamma component (shape \(a\), rate \(b\), \(Q\) the regularised upper incomplete gamma function). \(G(0) = 1\).
- Parameters:
- xarray_like
Lengths in Morgans,
>= 0. Flattened to 1-D.- nufloat or array_like
Interference parameter,
> 0. A scalar, or an array with the same number of elements as x.
- Returns:
- numpy.ndarray
\(\log G(x; \nu)\), shape
(x.size,).-infwhere the series underflows to a non-positive value.
See also
cointf.naive.log_tail_integralScalar 200-term reference implementation.
- cointf.fastlik.loglik_fast(data, L, nu, p, *, kernel=None, per_meiosis=False)¶
Exact log-likelihood of crossover positions under the Housworth-Stahl model.
- Parameters:
- datasequence of array_like or Meioses
Crossover positions in Morgans, one sequence per meiosis, or a prepared
Meiosesobject (faster for repeated evaluation).- Lfloat or array_like or None
Chromosome length(s) in Morgans: a scalar or one value per meiosis. Must be
Nonewhen data is aMeioses, which carries its own lengths.- nufloat or array_like
Interference parameter,
> 0: a scalar shared by all meioses or one value per meiosis.nu = 1is no interference.- pfloat or array_like
Fraction of crossovers escaping interference, in
[0, 1]: a scalar or one value per meiosis.- kernelcallable, optional
Function
kernel(u_gap, nu_gap, u_end, nu_end) -> (log_f, log_S)returning \(\log f^*\) at the gaps u_gap and \(\log S^*\) at the end distances u_end (seeseries_kernel(), the default, andcointf.chebyshev.ChebyshevKernel, a faster approximation). The nu arguments are scalars when nu is shared by all meioses, otherwise arrays matching their distances.- per_meiosisbool, default False
If True, return one log-likelihood per meiosis instead of the sum.
- Returns:
- float or numpy.ndarray
The total log-likelihood, or an array of shape
(M,)if per_meiosis is True.
- Raises:
See also
MeiosesPrepare data once for repeated evaluation.
cointf.naive.loglik_meiosis_naiveSlow single-meiosis reference.
Notes
Passing an array whose entries are all equal is treated as the corresponding scalar, so a pooled fit costs the same either way.
Examples
>>> from cointf.fastlik import loglik_fast >>> data = [[0.31, 1.12], [0.70], []] >>> total = loglik_fast(data, 1.5, nu=4.0, p=0.05) >>> per = loglik_fast(data, 1.5, nu=[4.0, 6.0, 2.0], p=0.05, per_meiosis=True) >>> per.shape (3,)
- cointf.fastlik.series_kernel(u_gap, nu_gap, u_end, nu_end)¶
Default likelihood kernel: exact \(\log f^*\) and \(\log S^*\).
The recursion needs \(f^*\) only at gaps between interference crossovers and \(S^*\) only at distances to the chromosome ends, so a kernel receives the two sets separately. Any callable with this signature can be passed as
kerneltologlik_fast()orcointf.quartet.loglik_quartet().cointf.chebyshev.ChebyshevKernelis a faster, approximate alternative.- Parameters:
- u_gapnumpy.ndarray
1-D array of gaps between crossovers on the interference scale (Morgans times \(q = 1 - p\)),
>= 0.- nu_gapfloat or numpy.ndarray
Interference parameter: a scalar, or an array shaped like u_gap.
- u_endnumpy.ndarray
1-D array of distances from a crossover to a chromosome end, on the interference scale,
>= 0.- nu_endfloat or numpy.ndarray
Interference parameter: a scalar, or an array shaped like u_end.
- Returns:
- log_fnumpy.ndarray
\(\log f^*(u; \nu)\) at u_gap.
- log_Snumpy.ndarray
\(\log S^*(u; \nu)\) at u_end.