configurator Module
Parameter configuration — build and runtime modification of population models.
Overview
Configurator is the unified API for setting and modifying simulation parameters
identically at build time and runtime.
Key features:
- Fluent chain API —
.competition(carrying_capacity=10000).reproduction(eggs_per_female=50).build() - Immediate writes — every chain method writes to NumPy arrays in-place
- Runtime modification —
pop.update().competition(carrying_capacity=5000)without rebuilding - Model-specific subclasses —
DiscreteConfigurator/AgeStructuredConfiguratorwith narrowed parameter signatures - Preset/modifier/fitness — applied directly to config arrays, no deferred execution
- Equilibrium sync —
carrying_capacity/eggs_per_female/sex_ratiochanges auto-triggersync_equilibrium_metrics
Quick Start
import natal as nt
sp = nt.Species.from_dict(name="demo", structure={"auto": {"A": ["WT", "Var"]}})
# Build-time
pop = (
nt.DiscreteGenerationPopulation.setup(sp)
.initial_state({"female": {"WT|WT": 5000}, "male": {"WT|WT": 5000}})
.reproduction(eggs_per_female=50, sex_ratio=0.5)
.competition(carrying_capacity=10000, low_density_growth_rate=6.0)
.custom(temperature=25.0)
.build()
)
# Runtime
pop.update().competition(carrying_capacity=5000)
pop.update().reproduction(eggs_per_female=100, sex_ratio=0.6)
DiscreteConfigurator
Configurator for DiscreteGenerationPopulation. Parameters are narrowed to the
discrete-generation model.
# Create
cfg = nt.Configurator.for_discrete(species)
# Or via setup()
cfg = nt.DiscreteGenerationPopulation.setup(species)
# Chain configuration — only discrete-relevant parameters are shown
cfg.age_structure(n_ages=2, new_adult_age=1) # fixed 2 ages
cfg.reproduction(
eggs_per_female=50, # eggs per female per tick
sex_ratio=0.5, # fraction female offspring
female_adult_mating_rate=1.0, # adult female mating probability
male_adult_mating_rate=1.0, # adult male mating probability
)
cfg.survival(
female_age0_survival=0.9, # female juvenile survival
male_age0_survival=0.9, # male juvenile survival
)
cfg.competition(
carrying_capacity=10000, # equilibrium carrying capacity K
low_density_growth_rate=6.0, # low-density growth rate r
juvenile_growth_mode="concave", # density-regulation function
)
AgeStructuredConfigurator
Configurator for AgeStructuredPopulation. Supports per-age array parameters
and the Champer equilibrium model.
cfg = nt.Configurator.for_age_structured(species)
cfg.age_structure(n_ages=8, new_adult_age=2)
# Per-age parameters accept flexible input:
# scalar — fills all ages
# list — per-age values
# dict — sparse map {age: value}
# callable — lambda age: ...
cfg.reproduction(
eggs_per_female=100,
sex_ratio=0.5,
female_age_based_mating_rate=[0.0, 0.0, 1.0, 1.0, 1.0, 0.5, 0.3, 0.0],
=True,
)
Note:
Configure simulation flags and population name.=Trueis accepted for compatibility but has no effect — sperm storage is always enabled in the age-structured model. cfg.survival( female=[1.0, 0.95, 0.9, 0.85, 0.8, 0.7, 0.5, 0.0], male=[1.0, 0.9, 0.85, 0.8, 0.7, 0.5, 0.3, 0.0], ) cfg.competition( carrying_capacity=5000, low_density_growth_rate=6.0, juvenile_growth_mode="logistic", competition_strength=5.0, # Champer model — custom equilibrium distribution equilibrium_distribution=custom_dist, )
initial_state(individual_count, sperm_storage=None)
cfg.initial_state(individual_count={
"female": {"WT|WT": [0, 200, 150, 100]},
"male": {"WT|WT": [0, 200, 150, 100]},
})
{sex: {genotype: age_data}}.
custom(**fields)
Register named fields stored in config.custom. Hooks read/write via
config.custom['name'][()].
with_observation(groups, *, collapse_age=False)
Register observation groups, applied at build() time. groups can be a dict
of name-to-spec, a list of specs, or None for one-group-per-genotype.
collapse_age controls whether the age axis is collapsed in exports.
presets(*presets)
Apply genetic presets immediately — writes directly to config arrays (not deferred).
reconfigure_preset(preset, **changes)
Modify a registered preset parameter and re-apply from baselines. Restores
baseline fitness/gamete arrays, applies the updated preset parameters, and
syncs equilibrium. Requires that the preset was first registered via
presets().
modifiers(gamete_modifiers=None, zygote_modifiers=None)
Register gamete/zygote modifiers, immediately rebuilding genotype/gamete maps.
fitness(viability=None, fecundity=None, sexual_selection=None, zygote_viability=None, mode="replace")
cfg.fitness(
viability={"WT|WT": 0.8, "WT|Var": 1.0},
fecundity={"female": {"WT|Var": 1.2}},
mode="multiply",
)
{"female": {...}, "male": {...}} for sex-specific values.
mode="replace" overwrites, mode="multiply" scales existing values.
hooks(*hook_items)
Register event hooks, forwarded to the Population constructor at build() time.
build(name=None, hooks=None)
Sync equilibrium metrics and create the Population object.
apply()
Run equilibrium sync without creating a Population. Normally unnecessary —
build() calls apply() internally.
Runtime Modification
pop.update()
# Single parameter
pop.update().competition(carrying_capacity=5000)
# Chained multiple parameters
pop.update().reproduction(eggs_per_female=100).competition(carrying_capacity=10000)
# Custom fields
pop.update().custom(temperature=35.0)
freeze() or rebuild needed.
Inside Hooks
@nt.hook(event="early", custom=True)
def my_hook(state, config, deme_id):
config.carrying_capacity[()] = 5000
config.custom['temperature'][()] = 40.0
Spatial Population
# All demes
pop.update().competition(carrying_capacity=5000)
# Single deme (clone-on-write)
pop.update(deme=3).competition(carrying_capacity=8000)
# Batch per-deme
from natal.spatial_builder import batch_setting
pop.update().competition(
carrying_capacity=batch_setting([100, 200, 300, 400])
)
Low-Level API
set_param(config, name, value)
from natal.configurator import set_param
set_param(config, "competition.carrying_capacity", 5000.0)
set_param(config, "carrying_capacity", 5000.0) # short name also works
parameters.py registry, locates the config field and index, and writes in-place.
Equilibrium-sensitive parameters (K / eggs / sex_ratio) auto-trigger sync.
hook_set_param(config, name, value)
from natal.configurator import hook_set_param
@nt.hook(event="early", custom=True)
def my_hook(state, config, deme_id):
hook_set_param(config, "carrying_capacity", 5000.0)
hook_set_param(config, "reproduction.eggs_per_female", 100.0)
return 0
objmode + set_param for callable-from-njit convenience. Use when
you need string-name routing inside hooks. The fastest path remains direct
config.field[()] = v.
Configurator.for_config(config)
Returns DiscreteConfigurator or AgeStructuredConfigurator based on config type.