CovidContagionGrid
This example extends the base covid_contagion model by introducing Melodie’s Grid module.
It demonstrates how to simulate agents moving and interacting in a 2D spatial environment.
Grid: Project Structure
The structure mirrors the base example but includes specific grid-related implementations:
examples/covid_contagion_grid
├── core/
│ ├── agent.py # Agents with spatial coordinates (x, y)
│ ├── grid.py # Definition of Grid and Spot classes
│ ├── environment.py # Orchestrates agent movement and infection
│ ├── model.py # Initializes the grid and places agents
│ ├── scenario.py # Loads parameters and grid-related matrices
│ └── ...
├── data/
│ ├── input/
│ │ ├── SimulatorScenarios.csv # Adds grid_x_size, grid_y_size
│ │ ├── ID_HealthState.csv # Same as base example
│ │ └── Parameter_GridStayProb.csv # 2D matrix controlling movement
│ └── output/
└── ...
Grid: Key Changes
Spatial Agents (``GridAgent``): Agents now inherit from
GridAgentinstead ofAgent. This gives themxandyattributes and built-in methods for movement.Category: The
set_category()method is required forGridAgent. It assigns an integer ID to the agent type (e.g., 0 for humans, 1 for animals). This is used by the Grid to:Efficiently manage multiple types of agents on the same map.
Return
(category, id)tuples when querying neighbors.Differentiate agents for visualization (e.g., drawing humans as circles and hospitals as crosses).
The Grid and Spots: We define a
CovidGridandCovidSpot. - Each Spot represents a cell on the grid and can hold properties (e.g.,stay_prob). - The Grid manages these spots and agent positions.Movement Logic: Agents decide whether to move based on the
stay_probof their current spot. If they move, they choose a random neighbor cell.Spatial Infection: Infection now happens locally. An infected agent searches for neighbors within a certain radius (Moore neighborhood by default) and tries to infect them.
Grid: Input Data
The CovidScenario class is updated to load spatial data:
Grid Dimensions: Loaded from
SimulatorScenarios.csv(grid_x_size,y_size).Spatial Heterogeneity: A matrix file
Parameter_GridStayProb.csvis loaded to set thestay_probfor each spot on the grid.
class CovidScenario(Scenario):
def load_data(self) -> None:
self.health_states = self.load_dataframe("ID_HealthState.csv")
# Load the 2D matrix for spot properties
self.stay_prob_matrix = self.load_matrix("Parameter_GridStayProb.csv")
Grid: Running the Model
Run the model using the provided entry script:
python examples/covid_contagion_grid/main.py
This will generate Result_Simulator_Agents.csv and Result_Simulator_Environment.csv in the data/output folder.
Grid: Code
This section shows the key code implementation for the grid model.
Model Structure
Defined in core/model.py.
1from typing import TYPE_CHECKING
2
3from Melodie import Model
4
5from .agent import CovidAgent
6from .data_collector import CovidDataCollector
7from .environment import CovidEnvironment
8from .grid import CovidGrid, CovidSpot
9from .scenario import CovidScenario
10
11if TYPE_CHECKING:
12 from Melodie import AgentList
13
14
15class CovidModel(Model):
16 """
17 The Model class assembles all components: agents, environment, grid, and data collector.
18 """
19
20 scenario: "CovidScenario"
21 agents: "AgentList[CovidAgent]"
22 environment: CovidEnvironment
23 data_collector: CovidDataCollector
24 grid: CovidGrid
25
26 def create(self) -> None:
27 """
28 Create component instances.
29 """
30 self.agents = self.create_agent_list(CovidAgent)
31 self.environment = self.create_environment(CovidEnvironment)
32 self.data_collector = self.create_data_collector(CovidDataCollector)
33 self.grid = self.create_grid(CovidGrid, CovidSpot)
34
35 def setup(self) -> None:
36 """
37 Setup the model components.
38 """
39 # 1. Initialize agents based on scenario parameter
40 self.agents.setup_agents(self.scenario.agent_num)
41
42 # 2. Setup grid dimensions and apply properties (like stay_prob)
43 self.grid.setup_params(width=self.scenario.grid_x_size, height=self.scenario.grid_y_size)
44
45 # 3. Place agents randomly on the grid
46 self.grid.setup_agent_locations(self.agents)
47
48 # 4. Seed initial infections
49 self.environment.seed_infection(self.agents)
50
51 def run(self) -> None:
52 """
53 The main simulation loop.
54 """
55 for t in self.iterator(self.scenario.period_num):
56 self.environment.agents_move(self.agents)
57 self.environment.agents_infect(self.agents)
58 self.environment.agents_recover(self.agents)
59 self.environment.update_population_stats(self.agents)
60 self.data_collector.collect(t)
61 self.data_collector.save()
Environment Logic
Defined in core/environment.py.
1import random
2from typing import TYPE_CHECKING
3
4from Melodie import Environment
5
6if TYPE_CHECKING:
7 from Melodie import AgentList
8 from .agent import CovidAgent
9 from .scenario import CovidScenario
10
11
12class CovidEnvironment(Environment):
13 """
14 The environment class orchestrates the simulation steps and manages macro-level logic.
15 """
16
17 scenario: "CovidScenario"
18
19 def setup(self) -> None:
20 """
21 Initialize macro-level counters for statistics.
22 """
23 self.num_susceptible: int = 0
24 self.num_infected: int = 0
25 self.num_recovered: int = 0
26
27 def agents_move(self, agents: "AgentList[CovidAgent]") -> None:
28 """
29 1. Agents move.
30 """
31 for agent in agents:
32 agent.move()
33
34 def agents_infect(self, agents: "AgentList[CovidAgent]") -> None:
35 """
36 2. Infected agents spread the virus.
37 """
38 for agent in agents:
39 agent.infect_neighbors(agents)
40
41 def agents_recover(self, agents: "AgentList[CovidAgent]") -> None:
42 """
43 3. Infected agents try to recover.
44 """
45 for agent in agents:
46 agent.recover()
47
48 def seed_infection(self, agents: "AgentList[CovidAgent]") -> None:
49 """
50 Infect a percentage of agents at the start of the simulation.
51 """
52 for agent in agents:
53 if random.random() < self.scenario.initial_infected_percentage:
54 agent.health_state = 1
55
56 def update_population_stats(self, agents: "AgentList[CovidAgent]") -> None:
57 """
58 Count the number of agents in each health state.
59 """
60 self.num_susceptible = 0
61 self.num_infected = 0
62 self.num_recovered = 0
63 for agent in agents:
64 if agent.health_state == 0:
65 self.num_susceptible += 1
66 elif agent.health_state == 1:
67 self.num_infected += 1
68 elif agent.health_state == 2:
69 self.num_recovered += 1
Agent Behavior
Defined in core/agent.py.
1import random
2from typing import TYPE_CHECKING
3
4from Melodie import GridAgent
5
6if TYPE_CHECKING:
7 from .grid import CovidGrid, CovidSpot
8 from .scenario import CovidScenario
9 from Melodie import AgentList
10
11
12class CovidAgent(GridAgent):
13 """
14 An agent capable of moving on a grid and spreading infection to neighbors.
15 Inherits from GridAgent to gain spatial attributes (x, y) and methods.
16 """
17
18 scenario: "CovidScenario"
19 grid: "CovidGrid"
20 spot: "CovidSpot"
21
22 def set_category(self) -> None:
23 """
24 Set the category of the agent.
25 Melodie uses this to manage different groups of agents.
26 """
27 self.category = 0
28
29 def setup(self) -> None:
30 """
31 Initialize agent attributes.
32 """
33 self.x: int = 0
34 self.y: int = 0
35 # Health state: 0 = Susceptible, 1 = Infected, 2 = Recovered
36 self.health_state: int = 0
37
38 def move(self) -> None:
39 """
40 Move the agent based on the property of the current spot.
41 The agent has a probability (1 - stay_prob) to move to a random neighboring cell.
42 """
43 current_spot = self.grid.get_spot(self.x, self.y)
44 if random.random() > current_spot.stay_prob:
45 # Move randomly within a radius of 1 cell (Moore neighborhood)
46 self.rand_move_agent(x_range=1, y_range=1)
47
48 def infect_neighbors(self, agents: "AgentList[CovidAgent]") -> None:
49 """
50 If infected, try to infect susceptible neighbors.
51 """
52 if self.health_state != 1:
53 return
54
55 # Get neighbors within a radius of 1
56 neighbors = self.grid.get_neighbors(self, radius=1)
57
58 for neighbor_id in neighbors:
59 # In Melodie, get_neighbors returns a list of agent IDs (or objects depending on config).
60 # Here we assume it returns IDs or we access the agent list directly.
61 # Actually, standard Grid.get_neighbors returns list of (agent_category, agent_id).
62 category, agent_id = neighbor_id
63 neighbor: "CovidAgent" = agents.get_agent(agent_id)
64
65 if neighbor.health_state == 0:
66 if random.random() < self.scenario.infection_prob:
67 neighbor.health_state = 1
68
69 def recover(self) -> None:
70 """
71 If infected, there is a chance to recover.
72 """
73 if self.health_state == 1 and random.random() < self.scenario.recovery_prob:
74 self.health_state = 2
Data Collection Setup
Identical to the base model. Defined in core/data_collector.py.
1from Melodie import DataCollector
2
3
4class CovidDataCollector(DataCollector):
5 """Collect micro and macro results for the grid contagion model."""
6
7 def setup(self) -> None:
8 self.add_agent_property("agents", "health_state")
9 self.add_environment_property("num_susceptible")
10 self.add_environment_property("num_infected")
11 self.add_environment_property("num_recovered")
Grid and Spot Definition
Defined in core/grid.py.
1from Melodie import Grid, Spot
2from typing import TYPE_CHECKING
3
4if TYPE_CHECKING:
5 from .scenario import CovidScenario
6
7
8class CovidSpot(Spot):
9 """
10 Represents a single cell on the grid.
11 Can hold properties that affect agent behavior, like 'stay_prob'.
12 """
13
14 def setup(self) -> None:
15 self.stay_prob: float = 0.0
16
17
18class CovidGrid(Grid):
19 """
20 Manages the 2D space, spots, and agent positions.
21 """
22
23 scenario: "CovidScenario"
24
25 def setup(self) -> None:
26 """
27 Configure the grid.
28 Here we apply the 'stay_prob' matrix loaded in the scenario to the spots.
29 """
30 # set_spot_property loads a 2D numpy array or list of lists into the grid spots
31 self.set_spot_property("stay_prob", self.scenario.stay_prob_matrix)