I am using the "mesa" library of Python to define an agent based model simulate rebellion action under censorship enviroment. Right now the model is built and successfully shown in interface, but unfortunately agents in my model can not move. I don't know whether it's because the math equation I designed has problem or it's coding logic has something wrong.
As a sociology student I'm desperate about current situationa, would someone please hlep me with it! Thank you from the bottom of my heart!
Frist of all, there are information spys and citizens in our model, and citzens possess 6 variables:
1.self.information, denote information citizen possesed at begening
2.self.censorship, they can sense the hardness of information censorship and its equally percived by all citizens
3.self.vision is the number of neighbours agent could see, it is setted as 7
- self.risk_preference, their risk preferenct toward rebellion act, it setted as normally randomly distributed to each citizens
5.self.shareinfo_prob,citizens information sharing problility is influenced by censorship, denoted as self.information * (1 - self.censorship)
6.self.punished_prob, to join rebellion act, they first measure their probability get punished by information, it has somthing to do with spy/rebellion citizens ratio.
7.self.sympathy, citizens sympathy towards others, it is ralte with the number of punished neighbour they could see multiple the information they could gathered from their neighbours
citizen has three status: normal citizen, rebellion citizen, punished citizen, the trasfer depends on calculation : sympathy -punished_prob+ shareinfo_prob, if the value > risk_preference, normal citizens turn into rebellion citizens
Spy won't change in our model, it walk around and punish rebellion citizens.
class Citizen (Agent):
def __init__(
self,
unique_id,
model,
pos,
vision,
risk_preference,
information,
censorship,
):
super().__init__(unique_id, model)
self.breed = "citizen"
self.pos = pos
self.information = random.normal(loc=0.5)
self.censorship = censorship
self.risk_preference = risk_preference
self.condition = "normal citizen"
self.vision = vision
self.punishment_sentence = 0
self.shareinfo_prob = self.information * (1 - self.censorship)
self.punished_prob = None
self.sympathy = self.update_sympathy()
"""
behavior
"""
def step(self):
"""
Decide whether to join rebellion acts, then move if applicable.
"""
if self.punishment_sentence:
self.punishment_sentence -= 1
return # no other changes or movements if agent is in punishment
self.update_neighbors()
self.update_estimated_punish_probability()
# print(f'{type(self.sympathy)=}')
# print(f'{type(self.punished_prob)=}')
# print(f'{type(self.shareinfo_prob)=}')
# print(f'{type(self.risk_preference)=}')
if (self.update_sympathy- self.punished_prob+ self.shareinfo_prob) > self.risk_preference:
self.condition = "rebellion"
else:
self.condition = "normal citizen"
if self.model.movement and self.empty_neighbors:
new_pos = self.random.choice(self.empty_neighbors)
self.model.grid.move_agent(self, new_pos)
def update_neighbors(self):
"""
Look around and see who my neighbors are
"""
self.neighborhood = self.model.grid.get_neighborhood(
self.pos, moore=False, radius=1
)
self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
self.empty_neighbors = [
c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
]
def update_estimated_punish_probability(self):
"""
Based on the ratio of sps to rebellion citizen in my neighborhood, estimate the
p(Punishment | I join collective action)=slf.punished_prob
"""
spys_in_vision = len([c for c in self.neighbors if c.breed == "spy"])
rebellions_in_vision = 1.0 # citizen counts herself
for c in self.neighbors:
if (
c.breed == "citizen"
and c.condition == "rebellion"
and c.punishment_sentence == 0
):
rebellions_in_vision += 1
self.punished_prob = 1 - math.exp(
-1 * self.model.punishment_prob_constant * (spys_in_vision / rebellions_in_vision)
)
def update_sympathy(self):
self.update_neighbors()
normal_neighbors = []
for agent in self.neighbors:
if (
agent.breed == "citizen"
and agent.condition == "normal citizen"
and agent.punishment_sentence == 0
):
normal_neighbors.append(agent)
punished_in_vision = 0.0
for c in self.neighbors:
if (
c.breed == "normal citizen"
and c.condition == "punished"
and c.punishment_sentence == 0
):
punished_in_vision += 1
self.sympathy =1 - math.exp(-1*normal_neighbors.shareinfo_prob*punished_in_vision)
class Spy(Agent):
def __init__(self, unique_id, model, pos, vision):
"""
Create a new Spy.
Args:
unique_id: unique int
x, y: Grid coordinates
vision: number of cells in each direction (N, S, E and W) that
agent can censor. Exogenous.
model: model instance
"""
super().__init__(unique_id, model)
self.breed = "spy"
self.pos = pos
self.vision = vision
def step(self):
"""
Inspect local vision and punish a random active agent. Move if
applicable.
"""
self.update_neighbors()
rebellion_neighbors = []
for agent in self.neighbors:
if (
agent.breed == "citizen"
and agent.condition == "rebellion"
and agent.punishment_sentence == 0
):
rebellion_neighbors.append(agent)
if rebellion_neighbors:
punishcollective = self.random.choice(rebellion_neighbors)
sentencecollective = self.random.randint(0, self.model.max_punishment_term)
punishcollective.punishment_sentence = sentencecollective
if self.model.movement and self.empty_neighbors:
new_pos = self.random.choice(self.empty_neighbors)
self.model.grid.move_agent(self, new_pos)
def update_neighbors(self):
"""
Look around and see who my neighbors are.
"""
self.neighborhood = self.model.grid.get_neighborhood(
self.pos, moore=False, radius=1
)
self.neighbors = self.model.grid.get_cell_list_contents(self.neighborhood)
self.empty_neighbors = [
c for c in self.neighborhood if self.model.grid.is_cell_empty(c)
]
from mesa import Model
from mesa.time import RandomActivation
from mesa.space import Grid
from mesa.datacollection import DataCollector
class Sympathy_CA(Model):
def __init__(
self,
height=40,
width=40,
citizen_density=0.7,
spy_density=0.074,
#citizen vision should be adjustable
citizen_vision=7,
spy_vision=7,
max_punishment_term=1000,
punishment_prob_constant=2.3,
censorship=0.8,
movement=True,
max_iters=1000,
):
super().__init__()
self.height = height
self.width = width
self.citizen_density = citizen_density
self.spy_density = spy_density
self.citizen_vision = citizen_vision
self.spy_vision = spy_vision
self.max_punishment_term = max_punishment_term
self.punishment_prob_constant = punishment_prob_constant
self.censorship = censorship
self.movement = movement
self.max_iters = max_iters
self.iteration = 0
self.schedule = RandomActivation(self)
self.grid = Grid(height, width, torus=True)
model_reporters = {
"normal citizen": lambda m: self.count_type_citizens(m, "normal citizen"),
"rebellion": lambda m: self.count_type_citizens(m, "rebellion"),
"punished": lambda m: self.count_punished(m),
}
agent_reporters = {
"x": lambda a: a.pos[0],
"y": lambda a: a.pos[1],
"breed": lambda a: a.breed,
"punishment_sentence": lambda a: getattr(a, "punishment_sentence", None),
"condition": lambda a: getattr(a, "condition", None),
"punished_prob": lambda a: getattr(a, "punished_prob", None),
}
self.datacollector = DataCollector(
model_reporters=model_reporters,
agent_reporters=agent_reporters
)
unique_id = 0
if self.spy_density + self.citizen_density > 1:
raise ValueError("Spy density + citizen density must be less than 1")
for (contents, x, y) in self.grid.coord_iter():
#print(x)
if self.random.random() < self.spy_density:
spy = Spy(unique_id, self, (x, y), vision=self.spy_vision)
unique_id += 1
self.grid[y][x] = spy
self.schedule.add(spy)
elif self.random.random() < (self.spy_density + self.citizen_density):
citizen = Citizen(
unique_id,
self,
(x, y),
vision=self.citizen_vision,
risk_preference=random.normal(loc=0.5),
information=random.normal(loc=0.5),
censorship=self.censorship
)
unique_id += 1
self.grid[y][x] = citizen
self.schedule.add(citizen)
self.running = True
self.datacollector.collect(self)
def step(self):
"""
Advance the model by one step and collect data.
"""
self.schedule.step()
self.datacollector.collect(self)
self.iteration += 1
if self.iteration > self.max_iters:
self.running = False
@staticmethod
def count_type_citizens(model, condition, exclude_punished=True):
"""
Helper method to count agents by rebellion/normal citizen
"""
count = 0
for agent in model.schedule.agents:
if agent.breed == "spy":
continue
if exclude_punished and agent.punishment_sentence:
continue
if agent.condition == condition:
count += 1
return count
@staticmethod
def count_punished(model):
"""
Helper method to count punished agents.
"""
count = 0
for agent in model.schedule.agents:
if agent.breed == "citizen" and agent.punishment_sentence:
count += 1
return count
SPY_COLOR = "#000000"
from mesa.visualization.ModularVisualization import ModularServer
from mesa.visualization.UserParam import UserSettableParameter
from mesa.visualization.modules import CanvasGrid
def citizen_spy_portrayal(agent):
if agent is None:
return
portrayal = {
"Shape": "circle",
"x": agent.pos[0],
"y": agent.pos[1],
"Filled": "true",
}
if type(agent) is Citizen:
color = (
AGENT_NORMAL_COLOR if agent.condition == "normal citizen" else AGENT_REBELLION_COLOR
)
color = PUNISHED_COLOR if agent.punishment_sentence else color
portrayal["Color"] = color
portrayal["r"] = 0.8
portrayal["Layer"] = 0
elif type(agent) is Spy:
portrayal["Color"] = SPY_COLOR
portrayal["r"] = 0.5
portrayal["Layer"] = 1
return portrayal
model_params = {
"height": 40,
"width": 40,
"censorship": UserSettableParameter(
"slider",
"censorship",
0.8,
0.1,
1.0,
0.1
),
"citizen_density": UserSettableParameter(
"slider",
"citizen_density",
0.7,
0.1,
1.0,
0.1
),
"spy_density": UserSettableParameter(
"slider",
"spy_density",
0.074,
0.001,
0.300,
0.001
),}
canvas_element = CanvasGrid(citizen_spy_portrayal, 40, 40, 480, 480)
server = ModularServer(Sympathy_CA,[canvas_element], "Sympathy_CA", model_params)
server.launch()
when I run this model, it shows:
Socket opened!
{"type":"reset"}
WARNING:tornado.access:404 GET /favicon.ico (127.0.0.1) 1.02ms
{"type":"get_step","step":1}
ERROR:tornado.application:Uncaught exception GET /ws (127.0.0.1)
HTTPServerRequest(protocol='http', host='127.0.0.1:8521', method='GET', uri='/ws',
version='HTTP/1.1', remote_ip='127.0.0.1')
Traceback (most recent call last):
File "C:\Users\ug\AppData\Local\Programs\Python\Python39\lib\site-packages\tornado\websocket.py", line 647, in _run_callback
result = callback(*args, **kwargs)
File "C:\Users\ug\AppData\Local\Programs\Python\Python39\lib\site-packages\mesa\visualization\ModularVisualization.py", line 207, in on_message
self.application.model.step()
File "d:\CAprogram\sympathy.py", line 261, in step
self.schedule.step()
File "C:\Users\ug\AppData\Local\Programs\Python\Python39\lib\site-packages\mesa\time.py", line 127, in step
agent.step()
File "d:\CAprogram\sympathy.py", line 53, in step
if (self.update_sympathy- self.punished_prob+ self.shareinfo_prob) > self.risk_preference:
TypeError: unsupported operand type(s) for -: 'method' and 'float'