Model structure#
This tutorial compares the structure of compartmental and agent-based models, focusing on their LaserFrame data structures and how they operate.
Overview#
laser-measles provides two primary modeling approaches:
Compartmental Model: Population-level SEIR dynamics using aggregated patch data
ABM Model: Individual-level simulation with stochastic agents
The key difference lies in their data organization and LaserFrame structures.
Patches#
Patches exist for both the compartmental and ABM models and track the spatial data and aggregates in the model. The patches use a BasePatchLaserFrame (or child class) for population-level aggregates:
[1]:
import polars as pl
from laser_measles.compartmental import CompartmentalModel
from laser_measles.compartmental.components import CaseSurveillanceParams
from laser_measles.compartmental.components import CaseSurveillanceTracker
from laser_measles.compartmental.params import CompartmentalParams
# Create a simple scenario
scenario = pl.DataFrame(
{"id": ["1", "2", "3"], "pop": [1000, 2000, 1500], "lat": [40.0, 41.0, 42.0], "lon": [-74.0, -73.0, -72.0], "mcv1": [0.0, 0.0, 0.0]}
)
# Initialize compartmental model
params = CompartmentalParams(num_ticks=100)
comp_model = CompartmentalModel(scenario, params)
# Examine the patch structure
print("Compartmental model patches:")
print(f"Shape: {comp_model.patches.states.shape}")
print(f"State names: {comp_model.patches.states.state_names}")
print(f"Initial S compartment: {comp_model.patches.states.S}")
print(f"Total population: {comp_model.patches.states.S.sum()}")
# You can also print the model to get some info:
print("Compartmental model 'out of the box':")
print(comp_model)
# Create a CaseSurveillanceTracker to monitor infections
case_tracker = lm.create_component(
CaseSurveillanceTracker,
CaseSurveillanceParams(detection_rate=1.0), # 100% detection for accurate infection counting
)
# Add transmission and surveillance to the model
from laser_measles.compartmental.components import TransmissionProcess
comp_model.add_component(TransmissionProcess)
comp_model.add_component(case_tracker)
print("\nCompartmental model with surveillance:")
print(comp_model)
# Run the simulation
comp_model.run()
# Access infection data
case_tracker_instance = comp_model.get_instance(CaseSurveillanceTracker)[0]
comp_infections_df = case_tracker_instance.get_dataframe()
print(f"\nCompartmental model total infections: {comp_infections_df['cases'].sum()}")
Compartmental model patches:
Shape: (4, 3)
State names: ['S', 'E', 'I', 'R']
Initial S compartment: [1000 2000 1500]
Total population: 4500
Compartmental model 'out of the box':
<CompartmentalModel>:
name: compartmental
patches:
┌─ PatchLaserFrame (capacity: 3, count: 3) ───────┐
└─ states uint32 (4, 3)
└─────────────────────────────────────────────────┘>
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[1], line 29
26 print(comp_model)
28 # Create a CaseSurveillanceTracker to monitor infections
---> 29 case_tracker = lm.create_component(
30 CaseSurveillanceTracker,
31 CaseSurveillanceParams(detection_rate=1.0), # 100% detection for accurate infection counting
32 )
34 # Add transmission and surveillance to the model
35 from laser_measles.compartmental.components import TransmissionProcess
NameError: name 'lm' is not defined
Key Features of patches (e.g., BasePatchLaserFrame):#
``states`` property: StateArray with shape
(num_states, num_patches)Attribute access:
states.S,states.E,states.I,states.RPopulation aggregates: Each patch contains total counts by disease state
Spatial organization: Patches represent geographic locations
People#
In addition to a patch, the ABM uses people (e.g., BasePeopleLaserFrame) for individual agents:
[2]:
import laser_measles as lm
from laser_measles.abm import ABMModel
from laser_measles.abm.components import TransmissionProcess
from laser_measles.abm.params import ABMParams
# Initialize ABM model
abm_params = ABMParams(num_ticks=100)
abm_model = ABMModel(scenario, abm_params)
# Examine the model
print("ABM model 'out of the box':")
print(abm_model)
# Now what if add a transmission?
abm_model.add_component(TransmissionProcess)
print("ABM model after adding transmission:")
print(abm_model)
# Add CaseSurveillanceTracker to ABM model
abm_case_tracker = lm.create_component(
lm.abm.components.CaseSurveillanceTracker, lm.abm.components.CaseSurveillanceParams(detection_rate=1.0)
)
abm_model.add_component(abm_case_tracker)
print("\nABM model with surveillance:")
print(abm_model)
# Run the simulation
abm_model.run()
# Access infection data
abm_case_tracker_instance = abm_model.get_instance(lm.abm.components.CaseSurveillanceTracker)[0]
abm_infections_df = abm_case_tracker_instance.get_dataframe()
print(f"\nABM model total infections: {abm_infections_df['cases'].sum()}")
ABM model 'out of the box':
<ABMModel>:
name: abm
patches:
┌─ PatchLaserFrame (capacity: 3, count: 3) ───────┐
└─ states uint32 (4, 3)
└─────────────────────────────────────────────────┘
people:
┌─ PeopleLaserFrame (capacity: 1, count: 1) ──────┐
├─ patch_id uint16 (1,)
├─ state uint8 (1,)
└─ susceptibility float32 (1,)
└─────────────────────────────────────────────────┘>
ABM model after adding transmission:
<ABMModel>:
name: abm
patches:
┌─ PatchLaserFrame (capacity: 3, count: 3) ───────┐
├─ incidence uint32 (3,)
└─ states uint32 (4, 3)
└─────────────────────────────────────────────────┘
people:
┌─ PeopleLaserFrame (capacity: 1, count: 1) ──────┐
├─ etimer uint16 (1,)
├─ itimer uint16 (1,)
├─ patch_id uint16 (1,)
├─ state uint8 (1,)
└─ susceptibility float32 (1,)
└─────────────────────────────────────────────────┘>
ABM model with surveillance:
<ABMModel>:
name: abm
patches:
┌─ PatchLaserFrame (capacity: 3, count: 3) ───────┐
├─ incidence uint32 (3,)
└─ states uint32 (4, 3)
└─────────────────────────────────────────────────┘
people:
┌─ PeopleLaserFrame (capacity: 1, count: 1) ──────┐
├─ etimer uint16 (1,)
├─ itimer uint16 (1,)
├─ patch_id uint16 (1,)
├─ state uint8 (1,)
└─ susceptibility float32 (1,)
└─────────────────────────────────────────────────┘>
|████████████████████████████████████████| 100/100 [100%] in 0.0s (10092.64/s)
ABM model total infections: 0
Key Features of BasePeopleLaserFrame:#
Individual agents: Each row represents one person
Agent properties:
patch_id,state,susceptibility,activeDynamic capacity: Can grow/shrink as agents are born/die
Stochastic processes: Each agent processed individually
Key Differences#
Aspect |
Compartmental |
ABM |
|---|---|---|
Data Structure |
|
|
Population Storage |
Aggregated counts by patch |
Individual agents |
State Representation |
|
|
Spatial Organization |
Patch-level mixing matrix |
Agent patch assignment |
Transitions |
Binomial sampling |
Individual stochastic events |
Performance |
Faster (fewer calculations) |
Slower (more detailed) |
Memory Usage |
Lower (aggregates) |
Higher (individual records) |
When to Use Each Model#
Use a Patches model only (e.g., Compartmental Model) when:
Analyzing population-level dynamics
Running many scenarios quickly
Interested in aggregate outcomes
Working with large populations
Use a Patches+People Model (e.g., ABM Model) when:
Modeling individual heterogeneity
Studying contact networks
Tracking individual histories
Need detailed stochastic processes
Both models share the same component architecture and can use similar initialization and analysis tools, making it easy to switch between approaches.