spatial Module
Spatial simulation models with multi-deme support.
Overview
The spatial subpackage provides SpatialPopulation, SpatialConfigurator, and
topology classes for multi-deme population simulations with migration.
Complete Module Reference
natal.spatial
Spatial population models, topology, and configuration.
BatchSetting
Deferred per-deme parameter specification.
Wraps one of three value kinds used by SpatialConfigurator to express
parameters that vary across demes:
- scalar: A Python sequence (list/tuple), one element per deme. Each element can be a scalar or an array (e.g. per-deme equilibrium distributions).
- array: A 1D or 2D numpy array. 1D arrays have one element per deme
(flat-index order); 2D arrays use
(row, col)layout matching the topology grid and are flattened in row-major order at build time. - spatial: A callable
(flat_idx) -> floator(row, col) -> float(auto-detected by parameter count), expanded at build time.
SpatialConfigurator detects BatchSetting values in builder method
calls, stores them, and expands them during build().
Initialize a BatchSetting from one of three value kinds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Union[Sequence[Any], NDArray[floating[Any]], Callable[..., float]]
|
Per-deme specification — a sequence of scalars (one per deme), a 1D/2D numpy array, or a callable for spatial expansion. |
required |
Source code in src/natal/spatial/configurator.py
expand
Expand to a concrete list of per-deme values.
- scalar/array: validate length, return as list (2D arrays are flattened row-major).
- spatial: call the function for each deme index. Parameter
count is auto-detected: 1 param →
fn(flat_idx), 2 params →fn(row, col).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_demes
|
int
|
Number of demes to expand to. |
required |
topology
|
Optional[GridTopology]
|
Optional |
None
|
Returns:
| Type | Description |
|---|---|
List[Any]
|
List of per-deme values (scalars or arrays). |
Raises:
| Type | Description |
|---|---|
ValueError
|
If length mismatch or spatial kind without topology. |
Source code in src/natal/spatial/configurator.py
first_value
Return a single concrete element for template-builder delegation.
SpatialConfigurator holds a single-deme template builder internally.
When a parameter is wrapped in batch_setting (a per-deme list),
the template builder still needs one scalar value to proceed through
setup() → … → build(). This method provides that value —
typically the first element of the list or array.
For spatial kind (lazy callable), returns None because the value
cannot be resolved without topology expansion at build time.
Returns:
| Type | Description |
|---|---|
Any
|
The first element for scalar/array kinds, or |
Source code in src/natal/spatial/configurator.py
SpatialConfigurator
SpatialConfigurator(species: Species, n_demes: int, topology: Optional[GridTopology] = None, *, pop_type: Literal['age_structured', 'discrete_generation'] = 'age_structured')
Fluent builder for SpatialPopulation.
Wraps a single-deme Configurator as a template. All chainable
configuration methods delegate to the template and return self.
Spatial-specific parameters (topology, migration, adjacency) are stored
directly and forwarded to SpatialPopulation at build time.
Initialize the spatial configurator.
Creates a single-deme template Configurator internally and
stores spatial parameters (topology, migration) for later use
during build().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
Species
|
Genetic architecture shared by all demes. |
required |
n_demes
|
int
|
Number of demes in the spatial layout. |
required |
topology
|
Optional[GridTopology]
|
Optional grid topology for migration routing. |
None
|
pop_type
|
Literal['age_structured', 'discrete_generation']
|
Population model type — |
'age_structured'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/natal/spatial/configurator.py
setup
setup(name: str = 'SpatialPopulation', stochastic: bool = True, continuous_sampling: bool = False, fixed_egg_count: bool = False) -> SpatialConfigurator
Configure basic population settings.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Human-readable population name. |
'SpatialPopulation'
|
stochastic
|
bool
|
Whether to use stochastic sampling. |
True
|
continuous_sampling
|
bool
|
If True, use Dirichlet sampling. |
False
|
fixed_egg_count
|
bool
|
If True, egg count is fixed. |
False
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
age_structure
age_structure(n_ages: int = 8, new_adult_age: int = 2, generation_time: Optional[float] = None, equilibrium_distribution: Optional[Union[List[float], NDArray[float64]]] = None) -> SpatialConfigurator
Configure age structure (age-structured models only).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_ages
|
int
|
Number of age classes. |
8
|
new_adult_age
|
int
|
Age at which individuals become adults. |
2
|
generation_time
|
Optional[float]
|
Optional pre-computed generation time. |
None
|
equilibrium_distribution
|
Optional[Union[List[float], NDArray[float64]]]
|
Optional equilibrium distribution. |
None
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
initial_state
Configure the initial population state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
individual_count
|
Any
|
Initial abundance mapping. |
required |
sperm_storage
|
Optional[Any]
|
Optional initial sperm storage (age-structured only). |
None
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
survival
survival(female_age_based_survival: Optional[Any] = None, male_age_based_survival: Optional[Any] = None, generation_time: Optional[float] = None, equilibrium_distribution: Optional[Any] = None, female_age0_survival: Optional[float] = None, male_age0_survival: Optional[float] = None) -> SpatialConfigurator
Configure survival rates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
female_age_based_survival
|
Optional[Any]
|
Per-age female survival (age-structured). |
None
|
male_age_based_survival
|
Optional[Any]
|
Per-age male survival (age-structured). |
None
|
generation_time
|
Optional[float]
|
Optional generation time override. |
None
|
equilibrium_distribution
|
Optional[Any]
|
Optional equilibrium distribution. |
None
|
female_age0_survival
|
Optional[float]
|
Female age-0 survival (discrete-generation). |
None
|
male_age0_survival
|
Optional[float]
|
Male age-0 survival (discrete-generation). |
None
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
reproduction
reproduction(eggs_per_female: Union[float, BatchSetting] = 50.0, sex_ratio: Union[float, BatchSetting] = 0.5, fixed_egg_count: bool = False, female_age_based_mating_rate: Optional[Any] = None, male_age_based_mating_rate: Optional[Any] = None, age_based_reproduction_rate: Optional[Any] = None, female_age_based_fertility: Optional[Any] = None, sperm_displacement_rate: float = 0.05, female_adult_mating_rate: float = 1.0, male_adult_mating_rate: float = 1.0) -> SpatialConfigurator
Configure reproduction and mating parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
eggs_per_female
|
Union[float, BatchSetting]
|
Expected offspring per adult female. Accepts |
50.0
|
sex_ratio
|
Union[float, BatchSetting]
|
Proportion of female offspring. |
0.5
|
fixed_egg_count
|
bool
|
If True, egg count is deterministic. |
False
|
female_age_based_mating_rate
|
Optional[Any]
|
Female mating rates (age-structured). |
None
|
male_age_based_mating_rate
|
Optional[Any]
|
Male mating rates (age-structured). |
None
|
age_based_reproduction_rate
|
Optional[Any]
|
Reproduction participation rates. |
None
|
female_age_based_fertility
|
Optional[Any]
|
Fertility weights. |
None
|
sperm_displacement_rate
|
float
|
Rate of sperm displacement (age-structured). |
0.05
|
female_adult_mating_rate
|
float
|
Adult female mating rate (discrete-generation). |
1.0
|
male_adult_mating_rate
|
float
|
Adult male mating rate (discrete-generation). |
1.0
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
competition
competition(competition_strength: float = 5.0, juvenile_growth_mode: Union[int, str, BatchSetting] = 'logistic', low_density_growth_rate: Union[float, BatchSetting] = 6.0, age_1_carrying_capacity: Union[int, None, BatchSetting] = None, old_juvenile_carrying_capacity: Union[int, None, BatchSetting] = None, expected_num_new_adult_females: Union[int, None, BatchSetting] = None, equilibrium_distribution: Optional[Union[List[float], NDArray[float64], BatchSetting]] = None, carrying_capacity: Union[int, None, BatchSetting] = None) -> SpatialConfigurator
Configure competition and density-dependence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
competition_strength
|
float
|
Relative competition factor for age-1 juveniles (age-structured only). |
5.0
|
juvenile_growth_mode
|
Union[int, str, BatchSetting]
|
Growth model identifier. Accepts |
'logistic'
|
low_density_growth_rate
|
Union[float, BatchSetting]
|
Growth rate at low density. Accepts |
6.0
|
age_1_carrying_capacity
|
Union[int, None, BatchSetting]
|
Carrying capacity at age=1 (age-structured).
Accepts |
None
|
old_juvenile_carrying_capacity
|
Union[int, None, BatchSetting]
|
Alias for |
None
|
expected_num_new_adult_females
|
Union[int, None, BatchSetting]
|
Equilibrium adult females. Accepts |
None
|
equilibrium_distribution
|
Optional[Union[List[float], NDArray[float64], BatchSetting]]
|
Optional equilibrium distribution. |
None
|
carrying_capacity
|
Union[int, None, BatchSetting]
|
Carrying capacity (discrete-generation). Accepts |
None
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
presets
Add gene-drive presets (applied during build).
Each positional argument may be a BatchSetting of preset objects,
allowing different demes to receive different presets.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*preset_list
|
GeneticPreset
|
One or more preset objects, or |
()
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
fitness
fitness(viability: Optional[Any] = None, fecundity: Optional[Any] = None, sexual_selection: Optional[Any] = None, zygote_viability: Optional[Any] = None, mode: str = 'replace') -> SpatialConfigurator
Configure fitness values (applied after presets).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
viability
|
Optional[Any]
|
Genotype selectors to viability fitness values. |
None
|
fecundity
|
Optional[Any]
|
Genotype selectors to fecundity fitness values. |
None
|
sexual_selection
|
Optional[Any]
|
Mating preference mapping. |
None
|
zygote_viability
|
Optional[Any]
|
Genotype selectors to zygote viability values. |
None
|
mode
|
str
|
|
'replace'
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
hooks
Register lifecycle hooks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*hook_items
|
_HookItem
|
Functions decorated with |
()
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
modifiers
modifiers(gamete_modifiers: Optional[List[Tuple[int, Optional[str], Callable[..., object]]]] = None, zygote_modifiers: Optional[List[Tuple[int, Optional[str], Callable[..., object]]]] = None) -> SpatialConfigurator
Configure custom modifier functions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
gamete_modifiers
|
Optional[List[Tuple[int, Optional[str], Callable[..., object]]]]
|
Modifiers for gamete production. |
None
|
zygote_modifiers
|
Optional[List[Tuple[int, Optional[str], Callable[..., object]]]]
|
Modifiers for zygote formation. |
None
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
with_observation
Register observation groups for compressed history recording.
The groups are compiled into a binary mask at build time and passed
to the spatial simulation kernel on each run() call. Once set,
history records store aggregated observation data instead of raw
flattened state across all demes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
groups
|
GroupsInput
|
Observation groups (dict of name -> spec, list of specs, or None for one-group-per-genotype). |
required |
collapse_age
|
bool
|
Whether to collapse the age axis in exports. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
SpatialConfigurator |
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
migration
migration(kernel: Optional[NDArray[float64]] = None, migration_rate: float = 0.0, strategy: Literal['auto', 'adjacency', 'kernel', 'hybrid'] = 'auto', adjacency: Optional[object] = None, kernel_bank: Optional[Sequence[NDArray[float64]]] = None, deme_kernel_ids: Optional[NDArray[int64]] = None, kernel_include_center: bool = False, adjust_migration_on_edge: bool = False) -> SpatialConfigurator
Configure spatial migration parameters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kernel
|
Optional[NDArray[float64]]
|
Odd-shaped 2D migration kernel. |
None
|
migration_rate
|
float
|
Fraction of each deme that migrates. |
0.0
|
strategy
|
Literal['auto', 'adjacency', 'kernel', 'hybrid']
|
Migration strategy ( |
'auto'
|
adjacency
|
Optional[object]
|
Explicit adjacency matrix. |
None
|
kernel_bank
|
Optional[Sequence[NDArray[float64]]]
|
Optional heterogeneous kernel bank. |
None
|
deme_kernel_ids
|
Optional[NDArray[int64]]
|
Per-deme kernel ids into |
None
|
kernel_include_center
|
bool
|
Whether kernel includes center cell. |
False
|
adjust_migration_on_edge
|
bool
|
Whether to adjust migration rates on boundaries. When False (default), boundary demes migrate less due to fewer valid neighbors. When True, all demes have the same total migration rate regardless of position. |
False
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
Self for chaining. |
Source code in src/natal/spatial/configurator.py
get_params
Return all registered parameter values.
Merges spatial-specific params with values read from the template config.
Source code in src/natal/spatial/configurator.py
get_param
build
Build and return the configured SpatialPopulation.
Two paths, chosen automatically based on whether any batch_setting()
values were used during the chain:
-
Homogeneous (no
batch_setting): build ONE template deme, clone N-1 times via_clone_deme(). All demes share the same config object — maximum memory efficiency, zero redundant work. -
Heterogeneous (any
batch_setting): expand batch values, group demes by parameter signature, build one template per group. Within a group, demes share a config. Across groups, heavy ndarrays (genotype maps, fitness tensors) are shared via_replace()when possible.
Returns:
| Type | Description |
|---|---|
SpatialPopulation
|
A |
Source code in src/natal/spatial/configurator.py
for_population
staticmethod
for_population(spatial_pop: SpatialPopulation, *, deme: int | None = None) -> Configurator | _SpatialUpdate
Create a Configurator wired to one deme, or a multi-deme updater.
This is the runtime-update counterpart of SpatialConfigurator()
(build-time). It returns an object whose chainable methods modify
the existing spatial population's configs:
for_population(pop, deme=3)→ a single-demeConfigurator(clone-on-write, same aspop.update_deme(3)).for_population(pop)→ a multi-deme updater whose chainable methods apply the change to every deme.
Examples::
# Single deme
SpatialConfigurator.for_population(pop, deme=2).competition(
carrying_capacity=800,
)
# All demes
SpatialConfigurator.for_population(pop).competition(
carrying_capacity=800,
)
# All demes with per-deme variation
from natal.spatial.configurator import batch_setting
SpatialConfigurator.for_population(pop).competition(
carrying_capacity=batch_setting([100, 200, 300, 400]),
)
Source code in src/natal/spatial/configurator.py
SpatialPopulation
SpatialPopulation(demes: Sequence[DemePopulation], *, topology: Optional[GridTopology] = None, adjacency: Optional[object] = None, migration_kernel: Optional[NDArray[float64]] = None, migration_strategy: Literal['auto', 'adjacency', 'kernel', 'hybrid'] = 'auto', kernel_bank: Optional[Sequence[NDArray[float64]]] = None, deme_kernel_ids: Optional[NDArray[int64]] = None, kernel_include_center: bool = False, migration_rate: float | NDArray[float64] | Sequence[float] = 0.0, adjust_migration_on_edge: bool = False, name: str = 'SpatialPopulation')
Spatial container composed of per-deme population objects.
This class models spatial structure via composition: every deme is one
already-initialized BasePopulation subclass instance.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Human-readable name for the spatial container. |
demes |
Sequence[DemePopulation]
|
Immutable view of managed demes. |
n_demes |
int
|
Number of demes in the spatial system. |
species |
object
|
Shared species object used by all demes. |
topology |
GridTopology | None
|
Spatial topology used by the landscape. |
adjacency |
NDArray[float64]
|
Outbound migration matrix between demes
when migration mode is |
migration_strategy |
Literal['auto', 'adjacency', 'kernel', 'hybrid']
|
Strategy selector for migration backend. |
migration_mode |
Literal['adjacency', 'kernel']
|
Active migration backend. |
migration_kernel |
NDArray[float64] | None
|
Migration kernel used when
|
kernel_bank |
tuple[NDArray[float64], ...] | None
|
Optional bank of per-pattern kernels reserved for future per-deme kernel routing. |
deme_kernel_ids |
NDArray[int64] | None
|
Optional per-deme kernel id
mapping into |
migration_rate |
float
|
Fraction of each deme that participates in migration on each tick. |
tick |
int
|
Current shared simulation tick across all demes. |
Initialize a spatial population container from existing demes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
demes
|
Sequence[DemePopulation]
|
Sequence of already-initialized deme populations. |
required |
topology
|
Optional[GridTopology]
|
Optional grid topology used to derive adjacency when
|
None
|
adjacency
|
Optional[object]
|
Optional explicit migration matrix with shape
|
None
|
migration_kernel
|
Optional[NDArray[float64]]
|
Optional odd-shaped 2D kernel used for topology-
aware migration. When provided, |
None
|
migration_strategy
|
Literal['auto', 'adjacency', 'kernel', 'hybrid']
|
Backend selection policy. |
'auto'
|
kernel_bank
|
Optional[Sequence[NDArray[float64]]]
|
Optional kernel bank reserved for future per-deme heterogeneous-kernel routing. |
None
|
deme_kernel_ids
|
Optional[NDArray[int64]]
|
Optional per-deme kernel id array reserved for future heterogeneous-kernel routing. |
None
|
kernel_include_center
|
bool
|
Whether kernel migration includes the kernel center as an outbound target for the source deme. |
False
|
migration_rate
|
float | NDArray[float64] | Sequence[float]
|
Fraction of each deme that migrates each tick. Scalar applies only to adult ages (>= new_adult_age from config); juvenile ages default to 0. Array indexed by age. |
0.0
|
adjust_migration_on_edge
|
bool
|
Whether to adjust migration rates on boundaries. When False (default), boundary demes migrate less due to fewer valid neighbors. When True, all demes have the same total migration rate regardless of position. |
False
|
name
|
str
|
Human-readable container name. |
'SpatialPopulation'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/natal/spatial/population.py
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 | |
demes
property
Sequence[DemePopulation]: Immutable view of all managed demes.
adjacency
property
NDArray[np.float64]: Outbound migration matrix between demes.
topology
property
GridTopology | None: Landscape topology used by the spatial model.
migration_mode
property
Literal["adjacency", "kernel"]: Active migration backend.
migration_strategy
property
Literal["auto", "adjacency", "kernel", "hybrid"]: Strategy policy.
migration_kernel
property
NDArray[np.float64] | None: Kernel used by topology-aware migration.
kernel_bank
property
tuple[NDArray[np.float64], ...] | None: Reserved heterogeneous kernels.
deme_kernel_ids
property
NDArray[np.int64] | None: Reserved per-deme kernel ids.
migration_rate
property
writable
NDArray[np.float64]: Age-specific migration rate per tick.
adjust_migration_on_edge
property
bool: Whether to adjust migration rates on boundaries.
history
property
A list of recorded historical states as (tick, flattened_array) tuples.
Each flattened array contains the full stacked spatial state:
[tick, ind_all.ravel(), sperm_all.ravel()].
record_observation
property
writable
The compiled Observation used for observation-mode history.
hooks
property
LifecycleWrappers: Compiled hooks and lifecycle loop functions.
builder
classmethod
builder(species: Species, n_demes: int, topology: Optional[GridTopology] = None, *, pop_type: Literal['age_structured', 'discrete_generation'] = 'age_structured') -> SpatialConfigurator
Create a SpatialConfigurator for fluent spatial population construction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
species
|
Species
|
Genetic architecture shared by all demes. |
required |
n_demes
|
int
|
Number of demes in the spatial layout. |
required |
topology
|
Optional[GridTopology]
|
Optional grid topology for migration. |
None
|
pop_type
|
Literal['age_structured', 'discrete_generation']
|
|
'age_structured'
|
Returns:
| Type | Description |
|---|---|
SpatialConfigurator
|
A |
Examples:
>>> pop = SpatialPopulation.builder(species, n_demes=100) \
... .setup(name="demo") \
... .initial_state(...) \
... .competition(carrying_capacity=batch_setting([...])) \
... .build()
Source code in src/natal/spatial/population.py
deme
Return one deme by positional index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
idx
|
int
|
Zero-based deme index. |
required |
Returns:
| Type | Description |
|---|---|
DemePopulation
|
The deme population at |
update
Return an updater for modifying this population's config.
Supports both scalar and batch_setting values in chain calls,
matching the SpatialConfigurator API::
# Modify all demes simultaneously
pop.update().competition(carrying_capacity=5000)
# Modify a specific deme (auto-detaches shared config)
pop.update(deme=3).competition(carrying_capacity=8000)
# Batch per-deme modification (same API as build time)
from natal.spatial.configurator import batch_setting
pop.update().competition(
carrying_capacity=batch_setting([100, 200, 300, 400])
)
Source code in src/natal/spatial/population.py
update_deme
Get a Configurator for a specific deme with clone-on-write.
Source code in src/natal/spatial/population.py
get_history
Return the recorded history as a stacked 2D array.
Each row is (tick, ind_all.ravel(), sperm_all.ravel()).
Returns a zero-row array if no history has been recorded.
Source code in src/natal/spatial/population.py
clear_history
set_observations
Register observation groups and immediately compile the binary mask.
Similar to BasePopulation.set_observations but also builds the
per-group deme selector from optional "deme" keys in each spec.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
groups
|
Any
|
Observation groups (dict of name -> spec, list of specs, or None for one-group-per-genotype). |
required |
collapse_age
|
bool
|
Whether to collapse the age axis during projection. |
False
|
Source code in src/natal/spatial/population.py
set_hook
set_hook(event_name: str, func: Callable[..., None], hook_id: Optional[int] = None, hook_name: Optional[str] = None, compile: bool = True, deme_selector: Optional[DemeSelector] = None) -> None
Register an event hook for selected demes.
If the function carries @hook(deme=...) metadata and no explicit
deme_selector is given, the metadata value is used automatically
— you don't need to repeat the selector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_name
|
str
|
Event name (must exist in ALLOWED_EVENTS). |
required |
func
|
Callable[..., None]
|
Callback function. |
required |
hook_id
|
Optional[int]
|
Numeric execution priority (optional, auto-assigned if omitted). |
None
|
hook_name
|
Optional[str]
|
Optional human-readable name for debugging. |
None
|
compile
|
bool
|
Whether to try compiling @hook-decorated functions. |
True
|
deme_selector
|
Optional[DemeSelector]
|
Optional deme selector. If omitted, reads from
|
None
|
Note
This API is a spatial convenience entrypoint. deme_selector is
interpreted here to choose target demes, then forwarded hooks are
registered on selected demes with panmictic selector semantics.
Compiler-level selector fields are transport-only metadata.
Source code in src/natal/spatial/population.py
remove_hook
Remove a specific hook from all demes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_name
|
str
|
Event name. |
required |
hook_id
|
int
|
Hook ID. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if removed successfully from all demes, otherwise False. |
Note
Hook removal follows the same consistency rule as registration: mutate each deme first, then rebuild the aggregate compiled hooks.
Source code in src/natal/spatial/population.py
trigger_event
Trigger an event and execute all registered hooks for a specific deme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event_name
|
str
|
Event name to trigger. |
required |
deme_id
|
int
|
Deme ID (default: 0). |
0
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
RESULT_CONTINUE (0) to continue, RESULT_STOP (1) to stop. |
Source code in src/natal/spatial/population.py
get_total_count
get_female_count
get_male_count
reset
Reset all demes and synchronize the container tick.
This resets each underlying deme using its own reset logic and then updates the spatial container tick to match the demes.
Source code in src/natal/spatial/population.py
aggregate_individual_count
Return the total individual-count tensor summed over all demes.
Source code in src/natal/spatial/population.py
aggregate_state
Build one aggregate state for global summaries across all demes.
Source code in src/natal/spatial/population.py
compute_allele_frequencies
Compute allele frequencies from the aggregate multi-deme state.
Source code in src/natal/spatial/population.py
migration_row
Return normalized outbound migration weights for one source deme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_idx
|
int
|
Source deme index. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[float64]
|
A dense float64 vector of length |
NDArray[float64]
|
from |
Source code in src/natal/spatial/population.py
get_compiled_hooks
Get compiled hook descriptors, optionally filtered by event.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
Optional[str]
|
Optional event name to filter by. |
None
|
Returns:
| Type | Description |
|---|---|
list[Any]
|
List of |
Source code in src/natal/spatial/population.py
run_tick
Run one spatial tick via the spatial kernel.
Returns:
| Type | Description |
|---|---|
SpatialPopulation
|
This spatial population instance after in-place state update. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If any deme has already finished. |
Source code in src/natal/spatial/population.py
run
run(n_steps: int, record_every: int = 1, finish: bool = False, clear_history_on_start: bool = False) -> SpatialPopulation
Run multiple spatial ticks via the spatial kernel.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_steps
|
int
|
Number of ticks to execute. |
required |
record_every
|
int
|
History recording interval forwarded to the compiled spatial kernel. |
1
|
finish
|
bool
|
Whether to mark all demes finished when the run completes without an early stop event. |
False
|
clear_history_on_start
|
bool
|
Whether to clear existing history before appending new snapshots. |
False
|
Returns:
| Type | Description |
|---|---|
SpatialPopulation
|
This spatial population instance after in-place state update. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
RuntimeError
|
If any deme has already finished. |
Source code in src/natal/spatial/population.py
GridTopology
dataclass
Base topology over a 2D grid of demes.
Attributes:
| Name | Type | Description |
|---|---|---|
rows |
int
|
Number of grid rows. |
cols |
int
|
Number of grid columns. |
wrap |
bool
|
Whether coordinates use periodic boundary conditions.
When |
Examples:
With wrap=False, out-of-bounds coordinates are rejected::
grid = SquareGrid(rows=3, cols=3, wrap=False)
grid.normalize_coord(-1, 1) is None
With wrap=True, opposite edges are connected::
grid = SquareGrid(rows=3, cols=3, wrap=True)
grid.normalize_coord(-1, 1) == (2, 1)
grid.normalize_coord(1, -1) == (1, 2)
to_index
Convert one grid coordinate to a flattened deme index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Grid coordinate as |
required |
Returns:
| Type | Description |
|---|---|
int
|
Flattened deme index in row-major order. |
Source code in src/natal/spatial/topology.py
from_index
Convert one flattened deme index to a grid coordinate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Flattened deme index in row-major order. |
required |
Returns:
| Type | Description |
|---|---|
Coord
|
Grid coordinate as |
Source code in src/natal/spatial/topology.py
normalize_coord
Normalize one coordinate according to the topology boundary rule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
int
|
Candidate row index. |
required |
col
|
int
|
Candidate column index. |
required |
Returns:
| Type | Description |
|---|---|
Coord | None
|
A valid grid coordinate if the location is in bounds or can be |
Coord | None
|
wrapped. Returns |
Coord | None
|
non-wrapping topology. With |
Coord | None
|
|
Source code in src/natal/spatial/topology.py
neighbor_coords
Return neighboring grid coordinates for one source coordinate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Source grid coordinate. |
required |
Returns:
| Type | Description |
|---|---|
List[Coord]
|
Neighbor coordinates in the topology-specific neighborhood order. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If a subclass does not implement neighbor generation. |
Source code in src/natal/spatial/topology.py
to_xy
Map one grid coordinate to a geometric embedding coordinate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Source grid coordinate. |
required |
Returns:
| Type | Description |
|---|---|
Tuple[float, float]
|
Cartesian coordinate used for geometry-aware utilities. |
Source code in src/natal/spatial/topology.py
neighbor_vectors
Return geometric displacement vectors from one coord to neighbors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Source grid coordinate. |
required |
Returns:
| Type | Description |
|---|---|
List[Tuple[float, float]]
|
Displacement vectors from |
List[Tuple[float, float]]
|
in embedding space. |
Source code in src/natal/spatial/topology.py
offset_dist_sq
Squared distance between grid coords offset by (dr, dc).
Uses the law of cosines with _COS_OPPOSITE_ANGLE::
dist² = dr² + dc² - 2·dr·dc·cos(θ)
For square grids cos(90°) = 0 → Cartesian distance. Subclasses
override the class attribute _COS_OPPOSITE_ANGLE to change the
metric (e.g. hex grids set cos(120°) = -0.5).
Source code in src/natal/spatial/topology.py
neighbors
Return neighboring deme indices for one flattened deme index.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
index
|
int
|
Source deme index in row-major order. |
required |
Returns:
| Type | Description |
|---|---|
List[int]
|
Neighbor deme indices in the same order as |
Source code in src/natal/spatial/topology.py
HeterogeneousKernelParams
Bases: NamedTuple
Per-kernel offset tables for heterogeneous kernel routing.
Only populated when kernel_bank and deme_kernel_ids are both
set on the spatial population.
Attributes:
| Name | Type | Description |
|---|---|---|
deme_kernel_ids |
NDArray[int64]
|
|
d_row |
NDArray[int64]
|
|
d_col |
NDArray[int64]
|
|
weights |
NDArray[float64]
|
|
nnzs |
NDArray[int64]
|
|
total_sums |
NDArray[float64]
|
|
max_nnz |
int
|
Maximum valid entries across all kernels. |
HexGrid
dataclass
Bases: GridTopology
Hex grid using parallelogram coordinates with pointy-top geometric embedding.
This implementation uses a parallelogram grid where each cell has six neighbors in a hexagonal pattern. The grid is defined using two basis vectors that form a 60-degree angle, creating a natural hexagonal topology.
Examples:
On a non-wrapping grid, a corner cell loses out-of-bounds neighbors::
grid = HexGrid(rows=3, cols=4, wrap=False)
len(grid.neighbor_coords((0, 0))) < 6
On a wrapping grid, the same corner cell keeps six neighbors because out-of-bounds coordinates are folded back into the opposite edge::
wrapped = HexGrid(rows=3, cols=4, wrap=True)
len(wrapped.neighbor_coords((0, 0))) == 6
to_xy
Map parallelogram coordinate to pointy-top Cartesian coordinates.
Positive x points right, positive y points downward. With side length = 1, adjacent hex centers are distance 1 apart.
Source code in src/natal/spatial/topology.py
neighbor_direction_vectors
Canonical geometric neighbor vectors for pointy-top hexes.
Includes the right-down vector (1/2, sqrt(3)/2).
Source code in src/natal/spatial/topology.py
neighbor_coords
Return neighboring coordinates for one hex-grid location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Source grid coordinate in parallelogram form. |
required |
Returns:
| Type | Description |
|---|---|
List[Coord]
|
Neighbor coordinates after boundary normalization. |
Examples:
A center cell has six neighbors. Under periodic boundaries, edge cells also keep six neighbors because wrapped coordinates are folded back into the valid row/col range::
HexGrid(rows=4, cols=4, wrap=False).neighbor_coords((1, 1))
HexGrid(rows=4, cols=4, wrap=True).neighbor_coords((0, 0))
Source code in src/natal/spatial/topology.py
MigrationParams
Bases: NamedTuple
Fixed migration configuration for spatial kernels.
All fields are determined at construction time and never change during a simulation.
Attributes:
| Name | Type | Description |
|---|---|---|
kernel |
NDArray[float64]
|
Migration kernel weight matrix. A |
include_center |
bool
|
Whether the kernel centre contributes outbound mass. |
rate |
NDArray[float64]
|
Fraction of each deme that migrates per tick. |
adjust_on_edge |
bool
|
Whether boundary demes normalise to match internal total outbound rate. |
adjacency |
NDArray[float64]
|
Dense |
mode_code |
int
|
Backend selector ( |
SpatialTopology
Bases: NamedTuple
Resolved topology dimensions for kernel routing.
Built once from GridTopology at construction time. When no
topology is set, all fields default to zero / False.
Attributes:
| Name | Type | Description |
|---|---|---|
rows |
int
|
Number of grid rows (0 if no topology). |
cols |
int
|
Number of grid columns (0 if no topology). |
wrap |
bool
|
Whether periodic boundary conditions apply. |
SquareGrid
dataclass
SquareGrid(rows: int, cols: int, wrap: bool = False, COS_OPPOSITE_ANGLE: float = 0.0, neighborhood: Literal['von_neumann', 'moore'] = 'moore')
Bases: GridTopology
Square grid with Von Neumann or Moore neighborhood.
Attributes:
| Name | Type | Description |
|---|---|---|
neighborhood |
Literal['von_neumann', 'moore']
|
Neighborhood rule used to enumerate adjacent demes. |
wrap |
bool
|
Inherited periodic-boundary flag. When enabled, neighbors that would cross one edge of the rectangular grid re-enter from the opposite edge. |
Examples:
Compare neighbors of the corner cell (0, 0)::
grid = SquareGrid(rows=3, cols=3, neighborhood="von_neumann", wrap=False)
grid.neighbor_coords((0, 0)) == [(1, 0), (0, 1)]
wrapped = SquareGrid(rows=3, cols=3, neighborhood="von_neumann", wrap=True)
wrapped.neighbor_coords((0, 0)) == [(2, 0), (1, 0), (0, 2), (0, 1)]
neighbor_coords
Return neighboring coordinates for one square-grid location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coord
|
Coord
|
Source grid coordinate. |
required |
Returns:
| Type | Description |
|---|---|
List[Coord]
|
Neighbor coordinates after applying boundary normalization. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/natal/spatial/topology.py
batch_setting
batch_setting(values: Union[Sequence[Any], NDArray[floating[Any]], Callable[..., float], BatchSetting]) -> BatchSetting
Create a BatchSetting for per-deme parameter specification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
values
|
Union[Sequence[Any], NDArray[floating[Any]], Callable[..., float], BatchSetting]
|
One of:
- A list/tuple of scalars of length |
required |
Returns:
| Type | Description |
|---|---|
BatchSetting
|
A |
BatchSetting
|
expands at build time. |
Source code in src/natal/spatial/configurator.py
build_adjacency_matrix
build_adjacency_matrix(topology: GridTopology, include_self: bool = False, row_normalize: bool = False) -> np.ndarray
Build an adjacency matrix from one topology.
The returned matrix uses the convention A[i, j] = weight from source
deme i to destination deme j.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
topology
|
GridTopology
|
Grid topology used to define neighborhood relations. |
required |
include_self
|
bool
|
Whether each deme should include itself as an outbound target. |
False
|
row_normalize
|
bool
|
Whether to normalize each non-zero row to sum to 1. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
A dense adjacency matrix with shape |
Source code in src/natal/spatial/topology.py
build_gaussian_kernel
build_gaussian_kernel(topology_cls: Literal['square', 'hex'] | type[GridTopology] = 'hex', size: int = 5, sigma: float | None = None, mean_dispersal: float | None = None) -> np.ndarray
Build a normalized Gaussian migration kernel for a grid topology.
The kernel is a (size, size) matrix where each entry [r, c] is
the outbound migration weight from a virtual centre cell to the grid
cell at offset (r - centre, c - centre). Distances are computed
via the topology's COS_OPPOSITE_ANGLE, so the correct metric is
used for square grids (Cartesian) vs hex grids (oblique /
law-of-cosines).
At runtime the spatial migration kernel slides this matrix over every source deme and re-normalises valid destinations at boundaries.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
topology_cls
|
Literal['square', 'hex'] | type[GridTopology]
|
Grid topology class (e.g. |
'hex'
|
size
|
int
|
Odd integer kernel size (default 5). Larger kernels capture longer-range dispersal but increase the non-zero offset table. |
5
|
sigma
|
float | None
|
Gaussian width parameter. Defaults to 1.0 when neither
|
None
|
mean_dispersal
|
float | None
|
Target mean dispersal distance. When provided,
Mutually exclusive with |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
Normalised |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
Via sigma::
kernel = build_gaussian_kernel("hex", size=11, sigma=1.5)
Via mean dispersal::
kernel = build_gaussian_kernel("hex", size=11, mean_dispersal=2.0)
Source code in src/natal/spatial/topology.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 | |