Merge branch 'implementations_tests'
We now have in main.py three functions to check theoretical decays vs actual decays. For this a custom testing environment is set up so no disruptions from for example, finding a food source or an ant depositing pheromones on the grid. The functions are otherwise pretty self-explanatory: check_pheromone_exponential_decay() check_ant_sensitivity_linear_decay() check_ant_pheromone_exponential_decay() Besides this, with this merge we also finally have an upper limit for ants on the grid using the num_max_agents model variable to check before new ants are generated. any use of the word 'chemical' has been replaced by 'pheromone' for consistency
This commit is contained in:
commit
85f9ecdaec
56
agent.py
56
agent.py
@ -12,11 +12,11 @@ from mesa.agent import Agent
|
|||||||
from mesa.space import Coordinate
|
from mesa.space import Coordinate
|
||||||
|
|
||||||
class RandomWalkerAnt(Agent):
|
class RandomWalkerAnt(Agent):
|
||||||
def __init__(self, unique_id, model, look_for_chemical=None,
|
def __init__(self, unique_id, model, look_for_pheromone=None,
|
||||||
energy_0=1,
|
energy_0=1,
|
||||||
chemical_drop_rate_0 : dict[str, float]={"A": 80, "B": 80},
|
pheromone_drop_rate_0 : dict[str, float]={"A": 80, "B": 80},
|
||||||
sensitivity_0=0.99,
|
sensitivity_0=0.99,
|
||||||
alpha=0.6, drop_chemical=None,
|
alpha=0.6, drop_pheromone=None,
|
||||||
betas : dict[str, float]={"A": 0.0512, "B": 0.0512},
|
betas : dict[str, float]={"A": 0.0512, "B": 0.0512},
|
||||||
sensitivity_decay_rate=0.01,
|
sensitivity_decay_rate=0.01,
|
||||||
sensitivity_max = 1
|
sensitivity_max = 1
|
||||||
@ -27,12 +27,12 @@ class RandomWalkerAnt(Agent):
|
|||||||
self._next_pos : None | Coordinate = None
|
self._next_pos : None | Coordinate = None
|
||||||
self.prev_pos : None | Coordinate = None
|
self.prev_pos : None | Coordinate = None
|
||||||
|
|
||||||
self.look_for_chemical = look_for_chemical
|
self.look_for_pheromone = look_for_pheromone
|
||||||
self.drop_chemical = drop_chemical
|
self.drop_pheromone = drop_pheromone
|
||||||
self.energy = energy_0 #TODO: use
|
self.energy = energy_0 #TODO: use
|
||||||
self.sensitivity_0 = sensitivity_0
|
self.sensitivity_0 = sensitivity_0
|
||||||
self.sensitivity = self.sensitivity_0
|
self.sensitivity = self.sensitivity_0
|
||||||
self.chemical_drop_rate = chemical_drop_rate_0
|
self.pheromone_drop_rate = pheromone_drop_rate_0
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
self.sensitivity_max = sensitivity_max
|
self.sensitivity_max = sensitivity_max
|
||||||
self.sensitivity_decay_rate = sensitivity_decay_rate
|
self.sensitivity_decay_rate = sensitivity_decay_rate
|
||||||
@ -87,7 +87,8 @@ class RandomWalkerAnt(Agent):
|
|||||||
if self.searching_food:
|
if self.searching_food:
|
||||||
for neighbor in self.front_neighbors:
|
for neighbor in self.front_neighbors:
|
||||||
if self.model.grid.is_food(neighbor):
|
if self.model.grid.is_food(neighbor):
|
||||||
self.drop_chemical = "B"
|
self.drop_pheromone = "B"
|
||||||
|
self.look_for_pheromone = "A"
|
||||||
self.sensitivity = self.sensitivity_0
|
self.sensitivity = self.sensitivity_0
|
||||||
|
|
||||||
self.prev_pos = neighbor
|
self.prev_pos = neighbor
|
||||||
@ -96,27 +97,26 @@ class RandomWalkerAnt(Agent):
|
|||||||
elif self.searching_nest:
|
elif self.searching_nest:
|
||||||
for neighbor in self.front_neighbors:
|
for neighbor in self.front_neighbors:
|
||||||
if self.model.grid.is_nest(neighbor):
|
if self.model.grid.is_nest(neighbor):
|
||||||
self.look_for_chemical = "A" # Is this a correct interpretation?
|
self.look_for_pheromone = "A" # Is this a correct interpretation?
|
||||||
self.drop_chemical = "A"
|
self.drop_pheromone = "A"
|
||||||
self.sensitivity = self.sensitivity_0
|
self.sensitivity = self.sensitivity_0
|
||||||
|
|
||||||
#TODO: Do we flip the ant here or reset prev pos?
|
|
||||||
# For now, flip ant just like at food
|
|
||||||
self.prev_pos = neighbor
|
self.prev_pos = neighbor
|
||||||
self._next_pos = self.pos
|
self._next_pos = self.pos
|
||||||
|
|
||||||
# recruit new ants
|
# recruit new ants
|
||||||
for agent_id in self.model.get_unique_ids(self.model.num_new_recruits):
|
for agent_id in self.model.get_unique_ids(self.model.num_new_recruits):
|
||||||
agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_chemical="B", drop_chemical="A")
|
if self.model.schedule.get_agent_count() < self.model.num_max_agents:
|
||||||
|
agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_pheromone="B", drop_pheromone="A")
|
||||||
agent._next_pos = self.pos
|
agent._next_pos = self.pos
|
||||||
self.model.schedule.add(agent)
|
self.model.schedule.add(agent)
|
||||||
self.model.grid.place_agent(agent, pos=neighbor)
|
self.model.grid.place_agent(agent, pos=neighbor)
|
||||||
|
|
||||||
# follow positive gradient
|
# follow positive gradient
|
||||||
if self.look_for_chemical is not None:
|
if self.look_for_pheromone is not None:
|
||||||
front_concentration = [self.model.grid.fields[self.look_for_chemical][cell] for cell in self.front_neighbors ]
|
front_concentration = [self.model.grid.fields[self.look_for_pheromone][cell] for cell in self.front_neighbors ]
|
||||||
front_concentration = self.sens_adj(front_concentration, self.look_for_chemical)
|
front_concentration = self.sens_adj(front_concentration, self.look_for_pheromone)
|
||||||
current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_chemical][self.pos], self.look_for_chemical)
|
current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_pheromone][self.pos], self.look_for_pheromone)
|
||||||
gradient = front_concentration - np.repeat(current_pos_concentration, 3)
|
gradient = front_concentration - np.repeat(current_pos_concentration, 3)
|
||||||
index = np.argmax(gradient)
|
index = np.argmax(gradient)
|
||||||
if gradient[index] > 0:
|
if gradient[index] > 0:
|
||||||
@ -138,19 +138,19 @@ class RandomWalkerAnt(Agent):
|
|||||||
def step(self):
|
def step(self):
|
||||||
self.sensitivity -= self.sensitivity_decay_rate
|
self.sensitivity -= self.sensitivity_decay_rate
|
||||||
self._choose_next_pos()
|
self._choose_next_pos()
|
||||||
self._adjust_chemical_drop_rate()
|
self._adjust_pheromone_drop_rate()
|
||||||
|
|
||||||
def _adjust_chemical_drop_rate(self):
|
def _adjust_pheromone_drop_rate(self):
|
||||||
if(self.drop_chemical is not None):
|
if(self.drop_pheromone is not None):
|
||||||
self.chemical_drop_rate[self.drop_chemical] -= self.chemical_drop_rate[self.drop_chemical] * self.betas[self.drop_chemical]
|
self.pheromone_drop_rate[self.drop_pheromone] -= self.pheromone_drop_rate[self.drop_pheromone] * self.betas[self.drop_pheromone]
|
||||||
|
|
||||||
def drop_chemicals(self) -> None:
|
def drop_pheromones(self) -> None:
|
||||||
# should only be called in advance() as we do not use hidden fields
|
# should only be called in advance() as we do not use hidden fields
|
||||||
if self.drop_chemical is not None:
|
if self.drop_pheromone is not None:
|
||||||
self.model.grid.fields[self.drop_chemical][self.pos] += self.chemical_drop_rate[self.drop_chemical]
|
self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate[self.drop_pheromone]
|
||||||
|
|
||||||
def advance(self) -> None:
|
def advance(self) -> None:
|
||||||
self.drop_chemicals()
|
self.drop_pheromones()
|
||||||
self.prev_pos = self.pos
|
self.prev_pos = self.pos
|
||||||
self.model.grid.move_agent(self, self._next_pos)
|
self.model.grid.move_agent(self, self._next_pos)
|
||||||
|
|
||||||
@ -162,11 +162,11 @@ class RandomWalkerAnt(Agent):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def searching_nest(self) -> bool:
|
def searching_nest(self) -> bool:
|
||||||
return self.drop_chemical == "B"
|
return self.drop_pheromone == "B"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def searching_food(self) -> bool:
|
def searching_food(self) -> bool:
|
||||||
return self.drop_chemical == "A"
|
return self.drop_pheromone == "A"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def front_neighbors(self):
|
def front_neighbors(self):
|
||||||
@ -176,7 +176,9 @@ class RandomWalkerAnt(Agent):
|
|||||||
assert(self.prev_pos is not None)
|
assert(self.prev_pos is not None)
|
||||||
all_neighbors = self.neighbors()
|
all_neighbors = self.neighbors()
|
||||||
neighbors_at_the_back = self.neighbors(pos=self.prev_pos, include_center=True)
|
neighbors_at_the_back = self.neighbors(pos=self.prev_pos, include_center=True)
|
||||||
return list(filter(lambda i: i not in neighbors_at_the_back, all_neighbors))
|
front_neighbors = list(filter(lambda i: i not in neighbors_at_the_back, all_neighbors))
|
||||||
|
assert(len(front_neighbors) == 3) # not sure whether always the case, used for debugging
|
||||||
|
return front_neighbors
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def front_neighbor(self):
|
def front_neighbor(self):
|
||||||
|
125
main.py
125
main.py
@ -11,14 +11,24 @@ from agent import RandomWalkerAnt
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
from mesa.space import Coordinate
|
from mesa.space import Coordinate
|
||||||
|
from mesa.datacollection import DataCollector
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
check_pheromone_exponential_decay()
|
||||||
|
check_ant_sensitivity_linear_decay()
|
||||||
|
check_ant_pheromone_exponential_decay()
|
||||||
|
|
||||||
|
def check_pheromone_exponential_decay():
|
||||||
|
"""
|
||||||
|
Check whether wanted exponential decay of pheromones on grid is done correctly
|
||||||
|
shows plot of pheromone placed on grid vs. equivalent exponential decay function
|
||||||
|
"""
|
||||||
width = 21
|
width = 21
|
||||||
height = width
|
height = width
|
||||||
num_initial_roamers = 5
|
num_initial_roamers = 0
|
||||||
num_max_agents = 100
|
num_max_agents = 100
|
||||||
nest_position : Coordinate = (width //2, height //2)
|
nest_position : Coordinate = (width //2, height //2)
|
||||||
max_steps = 100
|
max_steps = 1000
|
||||||
|
|
||||||
model = ActiveWalkerModel(width=width, height=height,
|
model = ActiveWalkerModel(width=width, height=height,
|
||||||
num_initial_roamers=num_initial_roamers,
|
num_initial_roamers=num_initial_roamers,
|
||||||
@ -26,30 +36,101 @@ def main():
|
|||||||
num_max_agents=num_max_agents,
|
num_max_agents=num_max_agents,
|
||||||
max_steps=max_steps)
|
max_steps=max_steps)
|
||||||
|
|
||||||
# just initial testing of MultiHexGrid
|
model.grid.fields["A"][5,5] = 10
|
||||||
a = model.agent_density()
|
model.datacollector = DataCollector(
|
||||||
for loc in model.grid.iter_neighborhood(nest_position):
|
model_reporters={"pheromone_a": lambda m: m.grid.fields["A"][5,5] },
|
||||||
a[loc] = 3
|
agent_reporters={}
|
||||||
for agent in model.grid.get_neighbors(pos=nest_position, include_center=True):
|
)
|
||||||
if agent.unique_id == 2:
|
model.run_model()
|
||||||
agent.look_for_chemical = "A"
|
a_test = model.datacollector.get_model_vars_dataframe()["pheromone_a"]
|
||||||
agent.prev_pos = (9,10)
|
|
||||||
a[agent.prev_pos] = 1
|
|
||||||
for pos in agent.front_neighbors:
|
|
||||||
a[pos] = 6
|
|
||||||
agent.step()
|
|
||||||
print(f"{agent._next_pos=}")
|
|
||||||
agent.advance()
|
|
||||||
print(agent.front_neighbor)
|
|
||||||
a[agent.front_neighbor] = 5
|
|
||||||
|
|
||||||
print(agent.pos, agent.unique_id, agent.look_for_chemical)
|
plt.figure()
|
||||||
neighbors = model.grid.get_neighborhood(nest_position)
|
xx = np.linspace(0,1000, 10000)
|
||||||
print(neighbors)
|
yy = a_test[0]*np.exp(-model.decay_rates["A"]*xx)
|
||||||
|
plt.plot(xx, yy, label="correct exponential function")
|
||||||
|
plt.scatter(range(len(a_test)), a_test, label="modeled decay", marker='o')
|
||||||
|
plt.title("Exponential grid pheromone decay test")
|
||||||
|
plt.legend(loc='best')
|
||||||
|
|
||||||
print(a)
|
plt.show()
|
||||||
|
|
||||||
|
|
||||||
|
def check_ant_sensitivity_linear_decay():
|
||||||
|
"""
|
||||||
|
Check whether wanted linear decay of ant sensitivity is done correctly
|
||||||
|
shows plot of ant sensitivity placed on grid vs. equivalent linear decay function
|
||||||
|
not food sources are on the grid for this run to not reset sensitivities
|
||||||
|
"""
|
||||||
|
width = 50
|
||||||
|
height = width
|
||||||
|
num_initial_roamers = 1
|
||||||
|
num_max_agents = 100
|
||||||
|
nest_position : Coordinate = (width //2, height //2)
|
||||||
|
max_steps = 1000
|
||||||
|
num_food_sources = 0
|
||||||
|
|
||||||
|
model = ActiveWalkerModel(width=width, height=height,
|
||||||
|
num_initial_roamers=num_initial_roamers,
|
||||||
|
nest_position=nest_position,
|
||||||
|
num_max_agents=num_max_agents,
|
||||||
|
num_food_sources=num_food_sources,
|
||||||
|
max_steps=max_steps)
|
||||||
|
|
||||||
|
model.datacollector = DataCollector(
|
||||||
|
model_reporters={},
|
||||||
|
agent_reporters={"sensitivity": lambda a: a.sensitivity}
|
||||||
|
)
|
||||||
|
start = model.schedule.agents[0].sensitivity_decay_rate
|
||||||
|
model.run_model()
|
||||||
|
a_test = model.datacollector.get_agent_vars_dataframe().reset_index()["sensitivity"]
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
xx = np.linspace(0,1000, 10000)
|
||||||
|
yy = a_test[0] - start*xx
|
||||||
|
plt.title("Linear Ant Sensitivity decay test")
|
||||||
|
plt.plot(xx, yy, label="correct linear function")
|
||||||
|
plt.scatter(range(len(a_test)), a_test, label="modeled decay", marker='o')
|
||||||
|
plt.legend(loc='best')
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
|
def check_ant_pheromone_exponential_decay():
|
||||||
|
"""
|
||||||
|
Check whether wanted exponential decay of pheromone drop rate for ants is correctly modeled
|
||||||
|
shows plot of pheromone placed on grid vs. equivalent exponential decay function
|
||||||
|
"""
|
||||||
|
width = 50
|
||||||
|
height = width
|
||||||
|
num_initial_roamers = 1
|
||||||
|
num_max_agents = 100
|
||||||
|
nest_position : Coordinate = (width //2, height //2)
|
||||||
|
max_steps = 1000
|
||||||
|
|
||||||
|
model = ActiveWalkerModel(width=width, height=height,
|
||||||
|
num_initial_roamers=num_initial_roamers,
|
||||||
|
nest_position=nest_position,
|
||||||
|
num_max_agents=num_max_agents,
|
||||||
|
max_steps=max_steps)
|
||||||
|
|
||||||
|
|
||||||
|
model.datacollector = DataCollector(
|
||||||
|
model_reporters={},
|
||||||
|
agent_reporters={"pheromone_drop_rate": lambda a: a.pheromone_drop_rate["A"]}
|
||||||
|
)
|
||||||
|
start = model.schedule.agents[0].pheromone_drop_rate["A"]
|
||||||
|
model.run_model()
|
||||||
|
a_test = model.datacollector.get_agent_vars_dataframe().reset_index()["pheromone_drop_rate"]
|
||||||
|
|
||||||
|
plt.figure()
|
||||||
|
xx = np.linspace(0,1000, 10000)
|
||||||
|
yy = a_test[0]*np.exp(-model.schedule.agents[0].betas["A"]*xx)
|
||||||
|
plt.plot(xx, yy, label="correct exponential function")
|
||||||
|
plt.scatter(range(len(a_test)), a_test, label="modeled decay", marker='o')
|
||||||
|
plt.title("Exponential pheromone drop rate decay test")
|
||||||
|
plt.legend(loc='best')
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
9
model.py
9
model.py
@ -20,6 +20,8 @@ class ActiveWalkerModel(Model):
|
|||||||
def __init__(self, width : int, height : int , num_max_agents : int,
|
def __init__(self, width : int, height : int , num_max_agents : int,
|
||||||
num_initial_roamers : int,
|
num_initial_roamers : int,
|
||||||
nest_position : Coordinate,
|
nest_position : Coordinate,
|
||||||
|
num_food_sources=5,
|
||||||
|
food_size=10,
|
||||||
max_steps:int=1000) -> None:
|
max_steps:int=1000) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
fields=["A", "B", "nests", "food"]
|
fields=["A", "B", "nests", "food"]
|
||||||
@ -37,12 +39,13 @@ class ActiveWalkerModel(Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
for agent_id in self.get_unique_ids(num_initial_roamers):
|
for agent_id in self.get_unique_ids(num_initial_roamers):
|
||||||
agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_chemical="A", drop_chemical="A")
|
if self.schedule.get_agent_count() < self.num_max_agents:
|
||||||
|
agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="A")
|
||||||
self.schedule.add(agent)
|
self.schedule.add(agent)
|
||||||
self.grid.place_agent(agent, pos=nest_position)
|
self.grid.place_agent(agent, pos=nest_position)
|
||||||
|
|
||||||
for _ in range(5):
|
for _ in range(num_food_sources):
|
||||||
self.grid.add_food(5)
|
self.grid.add_food(food_size)
|
||||||
|
|
||||||
self.datacollector = DataCollector(
|
self.datacollector = DataCollector(
|
||||||
model_reporters={},
|
model_reporters={},
|
||||||
|
@ -23,9 +23,9 @@ def setup(params=None):
|
|||||||
if params is None:
|
if params is None:
|
||||||
params = {
|
params = {
|
||||||
"width": 50, "height": 50,
|
"width": 50, "height": 50,
|
||||||
"num_max_agents" : 100,
|
"num_max_agents" : 1000,
|
||||||
"nest_position" : (25,25),
|
"nest_position" : (25,25),
|
||||||
"num_initial_roamers" : 5,
|
"num_initial_roamers" : 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user