1. Simple SIR Model
This first step illustrates how to write a basic agent-based SIR model in GAMA. It introduces the core structure of a GAML model and defines the agents, their behaviors, and a simple graphical output.
In particular, this step presents:
- How to define a
personspecies with a status variable (susceptible, infected, recovered) - How to give agents a random movement behavior using
wander - How to implement probabilistic infection using
flip - How to define a GUI experiment with a spatial display and a time-series chart
- How to track global output metrics
The model file for this step can be found in:
msi.gama.models/models/Tutorials/SIR Analysis/models/SIR Model 01.gaml
Model structure​
A GAMA model is composed of three types of sections:
global: defines the world agent — the unique agent that holds global variables, theinitblock, and global behaviors. It is always created and initialized first.species: defines the prototype of agents that populate the simulation — their attributes, behaviors, and visual aspects.experiment: defines the execution context — parameters, displays, monitors, and output files.
Defining the person species​
Each agent in the simulation is a person. A person has two attributes: a status string (either "susceptible", "infected" or "recovered") and an infection_timer integer that counts down how many steps remain before recovery.
species person {
string status <- "susceptible";
int infection_timer <- 0;
Movement​
At each step, every agent wanders randomly across the environment using the built-in wander action from the moving skill. The speed parameter controls how many units of space the agent moves per step.
string status <- "susceptible";
int infection_timer <- 0;
float speed <- 1.0;
reflex move {
do wander();
}
...
Infection​
Only infected agents execute the infect reflex. They ask all susceptible agents within infection_distance meters to potentially become infected, with a probability controlled by infection_rate using the flip operator. Once an agent is infected, its infection_timer is set to recovery_time. At each step the timer decrements; when it reaches zero the agent transitions to "recovered".
reflex infect when: status = "infected" {
ask (person at_distance infection_distance) where (each.status = "susceptible") {
if flip(infection_rate) {
status <- "infected";
infection_timer <- recovery_time;
}
}
infection_timer <- infection_timer - 1;
if infection_timer <= 0 {
status <- "recovered";
}
}
Note: The
when:facet on a reflex makes it conditional — the reflex body only executes when the condition is true. This avoids unnecessary computation for healthy and recovered agents.
Visual aspect​
Agents are displayed as colored circles: red for infected, blue for recovered, and green for susceptible. The ternary operator ? : is used for inline conditional color selection.
aspect base {
aspect base {
draw circle(1) at: location color:
(status = "infected") ? #red : ((status = "recovered") ? #blue : #green);
}
}
The global block​
The global section declares the five model parameters and three output counters. Parameters are declared with min: and max: facets so they can be exposed to the GAMA parameter panel.
global {
int nb_agents <- 200 min: 50 max: 500;
int nb_infected_init <- 5 min: 1 max: 50;
float infection_rate <- 0.5 min: 0.0 max: 1.0;
int infection_distance <- 5 min: 1 max: 20;
int recovery_time <- 50 min: 10 max: 200;
int nb_susceptible <- 0;
int nb_infected <- 0;
int nb_recovered <- 0;
Initialization​
The init block creates nb_agents person agents placed randomly in the environment. A subset of nb_infected_init agents are immediately set to "infected" and their timer initialized.
init {
create person number: nb_agents;
ask nb_infected_init among (person as list) {
status <- "infected";
infection_timer <- recovery_time;
}
}
Updating counters​
A global reflex recounts the three populations at each step using the count operator:
reflex update_counts {
nb_susceptible <- person count (each.status = "susceptible");
nb_infected <- person count (each.status = "infected");
nb_recovered <- person count (each.status = "recovered");
}
}
The experiment​
Important: If a parameter has a fixed value in the experiment (no
min:andmax:facets), it will still be explored relative to themin:andmax:values defined in theglobalblock. If a variable is not declared as aparameterat all in the experiment, it will not be explored and will keep its default value as defined inglobal.
The GUI experiment exposes the five parameters and defines two displays and three monitors.
experiment SIR_gui type: gui {
parameter "Number of agents" var: nb_agents;
parameter "Initially infected" var: nb_infected_init;
parameter "Infection rate" var: infection_rate;
parameter "Infection distance" var: infection_distance;
parameter "Recovery time" var: recovery_time;
output {
display "Population" type: java2D {
species person aspect: base;
}
display "SIR Chart" type: java2D {
chart "SIR dynamics" type: series {
data "Susceptible" value: nb_susceptible color: #green;
data "Infected" value: nb_infected color: #red;
data "Recovered" value: nb_recovered color: #blue;
}
}
monitor "Susceptible" value: nb_susceptible;
monitor "Infected" value: nb_infected;
monitor "Recovered" value: nb_recovered;
}
}
The Population display shows every agent as a colored circle at its current location. The SIR Chart display plots the three populations as time series — you should observe the classic epidemic curve: a peak of infected agents followed by a growing recovered population as susceptibles are depleted.
Complete model​
model SIR
global {
int nb_agents <- 200 min: 50 max: 500;
int nb_infected_init <- 5 min: 1 max: 50;
float infection_rate <- 0.5 min: 0.0 max: 1.0;
int infection_distance <- 5 min: 1 max: 20;
int recovery_time <- 50 min: 10 max: 200;
int nb_susceptible <- 0;
int nb_infected <- 0;
int nb_recovered <- 0;
init {
create person number: nb_agents;
ask nb_infected_init among (person as list) {
status <- "infected";
infection_timer <- recovery_time;
}
}
reflex update_counts {
nb_susceptible <- person count (each.status = "susceptible");
nb_infected <- person count (each.status = "infected");
nb_recovered <- person count (each.status = "recovered");
}
}
species person skills: [moving] {
string status <- "susceptible";
int infection_timer <- 0;
float speed <- 1.0;
reflex move {
do wander();
}
reflex infect when: status = "infected" {
ask (person at_distance infection_distance) where (each.status = "susceptible") {
if flip(infection_rate) {
status <- "infected";
infection_timer <- recovery_time;
}
}
infection_timer <- infection_timer - 1;
if infection_timer <= 0 {
status <- "recovered";
}
}
aspect base {
draw circle(1) at: location color:
(status = "infected") ? #red : ((status = "recovered") ? #blue : #green);
}
}
experiment SIR_gui type: gui {
parameter "Number of agents" var: nb_agents;
parameter "Initially infected" var: nb_infected_init;
parameter "Infection rate" var: infection_rate;
parameter "Infection distance" var: infection_distance;
parameter "Recovery time" var: recovery_time;
output {
display "Population" type: java2D {
species person aspect: base;
}
display "SIR Chart" type: java2D {
chart "SIR dynamics" type: series {
data "Susceptible" value: nb_susceptible color: #green;
data "Infected" value: nb_infected color: #red;
data "Recovered" value: nb_recovered color: #blue;
}
}
monitor "Susceptible" value: nb_susceptible;
monitor "Infected" value: nb_infected;
monitor "Recovered" value: nb_recovered;
}
}
What's next?​
In Step 2, we add a stop condition so the simulation halts automatically when the epidemic ends or after a maximum number of cycles — a prerequisite before running batch experiments.