Population Model (Panmictic)
The Population class is the core component of NATAL Core, responsible for managing the genetic state and simulation process of the population.
Note: This chapter and Population Initialization cover the panmictic (single deme, well-mixed) population model. For building multi-deme spatial populations, configuring migration topology, or heterogeneous deme parameters, please refer to Part 3 -- Spatial Simulation Guide.
Population Types
NATAL Core provides two main population types:
Discrete Generation Population
DiscreteGenerationPopulation is suitable for species with non-overlapping generations, where each generation completely replaces the previous one. The simulation process is simple and efficient.
Age-Structured Population
AgeStructuredPopulation is suitable for species with overlapping generations, supporting age-dependent survival and fecundity, and configurable sperm storage mechanisms.
Both population types are subclasses of
BasePopulationand share most methods.
Creating a Population
It is recommended to create populations through the chainable API. See Population Initialization for details.
import natal as nt
# Create an age-structured population
pop = (
nt.AgeStructuredPopulation.setup(species)
.name("MyExperiment")
.age_structure(n_ages=8)
.initial_state({"WT|WT": 1000})
.build()
)
# Create a discrete generation population
pop = (
nt.DiscreteGenerationPopulation.setup(species)
.name("DiscreteExp")
.initial_state({"WT|WT": 500})
.build()
)
Running Simulations
Single-Step Simulation
# Simulate one step (one time unit)
pop.run_tick()
# Simulate multiple steps, printing state after each step
for _ in range(100):
pop.run_tick()
print(pop.output_current_state())
Batch Simulation
Accessing Population State
Current State Information
# Population size
current_size = pop.total_population_size
print(f"Current population size: {current_size}")
# Female count
female_count = pop.total_females
print(f"Female count: {female_count}")
# Male count
male_count = pop.total_males
print(f"Male count: {male_count}")
# Sex ratio
ratio = pop.sex_ratio
print(f"Sex ratio (female/male): {ratio}")
# Current time step
current_tick = pop.tick
print(f"Current tick: {current_tick}")
Allele Frequencies
# Compute allele frequencies
allele_freqs = pop.compute_allele_frequencies()
print("Allele frequencies:", allele_freqs)
# Get specific allele frequency
drive_freq = allele_freqs.get("D", 0.0)
print(f"Drive allele frequency: {drive_freq}")
History Recording System
History Configuration
The population object has built-in history recording functionality, with configurable recording frequency and storage format:
# Configure history recording
pop.record_every = 10 # Record every 10 steps
pop.max_history = 1000 # Maximum of 1000 snapshots
# Run simulation with history recording
results = pop.run(n_steps=500, record_every=5)
History Data Access
# Get complete history
full_history = pop.output_history()
print("Number of history records:", len(full_history["snapshots"]))
print("Last step data:", full_history["snapshots"][-1])
# Get history at a specific tick
history_at_tick_100 = pop.output_history(tick=100)
print("State at tick 100:", history_at_tick_100)
# Get list of recorded time steps
ticks = [snapshot["tick"] for snapshot in full_history["snapshots"]]
print("Recorded ticks:", ticks)
History Management
# Clear history to save memory
pop.clear_history()
# Restart recording
results = pop.run(n_steps=100, record_every=5)
Output Functions
Current State Output
# Get detailed snapshot of current state
current_state = pop.output_current_state()
print("Current state:", current_state)
# Get readable dictionary format
readable_state = pop.output_current_state(as_dict=True)
print("Readable state:", readable_state)
# Get JSON format (for transport and storage)
json_state = pop.output_current_state(as_json=True)
print("JSON state:", json_state[:200]) # Show first 200 characters
Integration with Observation Rules
Combined with observation rules, specific subpopulation data can be extracted from the population state. For detailed methods, see Extracting Population Simulation Data.
# Create observation rules
observation = pop.create_observation(
groups={
"adult_wt": {"genotype": ["WT|WT"], "age": [1]},
"drive_carriers": {"genotype": ["WT|Drive", "Drive|Drive"]}
},
collapse_age=False,
)
# Get current state with observation rules
current = pop.output_current_state(observation=observation)
print("Current observation data:", current["observed"])
# Get history data with observation rules
history = pop.output_history(observation=observation)
print("History observation data:", history["observed"])
Reset and Restart
# Reset to initial state
pop.reset()
# Re-simulate after reset
pop.reset()
results = pop.run(n_steps=50)