mesa-geo introduction(1)

war spread model(内战传播模型):

A simplified ABM (Agent-Based Model) describing spatial dependency and spatial heterogeneity:

  • Spatial dependency: This refers to the phenomenon that an area is more likely to experience civil war if its surrounding areas are experiencing civil war.
  • Spatial heterogeneity: Based on existing research, we define spatial heterogeneity as the proximity to the capital. Areas closer to the capital are more prone to war, as the upper echelons of the country often initiate wars and the central government is typically located in the capital.

Here, to let you get the correct data type conveniently,this blog still use the official geo-data. You can try to get the TorontoNeighbourhoods.geojson from the official website, and have a try:
https://github.com/projectmesa/mesa-examples/blob/main/gis/geo_sir/data/TorontoNeighbourhoods.geojson

import package

1
2
3
import mesa 
import mesa_geo as mg
import mesa_geo.visualization as mgv

create regionAgent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class regionAgent(mg.GeoAgent):

def __init__(self, unique_id, model, geometry, crs,agent_type ="no_war",war_infection_risk=0.1 ,war_recovery_rate=0.2):
super().__init__(unique_id, model, geometry, crs)
self.atype= agent_type
self.war_infection_risk=war_infection_risk
self.war_revovery_rate=war_recovery_rate
self.in_war_step=0

def __repr__(self):
return "region" + str(self.unique_id)

#If an agent that is not currently engaged in war is within a fixed distance of an agent that is engaged in war
# there is also a possibility that war may directly occur between them (depending on the threshold set).
# Agents that are closer to the capital are more likely to experience war (a weight can be set here)
def war_spread(self):
if self.atype == "no_war":
neighbors= self.model.space.get_neighbors_within_distance(
self,self.model.exposure_distance
)

for neighnor in neighbors:
#useLdistance(self, agent_a, agent_b)或者agent_a.geometry.distance(agent_b.geometry)
A_C_distance=self.model.space.distance(agent_a=self, agent_b=self.model.capitalAgent)
if (neighnor.atype == "in_war"
and A_C_distance*0.001+self.random.random() < self.model.threshold_war_infection
):
self.atype = "in_war"
self.in_war_step=0
break

#set step for in-war districts
elif self.atype=="in_war":
if self.in_war_step<3:
if self.random.random() < self.war_revovery_rate:
self.atype = "no_war"
self.in_war_step=self.in_war_step+1
else:
self.in_war_step=0

def step(self):
self.war_spread()
self.model.counts[self.atype] += 1

model part: combine agents, time and space

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def get_in_war_count(model):
return model.counts["in_war"]

def get_no_war_count(model):
return model.counts["no_war"]

class war_model(mesa.Model):
#here I just use the data in official file
geojson_regions = "D:\...\TorontoNeighbourhoods.geojson"
unique_id = "HOODNUM"

def __init__(self,int_war_rate=0.2,war_revovery_rate=0.6,threshold_war_infection=1.5,exposure_distance=1000):
super().__init__()

self.int_war_rate=int_war_rate
self.war_recovery_rate=war_revovery_rate
self.threshold_war_infection=threshold_war_infection
self.exposure_distance=exposure_distance


self.schedule=mesa.time.RandomActivation(self)
self.space=mg.GeoSpace(warn_crs_conversion=False)
#capital id
self.capital_id=105


self.counts=None
self.reset_counts()

self.datacollector = mesa.DataCollector(
{
"no_war": get_no_war_count,
"in_war": get_in_war_count
}
)


ac=mg.AgentCreator(
regionAgent,
model=self)
region_agents = ac.from_file(self.geojson_regions,unique_id=self.unique_id)

for j in range(len(region_agents)):
if self.random.random() < self.int_war_rate:
#if self.random.random() < 0.2:
region_agents[j].atype="in_war"

self.space.add_agents(region_agents)

# find capital agent id
self.capitalAgent = next((agent for agent in region_agents if agent.unique_id == self.capital_id), None)


for agent in region_agents:
self.schedule.add(agent)


def reset_counts(self):
self.counts = {
"in_war": 0,
"no_war":0
}

def step(self):
self.reset_counts()
self.schedule.step()
self.datacollector.collect(self)

run war spread model

1
2
3
model = war_model()
for i in range(10):
model.step()

model visulization

1
model.datacollector.get_model_vars_dataframe()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

def war_model_draw(agent):
"""
Portrayal Method for canvas
"""

portrayal = {}
if isinstance(agent, regionAgent):
if agent.atype == "in_war":
portrayal["color"] = "Red"
else:
portrayal["color"] = "green"

return portrayal


model_params = {
"int_war_rate": {
"type": "SliderFloat",
"value": 0.2,
"label": "Population Size",
"min": 0,
"max": 0.6,
"step": 0.05,
},
"threshold_war_infection": {
"type": "SliderFloat",
"value": 1.5,
"label": "Population Size",
"min": 1.2,
"max": 2,
"step": 0.1,
},
"exposure_distance": {
"type": "SliderInt",
"value": 1000,
"label": "Population Size",
"min": 500,
"max": 2000,
"step": 100,
},
"war_revovery_rate": {
"type": "SliderFloat",
"value": 0.6,
"label": "Maximum Number of Steps to Recover",
"min": 0.1,
"max": 1,
"step": 0.1,
}}

page = mgv.GeoJupyterViz(
war_model,
model_params,
measures= [ ["in_war", "no_war"]],
name="war spread",
agent_portrayal=war_model_draw,
zoom=12,
scroll_wheel_zoom=False
)

visualization in the Jupyter notebook

1
page

reference

Chen Chong, Hu Jingtian. Spatial Dependence and the Prediction of Armed Conflicts [J]. International Political Science, 2022, 7(2): 86-123.
https://cc458.github.io/files/chen_hu_2022qjip.pdf


mesa-geo introduction(1)
http://example.com/2024/11/12/war-abm-mesageo/
Author
zhuoyu
Posted on
November 12, 2024
Licensed under