CovidGridContagion

To show how the Melodie.Grid module can be used, we provide this CovidGridContagion model, which is based the CovidContagion model. So, if you haven’t, we will strongly suggest to read the Tutorial section first.

The differences are:

  • Agents walk on a 2D grid randomly.

  • The grid is constructed with spots. Each spot has an attribute stay_prob, which decides if the agent standing on this spot will move probabilistically.

  • The infected agents can pass the virus to the other uninfected agents in the neighborhood.

Project Structure

The project structure is as below. Compared with the structure of the CovidContagion model, one grid.py file is added in the source folder, and one Parameter_GridStayProb.xlsx file is added in the data/input folder.

CovidGridContagion
├── data
│   ├── input
│   │   ├── SimulatorScenarios.xlsx
│   │   ├── ID_HealthState.xlsx
│   │   ├── ID_AgeGroup.xlsx
│   │   ├── Parameter_GridStayProb.xlsx
│   │   └── Parameter_AgeGroup_TransitionProb.xlsx
│   └── output
│       ├── CovidGridContagion.sqlite
│       ├── PopulationInfection_S0R0.png
│       └── PopulationInfection_S1R0.png
├── source
│   ├── agent.py
│   ├── environment.py
│   ├── grid.py
│   ├── data_collector.py
│   ├── data_info.py
│   ├── data_loader.py
│   ├── scenario.py
│   ├── model.py
│   └── analyzer.py
├── config.py
├── run_simulator.py
├── run_analyzer.py
└── readme.md

Grid and Spot

To include such differences about grid, Melodie provides two classes Grid and Spot, based on which the CovidGrid and CovidSpot classes are defined.

grid.py
 1from Melodie import Spot, Grid
 2from source import data_info
 3
 4
 5class CovidSpot(Spot):
 6
 7    def setup(self):
 8        self.stay_prob = 0.0
 9
10
11class CovidGrid(Grid):
12
13    def setup(self):
14        self.set_spot_property(
15            "stay_prob", self.scenario.get_matrix(data_info.grid_stay_prob)
16        )

As shown, the two classes are defined in brief, i.e., most functions and attributes are inherited from Melodie. The only reason to define this CovidGrid class for the model, is to include the stay_prob attribute of each CovidSpot. Or, one can also use the Grid and Spot classes of Melodie directly.

Matrix Data

The stay_prob values of the spots are saved in a matrix, which is the same size with the grid. Like other dataframes, the matrix is first registered in the data_info.py and loaded by the data_loader, then can be accessed by the scenario object by using get_matrix function (Line 15). The registry of the matrix is as follows.

data_info.py
 1import sqlalchemy
 2
 3from Melodie import MatrixInfo
 4
 5
 6grid_stay_prob = MatrixInfo(
 7    mat_name="grid_stay_prob",
 8    data_type=sqlalchemy.Float(),
 9    file_name="Parameter_GridStayProb.xlsx",
10)

Model

With the classes and data, the next step is to include the grid object as a new component of the model. As shown in Line 24, Melodie.Model provides a create_grid function, taking the two class variables as input - CovidGrid and CovidSpot.

model.py
 1from typing import TYPE_CHECKING
 2
 3from Melodie import Model
 4
 5from source import data_info
 6from source.agent import CovidAgent
 7from source.data_collector import CovidDataCollector
 8from source.environment import CovidEnvironment
 9from source.grid import CovidGrid
10from source.grid import CovidSpot
11from source.scenario import CovidScenario
12
13if TYPE_CHECKING:
14    from Melodie import AgentList
15
16
17class CovidModel(Model):
18    scenario: "CovidScenario"
19
20    def create(self):
21        self.agents: "AgentList[CovidAgent]" = self.create_agent_list(CovidAgent)
22        self.environment = self.create_environment(CovidEnvironment)
23        self.data_collector = self.create_data_collector(CovidDataCollector)
24        self.grid = self.create_grid(CovidGrid, CovidSpot)
25
26    def setup(self):
27        self.agents.setup_agents(
28            agents_num=self.scenario.agent_num,
29            params_df=self.scenario.get_dataframe(data_info.agent_params),
30        )
31        self.grid.setup_params(
32            width=self.scenario.grid_x_size,
33            height=self.scenario.grid_y_size
34        )
35        self.grid.setup_agent_locations(self.agents)

In the setup function, the grid needs to be initialized with size parameters from scenario. Then, the agents need to be located on the grid, which requires that they are already initialized with the attributes x and y.

GridAgent

The agents that can walk on the grid are defined by inheriting the GridAgent class of Melodie. They have three additional attributes: category, x, and y. Additionally, they also have access to the grid (Line 26) and some grid-related functions, e.g., rand_move_agent in Line 32.

agent.py
 1import random
 2from typing import TYPE_CHECKING
 3
 4from Melodie import GridAgent
 5
 6if TYPE_CHECKING:
 7    from source.scenario import CovidScenario
 8    from Melodie import AgentList
 9    from source.grid import CovidSpot
10    from source.grid import CovidGrid
11
12
13class CovidAgent(GridAgent):
14    scenario: "CovidScenario"
15    grid: "CovidGrid[CovidSpot]"
16    spot: "CovidSpot"
17
18    def set_category(self):
19        self.category = 0
20
21    def setup(self):
22        self.x: int = 0
23        self.y: int = 0
24        self.health_state: int = 0
25        self.age_group: int = 0
26
27    def move(self):
28        spot: "CovidSpot" = self.grid.get_spot(self.x, self.y)
29        stay_prob = spot.stay_prob
30        if random.uniform(0, 1) > stay_prob:
31            move_radius = 1
32            self.rand_move_agent(move_radius, move_radius)
33
34    def infection(self, agents: "AgentList[CovidAgent]"):
35        neighbors = self.grid.get_neighbors(self)
36        for neighbor_category, neighbor_id in neighbors:
37            neighbor_agent: "CovidAgent" = agents.get_agent(neighbor_id)
38            if neighbor_agent.health_state == 1:
39                if random.uniform(0, 1) < self.scenario.infection_prob:
40                    self.health_state = 1
41                    break

One thing that might be confused: Why is there the category attribute?

The category attribute is to make sure that, when there are multiple groups of agents walking on the grid, the grid can distinguish them and work well. So, the function set_category much be implemented in a class that inherits the GridAgent class. For example, as shown in Line 35-36, when iterating through the neighbors returned by grid.get_neighbors, each neighbor is indexed with both category and the id of the agent.

For more details of the Grid and Spot modules, please refer to the API Reference section.