Fixation models¶
A fixation model takes two fitness values, f_i (source) and f_j (target), and returns the probability that an organism with fitness f_j displaces one with fitness f_i in some population model. gpgraph-v2 ships four canonical models and accepts user callables.
All four built-in models accept either scalar or matching array inputs. The array path is what add_model uses to populate the entire edge set in a single numpy pass.

Each model maps a source/target fitness pair to a fixation probability with a different shape: SSWM is a sharp threshold, Moran and McCandlish are smoother, and the ratio model is the simplest. Picking one sets how strongly the resulting edge weights favor uphill moves.
strong_selection_weak_mutation¶
Gillespie (1984). Probability of fixation in the strong-selection weak-mutation limit:
For \(f_j \le f_i\), returns 0 (no advantageous fixation).
from gpgraph.fixation import strong_selection_weak_mutation
strong_selection_weak_mutation(1.0, 1.1) # 0.0952
The string alias is "sswm":
This is the default choice for most analyses. It is parameter-free and has a clean interpretation: each edge prob is the fixation probability of the target mutation given the source.
ratio¶
Not a probability, just the relative fitness. Useful when you want edge weights that grow without bound rather than saturate.
The string alias is "ratio".
Not a probability
ratio is not in [0, 1] for f_j > f_i. Treat the resulting prob edge attribute as a relative-flow weight, not a probability mass.
moran¶
Sella and Hirsch (2005). Moran-process fixation probability for population size \(N\):
with overflow-protected branches preserving the scalar v1 behavior. For \(f_i = f_j\), returns \(1/N\) via a two-point average of slightly perturbed evaluations (matches v1).
The string alias is "moran". Requires population_size=N as a keyword:
For N == 1, returns 1.0 by convention (matches v1).
mcclandish¶
McCandlish (2011). An exponentiated form of the Moran probability:
with the same overflow-protected branches and \(f_i = f_j\) handling as moran.
The string alias is "mcclandish".
Spelling
The function and registry key are spelled mcclandish (one d), matching the v1 spelling.
Custom callables¶
add_model accepts any callable as model:
def my_model(fi, fj, **_):
return (fj > fi).astype(float) # 1.0 for advantageous, 0.0 otherwise
G.add_model(column="phenotypes", model=my_model)
The function should either:
- Accept arrays. Receives two equal-length NumPy arrays of float64 source and target fitnesses; returns an array of the same shape. The array path is one numpy call per
add_model. This is the fast path. - Accept scalars. Receives two floats; returns a float.
add_modelfalls back to a Pythonforloop. Slower but correct.
If your callable raises TypeError on arrays, add_model automatically detects this and switches to the scalar loop.
Choosing a model¶
| When you want... | Use |
|---|---|
| The default, parameter-free trajectory analysis | sswm |
| Edge weights proportional to relative fitness (not normalized) | ratio |
| Population-size-dependent fixation, classic Moran formulation | moran |
| Population-size-dependent fixation, exponentiated form | mcclandish |
| Custom logic (e.g., enforce monotonicity) | a callable |
sswm is what gpgraph-v2's test suite and the streamlit demo use unless they explicitly call out a different model.
Numeric stability¶
All four built-in models use log-space decomposition to avoid overflow for large population_size or large fitness ratios. The boundary cases (f_j == f_i, population_size == 1, very large negative s_ij) are handled with the exact same branching as v1 so numerics agree at the seams.
If you see NaN or inf in G.edges[(i, j)]["prob"], check that:
- Your fitness column has no zeros for
moranormcclandish. Both require strictly positive fitnesses. - Your fitnesses are not negative. SSWM, moran, and mcclandish all assume strictly positive fitness.
population_sizeis>= 1.