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

  1. Spatial Agents (``GridAgent``): Agents now inherit from GridAgent instead of Agent. This gives them x and y attributes and built-in methods for movement.

    • Category: The set_category() method is required for GridAgent. 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).

  2. The Grid and Spots: We define a CovidGrid and CovidSpot. - Each Spot represents a cell on the grid and can hold properties (e.g., stay_prob). - The Grid manages these spots and agent positions.

  3. Movement Logic: Agents decide whether to move based on the stay_prob of their current spot. If they move, they choose a random neighbor cell.

  4. 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.csv is loaded to set the stay_prob for 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)