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.R
- Population 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,- active
- Dynamic 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.
