Mesa-Geo Introductory Model#
To overview the critical parts of Mesa-Geo this tutorial uses the pandemic modelling approach known as a S(usceptible), I(infected) and R(ecovered) or SIR model.
Components of the model are:
Agents: Each agent in the model represents an individual in the population. Agents have states of susceptible, infected, recovered, or dead. The Agents are point agents, randomly placed into the environment.
Environment: The environment is a set of polygons of a few Toronto neighborhoods.
Interaction Rules: Susceptible agents can become infected with a certain probability, if they come into contact with infected agents. Infected agents then recover after a certain period or perish based on a probability.
Parameters:
Population Size (number of human agents in the model)
Initial Infection (percent of the population initial infected)
Exposure Distance (proximity suscpetible agents must be to infected agents to possibly get infected)
Infection Risk (probability of becoming infected)
Recovery Rate (time infection lasts)
Mobility (distance agent moves)
The tutorial then proceeds in three parts:
Part 1 Create the Basic Model
Part 2 Add Agent Behaviors and Model Complexity
Part 3 Add Visualizations and Interface
(You can use the table of contents button on the left side of the interface to skip to any specific part)
Users can use Google Colab* (Please ensure you run the Colab dependency import cell- below)
*Based on a recent Google Colab update currently, Solara visuals are not rendering. However, the link still provides a easy way to download the jupyter file. You can see the issue on the Solara GitHub site
You can also download the file directly from GitHub
#Run this if in colab or if you need to install mesa and mesa-geo in your local environment.
!pip install mesa-geo --quiet
!mkdir -p data
!wget -P data https://raw.githubusercontent.com/projectmesa/mesa-geo/main/docs/tutorials/data/TorontoNeighbourhoods.geojson
Part 1 Create the Basic Model#
This portion initializes the human agents, the neighborhood agents, and the model class that manages the model dynamics.
First we import our dependencies
This cell imports the specific libraries we need to create our model.
Shapley a library GIS library for object in the cartesian plane. From Shapely we specifically need the Point class to create our human agents
Mesa the parent ABM library to Mesa-Geo
Then of course mesa-geo which although not strictly necessary we also specifically import the visualization part of the library so we do not have to write out mesa-geo.visualization modules when we call them.
from shapely.geometry import Point
import mesa
import mesa_geo as mg
import mesa_geo.visualization as mgv # the warning that appears from Solara is fixed in Mesa 3.0 you can install the pre-release with pip install -U --pre-mesa
Create the person agent class#
The person in this model represents one human being and we initilaize each person agent with two key parts:
The agent attributes, such as recovery rate and death risk
The step function, actions the agent will take each model step
The first thing we are going to do is create the person agent class. This class has several attributes necessary to make a more holistic model.
First, there are the required attributes for any GeoAgent in Mesa-Geo
unique_id: Some unique identifier, often an int, this ensure Mesa can keep track of each agent without confusion
model: Model object class that we will build later, this is a pointer to the model instance so the agent can get information from the model as it behaves
geometry: GIS geometric object in this case a GIS Point
crs: A string describing the coordinate reference system the agent is using
As you can see these are inherited from the mesa-geo librarary through the “mg.GeoAgent” in the class instantiation.
Second, the variable attributes these are unique to our SIR model:
agent_type: A string which describes the agent state (susceptible, infected, recovered, or dead)
mobility_range: Distance the agent can move in meters
infection risk: A float from 0.0 to 1.0 that determines the risk of the agent being infected if exposed.
recovery_rate: A float from 0.0 to 1.0 that determine how long the agents takes to recover
death_risk: A float from 0.0 to 1.0 that determines the probability the agent will die
The __repr__
function is a Python primitive that will print out information as directed by the code. In this case we will print out the agent ID
The step function is a Mesa primitive that the scheduler looks for and describes what action the agent takes each step
class PersonAgent(mg.GeoAgent):
"""Person Agent."""
def __init__(
self,
unique_id,
model,
geometry,
crs,
agent_type,
mobility_range,
infection_risk,
recovery_rate,
death_risk
):
super().__init__(unique_id, model, geometry, crs)
# Agent attributes
self.atype = agent_type
self.mobility_range = mobility_range
self.infection_risk=infection_risk,
self.recovery_rate = recovery_rate
self.death_risk = death_risk
def __repr__(self):
return "Person " + str(self.unique_id)
def step(self):
print (repr(self))
print(self.atype, self.death_risk, self.recovery_rate)
Create the neighborhood agent#
The neighborhood in this model represents one geographical area as defined by the geojson file we uploaded.
Similar to the person agent, we initialize each neighborhood agent with the same two key parts.
The agent attributes, such as geometry and state of neighborhood
The step function, behaviors the agent will take during each model step.
Similar to the person agent for the neighborhood agent there are two types of attributes.
The required attributes for any GeoAgent in Mesa-Geo:
unique_id: For geographic agents such as a neighborhood mesa-geo will assign a very large integer as the agent id, if desired users can specify their own.
model: Model object class that we will build later, this is a pointer to the model instance so the agent can get information from the model as it behaves
geometry: GIS geometric object in this case a polygon form the geojson defining the perimeter of the neighborhood
crs: A string describing the coordinate reference system the agent is using
Similar to the person agent, “mg.GeoAgent” is inherited from mesa-geo.
Next are the variable attributes:
agent_type: A string which describes the state of the neighborhood which will be either safe or hot spot
hotspot_threshold: An integer that is the number of infected people in a neighborhood to call it a hotspot
We will also use the __repr__
function to print out the agent ID
Then the step function, which is a primitive that the Mesa scheduler looks for and describes what action the agent takes each step
class NeighbourhoodAgent(mg.GeoAgent):
"""Neighbourhood agent. Changes color according to number of infected inside it."""
def __init__(
self, unique_id, model, geometry, crs, agent_type="safe", hotspot_threshold=1
):
super().__init__(unique_id, model, geometry, crs)
self.atype = agent_type
self.hotspot_threshold = (
hotspot_threshold # When a neighborhood is considered a hot-spot
)
def __repr__(self):
return "Neighbourhood " + str(self.unique_id)
def step(self):
"""Advance agent one step."""
print(repr(self))
Create the Model Class#
The model class is the manager that instantiates the agents, then manages what is happening in the model through the step function, and collects data.
We will create the model with parameters that will set the attributes of the agents as it instantiates them and a step function to call the agent step function.
First, we name our class in this case GeoSIR and we inherit the model class from Mesa. We store the path to our GeoJSON file in the object geojson regions. As JSONs mirror Pythons dictionary structure, we store the key for the neighbourhood id (“HOODNUM”) in the variable unique_id.
Second, we set up the python initializer to initiate our model class. To do this we will, set up key word arguments or kwargs of the parameters we want for our model. In this case we will use:
population size (pop_size): An integer that determines the number of person agents
initial infection (init_infection): A float between 0.0 and 1.0 which determines what percentage of the population is infected as the model initiates
exposure_distance (exposure_dist): An integer for the distance in meters a susceptible person agent must within to be infected by a person agent who is infected
maximum infection risk (max_infection_risk): A float between 0.0 and 1.0 of which determines the highest suscpetibility rate in the population
Third, we initialize our agents. Mesa-Geo has an AgentCreator class inside is geoagent.py file that can create GeoAgents from files, GeoDataFrames, GeoJSON or Shapely objects.
Creating the NeighbourhoodAgents
In this case we will use the torontoneighbourhoods.geojson
file located in the data folder to to create the NeighbourhoodAgents. Next, we will add them to the environment with the space.add_agents function. Then we will iterate through each of the NeighbourhoodAgents to add them to the schedule.
Creating the PersonAgents
We will use Mesa-Geo AgentCreator to create the person agents. To create a heterogeneous (diverse) population we will use the random object created as part of Mesa’s base class to help initialize the population’s parameters.
death_risk: A float from 0 to 1
agent_type: Compares the model parameter of initial infection of a random float between 0 and 1 and the initial infection parameter. If it is less than the initial infection parameter the agent is initialized as infected.
recover: Is an integer between 1 and the recovery rate. This determines the number of steps it takes for the agent to recover.
infection_risk: is a float between 0 and the parameter of max_infection_risk, which will then determine how likely a person is to get infected.
death_risk: Is a random float between 0 and 1 that will determine how likely a person is to die when infected.
By using Python’s random library to create these attributes for each agent, we can now create a diverse agent population.
Passing these parameters through the AgentCreator
class we initialize our agent object.
As Mesa-Geo is an GIS based ABM, we need assign each PersonAgent a Geometry and location. To do this we will use a helper function find_home
. This helper function first identifies a NeighbourhoodAgent where the PersonAgent will start. Next it identifies the center of the neighborhood and its boundary and then randomly moving from the center point, put staying within the bounds, it a lat and long to aissgn the PersonAgent is starting location.
Step Function
The final piece is to initialize a step function. This function a Mesa primitive calls the RandomActiviationByType scheduler we set up and then iterates through each agent calling their step function.
The Model
We know have the pieces of our Model. A GIS layer of polygons that creates NeighbourhoodAgents from our GeoJSON file. A diverse population of GIS Point objects, with different infection, recovery and death risks. A model class that initializes these agents, a scheduler to call these agents, a GIS space and step function to execute the simulation
class GeoSIR(mesa.Model):
"""Model class for a simplistic infection model."""
# Geographical parameters for desired map
geojson_regions = "data/TorontoNeighbourhoods.geojson"
unique_id = "HOODNUM"
def __init__(
self, pop_size=30, mobility_range=500, init_infection=0.2, exposure_dist=500, max_infection_risk=0.2,
max_recovery_time=5
):
super().__init__()
self.schedule = mesa.time.RandomActivationByType(self)
self.space = mg.GeoSpace(warn_crs_conversion=False)
# SIR model parameters
self.pop_size = pop_size
self.mobility_range = mobility_range
self.initial_infection = init_infection
self.exposure_distance = exposure_dist
self.infection_risk = max_infection_risk
self.recovery_rate = max_recovery_time
# Set up the Neighbourhood patches for every region in file
ac = mg.AgentCreator(NeighbourhoodAgent, model=self)
neighbourhood_agents = ac.from_file(
self.geojson_regions, unique_id=self.unique_id
)
#Add neighbourhood agents to space
self.space.add_agents(neighbourhood_agents)
#Add neighbourhood agents to scheduler
for agent in neighbourhood_agents:
self.schedule.add(agent)
# Generate random location, add agent to grid and scheduler
for i in range(pop_size):
#assess if they are infected
if self.random.random() < self.initial_infection:
agent_type = "infected"
else:
agent_type = "susceptible"
#determine movement range
mobility_range = self.random.randint(0,self.mobility_range)
#determine agent recovery rate
recover = self.random.randint(1,self.recovery_rate)
#determine agents infection risk
infection_risk = self.random.uniform(0,self.infection_risk)
#determine agent death probability
death_risk= self.random.random()
# Generate PersonAgent population
unique_person = mg.AgentCreator(
PersonAgent,
model=self,
crs=self.space.crs,
agent_kwargs={"agent_type": agent_type,
"mobility_range":mobility_range,
"recovery_rate":recover,
"infection_risk": infection_risk,
"death_risk": death_risk
}
)
x_home, y_home = self.find_home(neighbourhood_agents)
this_person = unique_person.create_agent(
Point(x_home, y_home), "P" + str(i),
)
self.space.add_agents(this_person)
self.schedule.add(this_person)
def find_home(self, neighbourhood_agents):
""" Find start location of agent """
#identify location
this_neighbourhood = self.random.randint(
0, len(neighbourhood_agents) - 1
) # Region where agent starts
center_x, center_y = neighbourhood_agents[
this_neighbourhood
].geometry.centroid.coords.xy
this_bounds = neighbourhood_agents[this_neighbourhood].geometry.bounds
spread_x = int(
this_bounds[2] - this_bounds[0]
) # Heuristic for agent spread in region
spread_y = int(this_bounds[3] - this_bounds[1])
this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2
this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2
return this_x, this_y
def step(self):
"""Run one step of the model."""
self.schedule.step()
Run The Base Model#
#explanatory
This cell is fairly simple
1 - We instantiate the SIR model by call the class name “GeoSIR” into the object model
.
2 - Then we call the step function to see if it prints out the Agent IDs, infection status, death_risk, and recovery rate as called in the PersonAgent class.
You can also see all the person agents are called and then the neighbourhood agents. This will become important later as we want to update the neighbourhood status later based on its PersonAgent status.
If you are curious about the numbers for the neighbourhood agents, you can open up the GeoJSON in the data folder and see that each neighborhood gets a unique id identified by HOODNUM
to ensure this number does not cause a conflict with our agent numbers, we add a “P” to their ID.
model = GeoSIR()
model.step()
Part 2 Add Agent and Model Complexity#
Increase PersonAgent Complexity#
In this section we add behaviors to the PersonAgent to build the necessary SIR dynamics.
To create the SIR dynamics we need the agents move, determine if they have been exposed and if they have process the probability of them being infected and possibly dying.
To do this we will update our step function. The step function logic uses the agent’s atype
to determine what actions to process
Part 1
If the PersonAgent atype
is susceptible, then we need to identify all PersonAgent’s neighbors within the exposure distance. To do this, we will use Mesa-Geo’s get_neighbors_within_distance
function which takes 2 parameters, the agent, and a distance, which in this case is the model parameter for exposure distance in meters. This creates a list of PersonAgents within that distance.
The get_neighbors_within_distance
function has two keyword arguments center
and relation
. center
takes True
or False
on whether to include the center, it is set to False
and measures as a buffer around the agent’s geometry. If True
it measures from the Center of the point. relation
is defaulted to intersects
but can take any common spatial relationship, such as contains
, within
, touches
, crosses
The step function then iterates through the list of neighbors to see if any agents are infected. If so it does a probabilistic comparison of a random float compared to the agents infection risk and if True
the agent becomes infected and the iteration ends.
Part 2
If the agent atype
is infected, then the step function does comparisons. First, it sees how many steps the agent has been infected. To track this the PersonAgent got a new attribute counter which is steps_infected
. If the steps are greater than or equal to their recovery rate, the agent is recovered, if not then the function does a probabilistic comparison with the agents death risk to see if the agent dies. If neither of these things happen the steps_infected
increases by one.
Part 3
The next part is if the agent atype
is not dead then the agent moves. For this we randomly get an integer for the x any (lat and long) between their negative mobility_range
and positive mobility range
. We pass these two integers into the helper function move_point
and then update the agents geometry with this new point.
Finally, we update the counts of agent types.
class PersonAgent(mg.GeoAgent):
"""Person Agent."""
def __init__(
self,
unique_id,
model,
geometry,
crs,
agent_type,
mobility_range,
infection_risk,
recovery_rate,
death_risk
):
super().__init__(unique_id, model, geometry, crs)
# Agent attributes
self.atype = agent_type
self.mobility_range = mobility_range
self.infection_risk=infection_risk,
self.recovery_rate = recovery_rate
self.death_risk = death_risk
self.steps_infected=0
self.steps_recovered = 0
def __repr__(self):
return "Person " + str(self.unique_id)
#Helper function for moving agent
def move_point(self, dx, dy):
"""
Move a point by creating a new one
:param dx: Distance to move in x-axis
:param dy: Distance to move in y-axis
"""
return Point(self.geometry.x + dx, self.geometry.y + dy)
def step(self):
#Part 1 - find neighbors based on infection distance
if self.atype == "susceptible":
neighbors = self.model.space.get_neighbors_within_distance(
self, self.model.exposure_distance
)
for neighbor in neighbors:
if (
neighbor.atype == "infected"
and self.random.random() < self.model.infection_risk
):
self.atype = "infected"
break #stop process if agent becomes infected
#Part -2 If infected, check if agent recovers or agent dies
elif self.atype == "infected":
if self.steps_infected >= self.recovery_rate:
self.atype = "recovered"
self.steps_infected = 0
elif self.random.random() < self.death_risk:
self.atype = "dead"
else:
self.steps_infected += 1
elif self.atype == "recovered":
self.steps_recovered+=1
if self.steps_recovered >=2:
self.atype= "susceptible"
self.steps_recovered = 0
#Part 3 - If not dead, move
if self.atype != "dead":
move_x = self.random.randint(-self.mobility_range, self.mobility_range)
move_y = self.random.randint(-self.mobility_range, self.mobility_range)
self.geometry = self.move_point(move_x, move_y) # Reassign geometry
self.model.counts[self.atype] += 1 # Count agent type
Increase NeighbourhoodAgent Complexity#
For the NeighbourhoodAgent we want to change their color based on the number of infected PersonAgents in their neighbourhood.
To do this we will create a helper function called color_hotspot
. We will then use mesa-geo’s get_intersecting_agents
function. We will then iterate through that list to get the agents with atype
infected if the list is longer than our hotspot_threshold
equal to 1 (so if two agents in the neighborhood are infected) then the atype
will change to hotspot
.
We then update our model counts.
class NeighbourhoodAgent(mg.GeoAgent):
"""Neighbourhood agent. Changes color according to number of infected inside it."""
def __init__(
self, unique_id, model, geometry, crs, agent_type="safe", hotspot_threshold=1
):
super().__init__(unique_id, model, geometry, crs)
self.atype = agent_type
self.hotspot_threshold = (
hotspot_threshold # When a neighborhood is considered a hot-spot
)
def __repr__(self):
return "Neighbourhood " + str(self.unique_id)
def color_hotspot(self):
# Decide if this region agent is a hot-spot
# (if more than threshold person agents are infected)
neighbors = self.model.space.get_intersecting_agents(self)
infected_neighbors = [
neighbor for neighbor in neighbors if neighbor.atype == "infected"
]
if len(infected_neighbors) > self.hotspot_threshold:
self.atype = "hotspot"
else:
self.atype = "safe"
def step(self):
"""Advance agent one step."""
self.color_hotspot()
self.model.counts[self.atype] += 1 # Count agent type
Increase model complexity#
For this section will add data collection where we collect the status of the PersonAgents and the NeighbourhoodAgents but counting the different atypes
.
As we run our SIR model, we want to ensure we are collecting information about the status of the disease.
To do this we will create helper functions that get this information. In this case we will put them in a separate cell, but depending on the developers preference they could also put them in the model class or collect the information in a handful of other ways.
In this case, we set up an attribute in the model called counts and these functions just get the total number from Mesa’s data collector of each of our statuses.
# Functions needed for datacollector
def get_infected_count(model):
return model.counts["infected"]
def get_susceptible_count(model):
return model.counts["susceptible"]
def get_recovered_count(model):
return model.counts["recovered"]
def get_dead_count(model):
return model.counts["dead"]
def get_hotspot_count(model):
return model.counts["hotspot"]
def get_safe_count(model):
return model.counts["safe"]
Now to finish the model so we can add the interface we add datacollection and a stop condition. As these updates are interspersed throughout the class. The comment #added
is used to make the changes easier to identify.
First, we add an attribute called self.counts
which will track our the agent types (e.g. infected). We will initialize it as None. We then initialize the counts in our next line self.reset_counts()
. This helper function located directly above the step function, resets the counts of each type of agent so it is always based on the current situation in the Model.
We are then going to add the attribute self.running so we can input the stop condition. Next we set our our data collector that call our functions from the previous cell which collects our agent types
With these added we can now call self.reset_counts
and self.datacollector.collect
in our step function so it collect our agent states each step.
Finally we add a stop condition. If no PersonAgent is infected the pandemic is over and we stop the model.
class GeoSIR(mesa.Model):
"""Model class for a simplistic infection model."""
# Geographical parameters for desired map
geojson_regions = "data/TorontoNeighbourhoods.geojson"
unique_id = "HOODNUM"
def __init__(
self, pop_size=30, mobility_range=500, init_infection=0.2, exposure_dist=500, max_infection_risk=0.2,
max_recovery_time=5
):
super().__init__()
#Scheduler
self.schedule = mesa.time.RandomActivationByType(self)
#Space
self.space = mg.GeoSpace(warn_crs_conversion=False)
# Data Collection
self.counts = None #added
self.reset_counts() #added
# SIR model parameters
self.pop_size = pop_size
self.mobility_range = mobility_range
self.initial_infection = init_infection
self.exposure_distance = exposure_dist
self.infection_risk = max_infection_risk
self.recovery_rate = max_recovery_time
self.running = True #added
#added
self.datacollector = mesa.DataCollector(
{
"infected": get_infected_count,
"susceptible": get_susceptible_count,
"recovered": get_recovered_count,
"dead": get_dead_count,
"safe": get_safe_count,
"hotspot": get_hotspot_count
}
)
# Set up the Neighbourhood patches for every region in file
ac = mg.AgentCreator(NeighbourhoodAgent, model=self)
neighbourhood_agents = ac.from_file(
self.geojson_regions, unique_id=self.unique_id
)
#Add neighbourhood agents to space
self.space.add_agents(neighbourhood_agents)
#Add neighbourhood agents to scheduler
for agent in neighbourhood_agents:
self.schedule.add(agent)
# Generate random location, add agent to grid and scheduler
for i in range(pop_size):
#assess if they are infected
if self.random.random() < self.initial_infection:
agent_type = "infected"
else:
agent_type = "susceptible"
#determine movement range
mobility_range = self.random.randint(0,self.mobility_range)
#determine agent recovery rate
recover = self.random.randint(1,self.recovery_rate)
#determine agents infection risk
infection_risk = self.random.uniform(0,self.infection_risk)
#determine agent death probability
death_risk= self.random.uniform(0,0.05)
# Generate PersonAgent population
unique_person = mg.AgentCreator(
PersonAgent,
model=self,
crs=self.space.crs,
agent_kwargs={"agent_type": agent_type,
"mobility_range":mobility_range,
"recovery_rate":recover,
"infection_risk": infection_risk,
"death_risk": death_risk
}
)
x_home, y_home = self.find_home(neighbourhood_agents)
this_person = unique_person.create_agent(
Point(x_home, y_home), "P" + str(i),
)
self.space.add_agents(this_person)
self.schedule.add(this_person)
def find_home(self, neighbourhood_agents):
""" Find start location of agent """
#identify location
this_neighbourhood = self.random.randint(
0, len(neighbourhood_agents) - 1
) # Region where agent starts
center_x, center_y = neighbourhood_agents[
this_neighbourhood
].geometry.centroid.coords.xy
this_bounds = neighbourhood_agents[this_neighbourhood].geometry.bounds
spread_x = int(
this_bounds[2] - this_bounds[0]
) # Heuristic for agent spread in region
spread_y = int(this_bounds[3] - this_bounds[1])
this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2
this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2
return this_x, this_y
#added
def reset_counts(self):
self.counts = {
"susceptible": 0,
"infected": 0,
"recovered": 0,
"dead": 0,
"safe": 0,
"hotspot": 0,
}
def step(self):
"""Run one step of the model."""
self.reset_counts() #added
self.schedule.step()
self.datacollector.collect(self) #added
# Run until no one is infected
if self.counts["infected"] == 0 :
self.running = False
To test our code we will run the model through 5 steps and then call the model dataframe via data collector with get_model_vars_dataframe()
. This will show a Pandas DataFrame.
model = GeoSIR()
for i in range(5):
model.step()
model.datacollector.get_model_vars_dataframe()
Part 3 - Add Interface#
Adding the interface requires three steps:
Define the agent portrayal
Set the sliders for the model parameters
Call the model through the Mesa-Geo visualization model
Visualizing agents is done through a function that is is passed in as a parameter. By default agents they are Point geometries are rendered as circles. However, Mesa uses ipyleaflet Users can pass through any Point geometry for their Agent (i.e. Marker, Circle, Icon, AwesomeIcon). To show this we will use different colors for the PersonAgent base don infection status and if they die, we will use the Font Awesome Icons and represent them with an x, in the traditional ipyleaflet marker.
We will also change the color of the NeighbourhoodAgent based whether or not it is a hotspot
Next we will build Sliders for each of our input parameters. These use the Solara’s input approach. This is stored in a dictionary of dictionaries that is then passed through in the model instantiation.
If you want the model to fill the entire screen you can hit the expand button in the upper right.
def SIR_draw(agent):
"""
Portrayal Method for canvas
"""
portrayal = {}
if isinstance(agent, PersonAgent):
if agent.atype == "susceptible":
portrayal["color"] = "Green"
elif agent.atype == "infected":
portrayal["color"] = "Red"
elif agent.atype == "recovered":
portrayal["color"] = "Blue"
else:
portrayal["marker_type"] = "AwesomeIcon"
portrayal["name"] = "times"
portrayal["icon_properties"] = {
"marker_color": 'black',
"icon_color":'white'}
if isinstance(agent, NeighbourhoodAgent):
if agent.atype == "hotspot":
portrayal["color"] = "Red"
else:
portrayal["color"] = "Green"
return portrayal
model_params = {
"pop_size": {
"type": "SliderInt",
"value": 80,
"label": "Population Size",
"min": 0,
"max": 100,
"step": 1,
},
"mobility_range": {
"type": "SliderInt",
"value": 500,
"label": "Max Possible Agent Movement",
"min": 100,
"max": 1000,
"step": 50,
},
"init_infection": {
"type": "SliderFloat",
"value": 0.4,
"label": "Initial Infection",
"min": 0.0,
"max": 1.0,
"step": 0.1,
},
"exposure_dist": {
"type": "SliderInt",
"value": 800,
"label": "Exposure Distance",
"min": 100,
"max": 1000,
"step": 50,
},
"max_infection_risk": {
"type": "SliderFloat",
"value": 0.7,
"label": "Maximum Infection Risk",
"min": 0.0,
"max": 1.0,
"step": 0.1
},
"max_recovery_time": {
"type": "SliderInt",
"value": 7,
"label": "Maximum Number of Steps to Recover",
"min": 1,
"max": 10,
"step": 1,
}}
To create the model with the interface we use Mesa’s GeoJupyterViz module. First we pass in the model class and next the parameters. We then switch to key word arguments. First measures, in this case of list of lists, where the first list will be a chart of the PersonAgent statuses and the second chart will be the NeighbourhoodAgent statuses. We also pass in a name, our agent portrayal function a zoom level and in this case set the scroll wheel zoom to false.
page = mgv.GeoJupyterViz(
GeoSIR,
model_params,
measures= [["infected", "susceptible", "recovered", "dead"], ["safe", "hotspot"]],
name="GeoSIR",
agent_portrayal=SIR_draw,
zoom=12,
scroll_wheel_zoom=False
)
# This is required to render the visualization in the Jupyter notebook
page