Source code for laser_measles.abm.components.process_constant_pop
"""
Component defining the ConstantPopProcess, which handles the birth events in a model with constant population - that is, births == deaths.
"""
import numpy as np
from laser_measles.abm.model import ABMModel
from laser_measles.components import BaseConstantPopParams
from laser_measles.components import BaseConstantPopProcess
from laser_measles.utils import cast_type
class ConstantPopParams(BaseConstantPopParams):
pass
[docs]
class ConstantPopProcess(BaseConstantPopProcess):
"""
A component to handle the birth events in a model with constant population - that is, births == deaths.
Attributes:
model: The model instance containing population and parameters.
verbose (bool): Flag to enable verbose output. Default is False.
initializers (list): List of initializers to be called on birth events.
metrics (DataFrame): DataFrame to holding timing metrics for initializers.
"""
def __init__(self, model: ABMModel, verbose: bool = False, params: ConstantPopParams | None = None):
"""
Initialize the Births component.
Parameters:
model (object): The model object which must have a `population` attribute.
verbose (bool, optional): If True, enables verbose output. Defaults to False.
params (BirthsParams, optional): Component parameters. If None, uses model.params.
"""
super().__init__(model, verbose)
self.params = params if params is not None else ConstantPopParams()
# re-initialize people frame with correct capacity
capacity = self.calculate_capacity(model=model)
model.initialize_people_capacity(capacity=int(capacity), initial_count=model.scenario["pop"].sum())
model.people.add_scalar_property("date_of_birth", dtype=np.int32, default=model.params.num_ticks + 1)
model.patches.add_scalar_property("births", dtype=np.uint32)
return
def __call__(self, model, tick) -> None:
"""
Adds new agents to each patch based on expected daily births calculated from CBR. Calls each of the registered initializers for the newborns.
Args:
model: The simulation model containing patches, population, and parameters.
tick: The current time step in the simulation.
Returns:
None
This method performs the following steps:
1. Draw a random set of indices, or size size "number of births" from the population,
"""
if self.lambda_birth == 0:
return
patches = model.patches
people = model.people
populations = patches.states.sum(axis=0)
# When we get to having birth rate per node, will need to be more clever here, but with constant birth rate across nodes,
# random selection will be population proportional. If node id is not contiguous, could be tricky?
births = model.prng.poisson(lam=populations * self.lambda_birth, size=populations.shape)
idx = model.prng.choice(populations.sum(), size=births.sum(), replace=False)
# Get number of deaths per patch per state
num_states = len(model.params.states)
num_patches = len(patches)
deaths = np.bincount(people.state[idx] * num_patches + people.patch_id[idx], minlength=num_patches * num_states)
deaths = deaths.reshape((num_states, num_patches))
# update state counters
patches.states -= cast_type(deaths, patches.states.dtype)
patches.states.S += cast_type(births, patches.states.dtype)
# Births, set date of birth and state to 0 (susceptible)
people.date_of_birth[idx] = tick # set to current tick
people.state[idx] = model.params.states.index("S") # set to susceptible
def _initialize(self, model: ABMModel) -> None:
"""
Simple initializer for ages where birth rate = mortality rate
Args:
model: The ABM model instance to initialize
"""
people = model.people
# Simple initializer for ages where birth rate = mortality rate:
# Initialize ages for existing population
if self.mu_death > 0:
people.date_of_birth[0 : people.count] = cast_type(
-1 * model.prng.exponential(1 / self.mu_death, people.count), people.date_of_birth.dtype
)