11import math
2+ import enum
23
34from mesa .time import RandomActivation
45from mesa .space import SingleGrid
56
67from mesa .experimental .datacollection .mesa_classes import ObservableModel , ObservableAgent
78from mesa .experimental .datacollection .collectors import DataCollector , collect , Measure
9+ from mesa .experimental .datacollection .mesa_classes import ConditionalAgentSet
10+ from mesa .experimental .datacollection .pubsub import ObservableState
811
912
1013class EpsteinAgent (ObservableAgent ):
@@ -13,6 +16,12 @@ def __init__(self, unique_id, model, vision):
1316 self .vision = vision
1417
1518
19+ class CitizenState (enum .StrEnum ):
20+ ACTIVE = "active"
21+ QUIESCENT = "quiescent"
22+ JAILED = "jailed"
23+
24+
1625class Citizen (EpsteinAgent ):
1726 """
1827 A member of the general population, may or may not be in active rebellion.
@@ -37,6 +46,7 @@ class Citizen(EpsteinAgent):
3746 arrest_probability: agent's assessment of arrest probability, given
3847 rebellion
3948 """
49+ condition = ObservableState ()
4050
4151 def __init__ (
4252 self ,
@@ -68,7 +78,7 @@ def __init__(
6878 self .regime_legitimacy = regime_legitimacy
6979 self .risk_aversion = risk_aversion
7080 self .threshold = threshold
71- self .condition = "Quiescent"
81+ self .condition = CitizenState . QUIESCENT
7282 self .jail_sentence = 0
7383 self .grievance = self .hardship * (1 - self .regime_legitimacy )
7484 self .arrest_probability = None
@@ -77,16 +87,20 @@ def step(self):
7787 """
7888 Decide whether to activate, then move if applicable.
7989 """
80- if self .jail_sentence :
90+ if self .condition == CitizenState . JAILED :
8191 self .jail_sentence -= 1
8292 return # no other changes or movements if agent is in jail.
93+
8394 self .update_neighbors ()
8495 self .update_estimated_arrest_probability ()
96+
8597 net_risk = self .risk_aversion * self .arrest_probability
86- if self .grievance - net_risk > self .threshold :
87- self .condition = "Active"
88- else :
89- self .condition = "Quiescent"
98+ if (self .grievance - net_risk > self .threshold ) and (self .condition != CitizenState .ACTIVE ):
99+ self .condition = CitizenState .ACTIVE
100+ elif self .condition == CitizenState .ACTIVE :
101+ self .condition = CitizenState .QUIESCENT
102+ # else, agent is quiescent and stays that way
103+
90104 if self .model .movement and self .empty_neighbors :
91105 new_pos = self .random .choice (self .empty_neighbors )
92106 self .model .grid .move_agent (self , new_pos )
@@ -113,14 +127,17 @@ def update_estimated_arrest_probability(self):
113127 for c in self .neighbors :
114128 if (
115129 isinstance (c , Citizen )
116- and c .condition == "Active"
117- and c .jail_sentence == 0
130+ and c .condition == CitizenState .ACTIVE
118131 ):
119132 actives_in_vision += 1
120133 self .arrest_probability = 1 - math .exp (
121134 - 1 * self .model .arrest_prob_constant * (cops_in_vision / actives_in_vision )
122135 )
123136
137+ def sent_to_jail (self , sentence ):
138+ self .condition = CitizenState .JAILED
139+ self .jail_sentence = sentence
140+
124141
125142class Cop (EpsteinAgent ):
126143 """
@@ -145,14 +162,12 @@ def step(self):
145162 if (
146163 isinstance (agent , Citizen )
147164 and agent .condition == "Active"
148- and agent .jail_sentence == 0
149165 ):
150166 active_neighbors .append (agent )
151167 if active_neighbors :
152168 arrestee = self .random .choice (active_neighbors )
153169 sentence = self .random .randint (0 , self .model .max_jail_term )
154- arrestee .jail_sentence = sentence
155- arrestee .condition = "Quiescent"
170+ arrestee .sent_to_jail (sentence )
156171 if self .model .movement and self .empty_neighbors :
157172 new_pos = self .random .choice (self .empty_neighbors )
158173 self .model .grid .move_agent (self , new_pos )
@@ -226,12 +241,6 @@ def __init__(
226241 self .schedule = RandomActivation (self )
227242 self .grid = SingleGrid (width , height , torus = True )
228243
229-
230- citizens = m .get_agents_of_type (Citizen )
231-
232- self .quiescent = Measure (citizens , lambda agent_set :
233- agent_set .select (lambda agent : agent .condition == "quiescent" ))
234-
235244 if self .cop_density + self .citizen_density > 1 :
236245 raise ValueError ("Cop density + citizen density must be less than 1" )
237246 for contents , pos in self .grid .coord_iter ():
@@ -252,6 +261,22 @@ def __init__(
252261 self .grid .place_agent (agent , pos )
253262 self .schedule .add (agent )
254263
264+ # static groups
265+ citizens = self .get_agents_of_type (Citizen )
266+ cops = self .get_agents_of_type (Cop )
267+
268+ # conditional groups
269+ self .quiescent = ConditionalAgentSet (citizens , self ,
270+ condition = lambda agent : agent .condition == CitizenState .QUIESCENT )
271+ self .active = ConditionalAgentSet (citizens , self ,
272+ condition = lambda agent : agent .condition == CitizenState .ACTIVE )
273+ self .jailed = ConditionalAgentSet (citizens , self , condition = lambda agent : agent .jail_sentence > 0 )
274+
275+ # measures
276+ self .n_quiescent = Measure (self , self .quiescent , lambda obj : len (obj ))
277+ self .n_active = Measure (self , self .active , lambda obj : len (obj ))
278+ self .n_jailed = Measure (self , self .jailed , lambda obj : len (obj ))
279+
255280 self .running = True
256281
257282 def step (self ):
@@ -265,26 +290,17 @@ def step(self):
265290
266291
267292if __name__ == '__main__' :
268- model = EpsteinCivilViolence ()
269-
270- citizens = model .get_agents_of_type (Citizen )
271- cops = model .get_agents_of_type (Cop )
293+ model = EpsteinCivilViolence (seed = 15 )
272294
273295 dc = DataCollector (model , [
274- collect ("n_quiescent" , citizens , attributes = "condition" ,
275- callable = lambda d : len ([e for e in d if e ["condition" ] == "Quiescent" ])),
276- collect ("n_active" , citizens , attributes = "condition" ,
277- callable = lambda d : len ([e for e in d if e ["condition" ] == "Active" ])),
278- collect ("jail_sentence" , citizens ,
279- callable = lambda d : len ([e for e in d if e ["jail_sentence" ] > 0 ])),
280- collect ("data" , citizens , ["jail_sentence" , "arrest_probability" ])
296+ collect ("model_data" , model , attributes = ["n_quiescent" , "n_active" , "n_jailed" ]),
297+ collect ("jail_sentence" , model .jailed ),
298+ collect ("citizen_data" , model .get_agents_of_type (Citizen ), ["jail_sentence" , "arrest_probability" ])
281299 ])
282300
283301 dc .collect_all ()
284- for _ in range (10 ):
302+ for _ in range (50 ):
285303 model .step ()
286304 dc .collect_all ()
287305
288- print (dc .jail_sentence .to_dataframe ().head ())
289- print (dc .n_quiescent .to_dataframe ().head ())
290- print (dc .data .to_dataframe ().head ())
306+ print (dc .data .to_dataframe ())
0 commit comments