Skip to content

Commit 48393a6

Browse files
committed
Ready!
1 parent 9ba5ecf commit 48393a6

11 files changed

Lines changed: 284 additions & 133 deletions

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
# agent-based-modelling
1+
# Agent-based Modelling
2+
3+
This content is ready to go! As usual, create a CodeSpace or work locally use the DevContainers plugin and Docker Desktop. The virutal environment should be selected by default this time, so try running the code right away. 😎 If not, select the Poetry environment. It make need a couple of minutes to fully load.
4+
5+
Note that some cells in the notebook will take time to run, especially when doing large batch runs and sensitivity analyses. We should be able to run code in the live session together without issue.
6+
7+
See you in class!

icsspy/abms/.DS_Store

6 KB
Binary file not shown.

icsspy/abms/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .bounded_confidence import BoundedConfidenceModel
2+
from .sir import SIRModel
3+
4+
# from .threshold.model import ThresholdModel
5+
6+
__all__ = ["SIRModel", "BoundedConfidenceModel"] # ThresholdModel

icsspy/abms/bounded_confidence.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import numpy as np
2+
from mesa import Agent, Model
3+
from mesa.datacollection import DataCollector
4+
from mesa.space import MultiGrid
5+
from mesa.time import RandomActivation
6+
7+
8+
class BoundedConfidenceAgent(Agent):
9+
def __init__(self, unique_id, model, epsilon, max_agent_step_size=1):
10+
super().__init__(unique_id, model)
11+
self.opinion = self.random.uniform(
12+
-1, 1
13+
) # Agent's opinion initialized between -1 and 1
14+
self.epsilon = epsilon # Confidence bound ($\epsilon$)
15+
self.max_agent_step_size = max_agent_step_size # Step size for movement
16+
self.pos_history = [] # Track agent's position over time
17+
self.interactions = {} # Track interactions with a count
18+
19+
def step(self):
20+
# Move to a random neighboring cell with the given step size
21+
possible_moves = self.model.grid.get_neighborhood(
22+
self.pos, moore=True, include_center=False, radius=self.max_agent_step_size
23+
)
24+
new_position = self.random.choice(possible_moves)
25+
self.model.grid.move_agent(self, new_position)
26+
self.pos_history.append(new_position) # Record the new position
27+
28+
# Interact with neighbors within confidence bound
29+
neighbors = self.model.grid.get_neighbors(
30+
self.pos, moore=True, include_center=False
31+
)
32+
for neighbor in neighbors:
33+
if abs(self.opinion - neighbor.opinion) <= self.epsilon:
34+
# Update opinion if within confidence bound
35+
self.opinion = (self.opinion + neighbor.opinion) / 2
36+
37+
# Record interactions
38+
if neighbor.unique_id in self.interactions:
39+
self.interactions[neighbor.unique_id] += 1
40+
else:
41+
self.interactions[neighbor.unique_id] = 1
42+
43+
44+
class BoundedConfidenceModel(Model):
45+
def __init__(
46+
self,
47+
grid_width,
48+
grid_height,
49+
N,
50+
epsilon,
51+
max_agent_step_size=1,
52+
max_steps=1000,
53+
convergence_threshold=0.001,
54+
):
55+
super().__init__()
56+
self.num_agents = N
57+
self.grid = MultiGrid(grid_width, grid_height, True)
58+
self.schedule = RandomActivation(self)
59+
self.epsilon = epsilon
60+
self.max_agent_step_size = max_agent_step_size
61+
self.max_steps = max_steps
62+
self.convergence_threshold = convergence_threshold
63+
64+
# Initialize step_count
65+
self.step_count = 0 # This will track the number of steps taken
66+
67+
# Create agents
68+
for i in range(self.num_agents):
69+
step_size = (
70+
max_agent_step_size if i % 2 == 0 else 1
71+
) # Half agents have large step size, half small
72+
agent = BoundedConfidenceAgent(
73+
i, self, epsilon, max_agent_step_size=step_size
74+
)
75+
self.grid.place_agent(
76+
agent,
77+
(
78+
self.random.randrange(self.grid.width),
79+
self.random.randrange(self.grid.height),
80+
),
81+
)
82+
agent.pos_history.append(
83+
agent.pos
84+
) # Initialize the position history with the starting position
85+
self.schedule.add(agent)
86+
87+
self.datacollector = DataCollector(
88+
agent_reporters={"Opinion": "opinion"},
89+
model_reporters={"Avg_Similarity": self.calculate_similarity},
90+
)
91+
92+
def calculate_similarity(self):
93+
"""Calculate average pairwise similarity in agent opinions."""
94+
opinions = np.array([agent.opinion for agent in self.schedule.agents])
95+
pairwise_diffs = np.abs(opinions[:, None] - opinions[None, :])
96+
similarity = 1 - pairwise_diffs # Similarity is inverse of difference
97+
avg_similarity = np.mean(similarity)
98+
return avg_similarity
99+
100+
def step(self):
101+
self.datacollector.collect(self)
102+
self.schedule.step()
103+
self.step_count += 1
104+
105+
# Check stopping conditions
106+
if self.check_convergence() or self.step_count >= self.max_steps:
107+
self.running = False
108+
109+
def check_convergence(self):
110+
"""Check if the opinions have converged."""
111+
opinions = np.array([agent.opinion for agent in self.schedule.agents])
112+
max_diff = np.max(np.abs(opinions[:, None] - opinions[None, :]))
113+
return max_diff < self.convergence_threshold

icsspy/abms/sir.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
from mesa import Agent, Model
2+
from mesa.datacollection import DataCollector
3+
from mesa.space import MultiGrid
4+
from mesa.time import RandomActivation
5+
6+
7+
class SIRAgent(Agent):
8+
def __init__(
9+
self, unique_id, model, recovery_time_range=(8, 12), max_agent_step_size=1
10+
):
11+
12+
super().__init__(unique_id, model)
13+
self.state = "S"
14+
self.infected_time = 0
15+
self.recovery_time_range = recovery_time_range
16+
self.max_agent_step_size = max_agent_step_size
17+
self.pos_history = []
18+
self.interactions = {}
19+
20+
def step(self):
21+
if self.state == "I":
22+
self.infected_time += 1
23+
# check if agent is within the recovery window
24+
if self.infected_time >= self.recovery_time_range[0]:
25+
# if so, recover probabilistically
26+
if (
27+
self.random.random() < 0.5
28+
or self.infected_time >= self.recovery_time_range[1]
29+
):
30+
self.state = "R"
31+
32+
# if still infected, try to infect others
33+
if self.state == "I":
34+
neighbors = self.model.grid.get_neighbors(
35+
self.pos, moore=True, include_center=False
36+
)
37+
for neighbor in neighbors:
38+
if (
39+
neighbor.state == "S"
40+
and self.random.random() < self.model.infection_rate
41+
):
42+
neighbor.state = "I"
43+
44+
possible_moves = self.model.grid.get_neighborhood(
45+
self.pos, moore=True, include_center=False, radius=self.max_agent_step_size
46+
)
47+
48+
new_position = self.random.choice(possible_moves)
49+
self.model.grid.move_agent(self, new_position)
50+
self.pos_history.append(new_position)
51+
52+
# record interactions with other agents in this step
53+
neighbors = self.model.grid.get_neighbors(
54+
self.pos, moore=True, include_center=False
55+
)
56+
57+
for neighbor in neighbors:
58+
if neighbor.unique_id in self.interactions:
59+
self.interactions[neighbor.unique_id] += 1
60+
else:
61+
self.interactions[neighbor.unique_id] = 1
62+
63+
64+
class SIRModel(Model):
65+
def __init__(
66+
self,
67+
grid_width,
68+
grid_height,
69+
N,
70+
infection_rate,
71+
recovery_time_range,
72+
max_agent_step_size=1,
73+
n_initial_infections=1,
74+
max_iterations=1000,
75+
change_threshold=0.001,
76+
):
77+
78+
super().__init__()
79+
self.num_agents = N
80+
self.grid = MultiGrid(grid_width, grid_height, True)
81+
self.schedule = RandomActivation(self)
82+
self.infection_rate = infection_rate
83+
self.recovery_time_range = recovery_time_range
84+
self.max_agent_step_size = max_agent_step_size
85+
self.max_iterations = max_iterations
86+
self.current_iteration = 0
87+
self.change_threshold = change_threshold
88+
# track the ratio of infected agents in the previous step
89+
self.previous_infected_ratio = None
90+
91+
# create agents
92+
for i in range(self.num_agents):
93+
# half the agents will have a large step size, half small
94+
step_size = max_agent_step_size if i % 2 == 0 else 1
95+
a = SIRAgent(i, self, recovery_time_range, max_agent_step_size=step_size)
96+
self.grid.place_agent(
97+
a,
98+
(
99+
self.random.randrange(self.grid.width),
100+
self.random.randrange(self.grid.height),
101+
),
102+
)
103+
# initialize the position history with the starting position
104+
a.pos_history.append(a.pos)
105+
self.schedule.add(a)
106+
107+
# randomly infect a specified number of agents
108+
initial_infected_agents = self.random.sample(
109+
self.schedule.agents, n_initial_infections
110+
)
111+
for agent in initial_infected_agents:
112+
agent.state = "I"
113+
agent.infected_time = 0 # initialize infection duration
114+
115+
self.datacollector = DataCollector(
116+
model_reporters={
117+
"Susceptible": lambda m: self.count_state("S"),
118+
"Infected": lambda m: self.count_state("I"),
119+
"Recovered": lambda m: self.count_state("R"),
120+
},
121+
agent_reporters={
122+
"State": "state",
123+
"Infection_Duration": "infection_duration",
124+
},
125+
)
126+
# this line is necessary for doing multiple model runs simultaneously
127+
# we will do this later in the lecture
128+
self.running = True
129+
130+
def step(self):
131+
self.datacollector.collect(self)
132+
self.schedule.step()
133+
self.current_iteration += 1
134+
135+
# check for stopping condition based on maximum iterations
136+
if self.current_iteration >= self.max_iterations:
137+
self.running = False
138+
139+
# check for stopping condition based on change in infected ratio
140+
current_infected_ratio = self.count_state("I")
141+
if self.previous_infected_ratio is not None:
142+
change = abs(current_infected_ratio - self.previous_infected_ratio)
143+
if change < self.change_threshold:
144+
self.running = False
145+
self.previous_infected_ratio = current_infected_ratio
146+
147+
def count_state(self, state_name):
148+
count = sum([1 for a in self.schedule.agents if a.state == state_name])
149+
return count / self.num_agents

0 commit comments

Comments
 (0)