mesa-geo introduction(2)

tutorial: mesa-geo Simulation of national attack and defense in war

This tutorial designs three types of agents: German Army Agent, French Army Agent, and Region Agent. It aims to simulate the processes of engagement and defense through the method of Agent-Based Modeling (ABM).

import the packages

1
2
3
4
5

import mesa
import mesa_geo as mg
import mesa_geo.visualization as mgv
from shapely.geometry import Point

define the german agent(attack agent)

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
class german_army(mg.GeoAgent):
def __init__(self, unique_id, model, geometry, crs ,attack_value, attack_speed, soldier_number,enermy_distance, wartype="prepared"):
super().__init__(unique_id, model, geometry, crs)
self.breed="german_army"
self.attack_value=attack_value
self.attack_speed=attack_speed
self.soldier_number=soldier_number
self.enermy_distance=enermy_distance
self.wartype= wartype
self.set_place=0

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

def calculate_distance(self,x):
return self.model.space.distance(self, x)

def step(self):
if self.wartype=="prepared":
#When encountering the French army, it will approach and engage in battle with them.
neighbors=self.model.space.get_neighbors_within_distance(self,self.enermy_distance)
neighbors_setplace=self.model.space.get_neighbors_within_distance(self,2000000)
enermy_army_neighbors=[]
for agent in neighbors:
if agent is not None and agent.breed=="France_army" and agent.wartype!="dead":
enermy_army_neighbors.append(agent)
if enermy_army_neighbors is None:
#Proceed to the nearest base location.
set_neighbors=[]
for agent in neighbors_setplace:
if agent.wartype=="set_a_place":
set_neighbors.append(agent)
closest_set = min(set_neighbors, key=lambda x: self.calculate_distance(x))
#go to the place near that:
x0=closest_set.geometry.x
y0=closest_set.geometry.y
x_moveto = x0 + self.random.randint(100,200)
y_moveto = y0 + self.random.random(100,200)
self.geometry=Point(x_moveto,y_moveto)

else:
if enermy_army_neighbors:
#To select the nearest army and move towards its vicinity, you can use a combination of the min function and a lambda function
closest_enermy = min(enermy_army_neighbors, key=lambda x: self.calculate_distance(x))

x0=closest_enermy.geometry.x
y0=closest_enermy.geometry.y
x_moveto = x0 + self.random.randint(100,200)
y_moveto = y0 + self.random.randint(100,200)
self.geometry=Point(x_moveto,y_moveto)
#To compare numerical values with the French army, and if victorious, stay for a step to set up a stronghold (transition to stronghold status), you would typically need to implement some game logic that involves combat resolution and state transitions. Here’s a conceptual outline in Python-like pseudocode
if self.attack_speed > closest_enermy.attack_speed:
closest_enermy.soldier_number= closest_enermy.soldier_number-1.5*self.attack_value*self.soldier_number
self.soldier_number=self.soldier_number-self.attack_value*self.soldier_number
else:
closest_enermy.soldier_number= closest_enermy.soldier_number-0.8*self.attack_value*self.soldier_number
self.soldier_number=self.soldier_number-self.attack_value*self.soldier_number

if closest_enermy.soldier_number<=0 and self.soldier_number > 0:
closest_enermy.wartype="dead"
self.wartype="set_a_place"
self.set_place=self.set_place+1
elif closest_enermy.soldier_number<=0 and self.soldier_number <= 0:
self.wartype="dead"
closest_enermy.wartype="dead"
elif closest_enermy.soldier_number >0 and self.soldier_number <= 0:
self.wartype="dead"
closest_enermy.wartype="prepared"
elif closest_enermy.soldier_number >0 and self.soldier_number > 0:
closest_enermy.wartype="in_war"
self.wartype="in_war"


elif self.wartype=="in_war":
neighbors=self.model.space.get_neighbors_within_distance(self,500)
fclosest_enermy=[neighbor for neighbor in neighbors if neighbor.breed=="France_army"]
if fclosest_enermy:
closest_enermy = fclosest_enermy[0]
else:
closest_enermy = None

closest_enermy.soldier_number= closest_enermy.soldier_number-self.attack_value*self.soldier_number
self.soldier_number=self.soldier_number-self.attack_value*self.soldier_number

if closest_enermy.soldier_number<=0:
closest_enermy.wartype="dead"
self.wartype="set_a_place"
elif closest_enermy.soldier_number >0 and self.soldier_number <= 0:
self.wartype="dead"
closest_enermy.wartype="prepared"
elif closest_enermy.soldier_number >0 and self.soldier_number > 0:
closest_enermy.wartype="in_war"
self.wartype="in_war"

elif self.wartype=="set_a_place":
self.set_place=self.set_place+1

if self.set_place >=2:

self.soldier_number=self.soldier_number*1.2
self.wartype="prepared"




self.model.counts[self.wartype] = self.model.counts[self.wartype]+1

define the french class for defence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class france_army(mg.GeoAgent):
def __init__(self, unique_id, model, geometry, crs,soldier_number,attack_speed):
super().__init__(unique_id, model, geometry, crs)
self.breed = "France_army"
self.soldier_number=soldier_number
self.attack_speed=attack_speed
self.wartype= "prepared"

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

def step(self):
#有新的援兵动员
if self.wartype=="in_war":
self.soldier_number=self.soldier_number*1.5
elif self.wartype=="prepared":
self.soldier_number=self.soldier_number

self.model.counts[self.wartype] += 1

To set up region agent in border area

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
class regionAgent(mg.GeoAgent):
def __init__(self,unique_id, model, geometry, crs,wartype="not in war"):
super().__init__(unique_id, model, geometry, crs)
self.breed="region"

self.wartype=wartype

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


def hotspot(self):
neighbors = self.model.space.get_intersecting_agents(self)
waragent = [
agent for agent in neighbors if agent.wartype == "in_war"or agent.wartype=="set_a_place"
]
if waragent:
self.wartype="region in war"
else:
self.wartype="not in war"

def step(self):
self.hotspot()
self.model.counts[self.wartype] += 1

```



```python
model function to count

def get_in_war_count(model):
return model.counts["in_war"]


def get_set_a_place_count(model):
return model.counts["set_a_place"]


def get_prepared_count(model):
return model.counts["prepared"]


def get_dead_count(model):
return model.counts["dead"]

def get_region_in_war_count(model):
return model.counts["region in war"]

def get_region_not_in_war(model):
return model.counts["not in war"]

define the battle model

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
class warmodel(mesa.Model):

geojson_regions = "\german.geojson"
unique_id = "regionnum"

def __init__(self,german_army_size=60,france_army_size=60):
super().__init__()
self.german_army_size=german_army_size
self.france_army_size=france_army_size

self.schedule = mesa.time.RandomActivationByType(self)
self.space = mg.GeoSpace(warn_crs_conversion=False)

self.counts = None #added
self.reset_counts() #added

self.datacollector = mesa.DataCollector(
{
"in_war": get_in_war_count,
"set_a_place":get_set_a_place_count,
"prepared": get_prepared_count,
"dead": get_dead_count,
"region in war":get_region_in_war_count,
"not in war":get_region_not_in_war

}
)

ac = mg.AgentCreator(regionAgent, model=self)

region_agents=ac.from_file(
self.geojson_regions, unique_id=self.unique_id
)

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

#put agents in the regions

for i in range(german_army_size):
attack_value=0.6 + self.random.random() * 0.5
attack_speed= self.random.randint(8,20)
soldier_number=self.random.randint(3000,6000)
enermy_distance=90000

unique_german_army = mg.AgentCreator(
german_army,
model=self,
crs=self.space.crs,
agent_kwargs={"attack_value": attack_value,
"attack_speed":attack_speed,
"soldier_number":soldier_number,
"enermy_distance": enermy_distance
}
)

x_home, y_home = self.find_home(region_agents,"german")

german_army_agent = unique_german_army.create_agent(
Point(x_home, y_home), "G" + str(i),
)
self.space.add_agents(german_army_agent)
self.schedule.add(german_army_agent)

for i in range(france_army_size):

attack_speed= self.random.randint(6,10)
soldier_number=self.random.randint(4000,8000)

unique_german_army = mg.AgentCreator(
france_army,
model=self,
crs=self.space.crs,
agent_kwargs={
"attack_speed":attack_speed,
"soldier_number":soldier_number
}
)

x_home, y_home = self.find_home(region_agents,"france")

france_army_agent = unique_german_army.create_agent(
Point(x_home, y_home), "F" + str(i),
)
self.space.add_agents(france_army_agent)
self.schedule.add(france_army_agent)



def find_home(self,region_agents,country):
self.country=country
select_in_germanregions=[]
select_in_franceregions=[]
for agent in region_agents:
if "g" in agent.unique_id:
select_in_germanregions.append(agent)
elif "f" in agent.unique_id:
select_in_franceregions.append(agent)

if self.country=="german":
this_region = self.random.randint(
0, len(select_in_germanregions) - 1
) # Region where agent starts
center_x, center_y = select_in_germanregions[
this_region
].geometry.centroid.coords.xy
this_bounds = select_in_germanregions[this_region].geometry.bounds
spread_x = int(
this_bounds[2] - this_bounds[0]
) # Heuristic for agent spread in region
spread_y = int(this_bounds[3] - this_bounds[1])
this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2
this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2
return this_x,this_y

elif self.country=="france":
this_region = self.random.randint(
0, len(select_in_franceregions) - 1
) # Region where agent starts
center_x, center_y = select_in_franceregions[
this_region
].geometry.centroid.coords.xy
this_bounds = select_in_franceregions[this_region].geometry.bounds
spread_x = int(
this_bounds[2] - this_bounds[0]
) # Heuristic for agent spread in region
spread_y = int(this_bounds[3] - this_bounds[1])
this_x = center_x[0] + self.random.randint(0, spread_x) - spread_x / 2
this_y = center_y[0] + self.random.randint(0, spread_y) - spread_y / 2
return this_x,this_y

def reset_counts(self):
self.counts = {
"in_war": 0,
"set_a_place": 0,
"prepared": 0,
"dead": 0,
"region in war":0,
"not in war":0
}

def step(self):

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

Instantiate the model and run it.

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

do the visuliztion

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
61
62
63
def warmodel_draw(agent):
"""
Portrayal Method for canvas
"""

portrayal = {}
if isinstance(agent, german_army):
if agent.wartype == "in_war":
portrayal["color"] = "red"
elif agent.wartype == "set_a_place":
portrayal["color"] = "yellow"
elif agent.wartype == "prepared":
portrayal["color"] = "Blue"
else:
portrayal["color"] = "grey"

if isinstance(agent, france_army):
if agent.wartype == "in_war":
portrayal["color"] = "pink"
elif agent.wartype == "prepared":
portrayal["color"] = "green"
else:
portrayal["color"] = "black"

if isinstance(agent, regionAgent):
if agent.wartype == "region in war":
portrayal["color"] = "Red"
else:
portrayal["color"] = "green"

return portrayal


model_params = {
"german_army_size": {
"type": "SliderInt",
"value": 60,
"label": "german_army_size",
"min": 30,
"max": 100,
"step": 1,
},
"france_army_size": {
"type": "SliderInt",
"value": 60,
"label": "france_army_size",
"min": 30,
"max": 100,
"step": 1,
}}


page = mgv.GeoJupyterViz(
warmodel,
model_params,
measures= [["in_war", "prepared", "set_a_place", "dead"], ["region in war", "not in war"]],
name="warmodel_german-france",
agent_portrayal=warmodel_draw,
zoom=10,
scroll_wheel_zoom=False
)
# This is required to render the visualization in the Jupyter notebook
page


mesa-geo introduction(2)
http://example.com/2024/12/15/mesa-geo-intro-(2)/
Author
zhuoyu
Posted on
December 15, 2024
Licensed under