-
Notifications
You must be signed in to change notification settings - Fork 5
Modeling (Non‐Disease) Mortality ‐‐ An Overview of Options
Because LASER is so flexible, it doesn't dictate to the end user how to model things like natural mortality, except to say that you should not remove dead agents from the main memory space as they die (KM: why not? In sufficiently large, or long-running, models, that may become the size-limiting factor), and you should not do things which are slow. Within those parameters, there are numerous options and it's worth noting what they are, and showing how LASER can support them.
This is a good list from ChatGPT.
It might be worth adding here that in most of the solutions below, the agent's death day is pre-calculated at startup. One somewhat natural optimization we can do from there, if we know how long our simulation is going to run, is to "discard" all those deaths which aren't going to occur during the lifetime of the simulation we are about to run. Whether this ends up being worth it, in each case, is left for the modeler to worry about.
Several of the solutions outlined below do not fall into the realm of suitability for LASER but are retained here, and noted as such, for completeness.
Here's a comprehensive list of distinct ways to model mortality in your agent-based simulation using properties like age or date of birth, along with a Kaplan-Meier estimator for assigning lifespans:
Attributes: age, lifespan
Mechanism: Each agent is assigned an initial age and a lifespan based on the Kaplan-Meier estimator. Mortality is checked by comparing age to lifespan at each timestep. One can optionally set an "active" or "alive" type boolean flag which can be checked by other components, or one can just keep comparing age with lifespan where components care. If one wants to keep a count of new deaths, one probably needs to flip an alive bit upon death. Note that the aging and killing components are probably best merged into one.
Pros: Simple and intuitive.
Cons: Requires updating age every timestep.
Conclusion: This is basically Jonathan's default approach.
Attributes: date_of_birth, lifespan
Mechanism: Each agent is assigned a date_of_birth (based on simulation start time) and a lifespan. Death occurs when the simulation tick reaches date_of_birth + lifespan.
Pros: Avoids explicitly storing age; uses ticks directly.
Cons: Requires recalculating current_age when needed.
Attributes: time_to_death
Mechanism: Assign each agent a time_to_death at initialization, calculated from Kaplan-Meier. Agents are added to a priority queue sorted by time_to_death. Check the queue each timestep to handle deaths.
Pros: Efficient for large populations; minimizes per-agent updates.
Cons: Adds overhead for maintaining a priority queue.
Conclusion: This is basically the built-in laser-measles approach.
Attributes: age, lifespan
Mechanism: Each agent has an age and lifespan. Upon initialization, schedule a "death event" in a global event queue for current_tick + (lifespan - age). Deaths are processed when their event triggers.
Pros: Combines age tracking and event scheduling; efficient for sparsely occurring deaths.
Cons: Requires maintaining a global event queue.
Conclusion: This seems similar to #3 but less efficient. No plans to consider this.
Attributes: age, mortality_rate
Mechanism: Assign a mortality_rate based on age (using Kaplan-Meier survival probabilities). At each timestep, roll a random number to determine if the agent dies.
Pros: Allows dynamic mortality rates that change with age or environment.
Cons: Stochastic, which can lead to variability in results.
Conclusion: This is basically what EMOD historically does.
Attributes: age, lifespan--
Mechanism: Precompute a survival table for the population, mapping each age to a Kaplan-Meier-derived probability. Assign each agent a lifespan based on the table. Death occurs when age exceeds lifespan.
Pros: Simplifies lifespan assignment; avoids per-agent Kaplan-Meier computation.
Cons: Requires sufficient memory for the table.
Conclusion: I don't really see the case for this.
Attributes: remaining_lifespan
Mechanism: Assign each agent an initial remaining_lifespan based on Kaplan-Meier. Decrease remaining_lifespan by one each timestep. Death occurs when it reaches zero.
Pros: Straightforward implementation; avoids recalculating age. Countdown timers are a staple of LASER.
Cons: Requires updating this property for every agent every timestep.
Conclusion: I consider this a variation on #3 and a very LASER-ish option. If the end-user is comfortable with PriorityQueues, go with #3. If the user just wants countdown timers, this approach is very simple. And the user can then choose whether to do this every timestep, or at a courser granularity, like every 30 days, which is nearly always fine for mortality.
Attributes: expected_death_date (timestep)
Mechanism: Precompute an expected_death_date for each agent based on Kaplan-Meier and their date_of_birth. Death occurs when the simulation tick reaches the expected_death_date.
Pros: Avoids explicit age tracking; ties mortality directly to simulation ticks.
Cons: Assumes a deterministic lifespan, which may not capture stochastic variability.
Conclusion: Note that 1,2,7, and 8 are all just variations on the same theme, depending on how the modeler prefers to think about things, prefers to count up or down, and prefers to think about ages, birthdays, or simulation death days.
9-14 are unlikely to be suitable for LASER.
Attributes: age, hazard_rate
Mechanism: Assign a dynamic hazard_rate to each agent based on Kaplan-Meier. At each timestep, roll a random number to see if the agent dies (probability proportional to the hazard rate).
Pros: Allows mortality to be influenced by changing factors.
Cons: Computationally intensive for large populations.
Conclusion: Very unlikely that we'll want this for a LASER solution, which is "light agent" and needs to be fast for very large populations.
Attributes: lifespan_bucket, age
Mechanism: Divide agents into lifespan buckets (e.g., 0-10 years, 10-20 years) based on Kaplan-Meier. At each timestep, check if an agent's bucket is expired based on their age.
Pros: Simplifies processing for groups of agents with similar lifespans.
Cons: Reduces granularity in lifespan modeling.
Attributes: age, alive
Mechanism: Assign each agent a survival curve based on Kaplan-Meier. At each timestep, use the curve to determine the probability that the agent survives to the next timestep.
Pros: Flexible and accurate; dynamically adapts to changing survival probabilities.
Cons: Computationally expensive for large populations. Thus not suitable for LASER.
Attributes: age, lifespan
Mechanism: Start each agent at death and simulate backward to birth, assigning a lifespan post hoc based on Kaplan-Meier. Adjust the simulation timeline accordingly. This seems similar to the idea of "simulate vital dynamics separately first and reuse in epi sims" idea.
Pros: Ensures equilibrium in mortality.
Cons: Unorthodox and may require significant rethinking of simulation logic.
Attributes: cohort_age, cohort_size
Mechanism: Group agents by cohorts (e.g., age bins). Assign each cohort an aggregated mortality rate based on Kaplan-Meier. Deaths occur at the cohort level rather than the individual level.
Pros: Simplifies large-scale simulations.
Cons: Loses individual-level resolution.
Attributes: base_lifespan, adjusted_lifespan
Mechanism: Assign each agent a base_lifespan from Kaplan-Meier and dynamically adjust it based on external factors (e.g., disease, accidents). Death occurs when age exceeds adjusted_lifespan.
Pros: Incorporates dynamic, external influences on mortality.
Cons: Complex to implement.
Attributes: date_of_birth
Mechanism: Constant-population demographics (births = deaths) are commonly employed in some of the canonical examples of disease models, and can be useful in long-time-horizon simulations to prevent excessive long-term growth. This imposes an extra constraint linking total births and total mortality, which in most agent-based models will be independent processes. One way to approach this is simply to maintain those independent processes with equal rates - the population will remain constant on average but will have stochastic fluctuation. However, the user may prefer that the population be strictly fixed, or the fertility or mortality models may so complex that computing these overall rates is challenging. An alternative solution is then to link the birth and death processes, such that every time an agent is born, another agent must die. As mortality is often the more complex process, it can be useful to have mortality be the independent process, and if one doesn't care about losing the information associated with dead agents, one can simply implement mortality by reinitializing the dying agent as a newborn. This is compatible with any of the specific approaches to mortality calculation described about.
Pros: Simple, useful for comparing to canonical analytic models, prevents memory issues associated with long-term exponential population growth or with memory being primarily occupied by a large population of dead agents.
Cons: State information associated with dead agents may be lost. Difficult to implement if there is complexity in both the fertility and mortality models (that said, if this is the case, then the constant-population assumption is probably poor).
Each method has trade-offs depending on the complexity of your model, the desired realism, and computational efficiency. Let me know if you'd like me to elaborate on implementing any of these!