From 11821b61385b255a63e72d0652d679e13a680109 Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Wed, 17 May 2023 18:09:26 +0200 Subject: [PATCH 1/3] plots for linear sensitivity decay and grid pheromone decay --- main.py | 98 +++++++++++++++++++++++++++++++++++++++++++------------- model.py | 6 ++-- 2 files changed, 80 insertions(+), 24 deletions(-) diff --git a/main.py b/main.py index 1216451..4e30f8e 100755 --- a/main.py +++ b/main.py @@ -13,12 +13,23 @@ import matplotlib.pyplot as plt from mesa.space import Coordinate def main(): + check_pheromone_exponential_decay() + check_ant_sensitivity_linear_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 + """ + + from mesa.datacollection import DataCollector + width = 21 height = width - num_initial_roamers = 5 + num_initial_roamers = 0 num_max_agents = 100 nest_position : Coordinate = (width //2, height //2) - max_steps = 100 + max_steps = 1000 model = ActiveWalkerModel(width=width, height=height, num_initial_roamers=num_initial_roamers, @@ -26,28 +37,71 @@ def main(): num_max_agents=num_max_agents, max_steps=max_steps) - # just initial testing of MultiHexGrid - a = model.agent_density() - for loc in model.grid.iter_neighborhood(nest_position): - a[loc] = 3 - for agent in model.grid.get_neighbors(pos=nest_position, include_center=True): - if agent.unique_id == 2: - agent.look_for_chemical = "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 + model.grid.fields["A"][5,5] = 10 + model.datacollector = DataCollector( + model_reporters={"pheromone_a": lambda m: m.grid.fields["A"][5,5] }, + agent_reporters={} + ) + model.run_model() + a_test = model.datacollector.get_model_vars_dataframe()["pheromone_a"] - print(agent.pos, agent.unique_id, agent.look_for_chemical) - neighbors = model.grid.get_neighborhood(nest_position) - print(neighbors) + import matplotlib.pyplot as plt + import numpy as np - print(a) + plt.figure() + xx = np.linspace(0,1000, 10000) + 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') + + 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 + """ + from mesa.datacollection import DataCollector + + 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"] + + import matplotlib.pyplot as plt + import numpy as np + + 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() if __name__ == "__main__": diff --git a/model.py b/model.py index c88a204..a79c2a8 100644 --- a/model.py +++ b/model.py @@ -20,6 +20,8 @@ class ActiveWalkerModel(Model): def __init__(self, width : int, height : int , num_max_agents : int, num_initial_roamers : int, nest_position : Coordinate, + num_food_sources=5, + food_size=10, max_steps:int=1000) -> None: super().__init__() fields=["A", "B", "nests", "food"] @@ -41,8 +43,8 @@ class ActiveWalkerModel(Model): self.schedule.add(agent) self.grid.place_agent(agent, pos=nest_position) - for _ in range(5): - self.grid.add_food(5) + for _ in range(num_food_sources): + self.grid.add_food(food_size) self.datacollector = DataCollector( model_reporters={}, From a8cb347e4ad2d19f8181bf702e960f401708cd0b Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Wed, 17 May 2023 19:32:08 +0200 Subject: [PATCH 2/3] add test ant pheromone decay, rename chemicals to pheromone --- agent.py | 48 ++++++++++++++++++++++++------------------------ main.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ model.py | 2 +- 3 files changed, 71 insertions(+), 25 deletions(-) diff --git a/agent.py b/agent.py index 4a04919..784c891 100644 --- a/agent.py +++ b/agent.py @@ -12,11 +12,11 @@ from mesa.agent import Agent from mesa.space import Coordinate 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, - 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, - alpha=0.6, drop_chemical=None, + alpha=0.6, drop_pheromone=None, betas : dict[str, float]={"A": 0.0512, "B": 0.0512}, sensitivity_decay_rate=0.01, sensitivity_max = 1 @@ -27,12 +27,12 @@ class RandomWalkerAnt(Agent): self._next_pos : None | Coordinate = None self.prev_pos : None | Coordinate = None - self.look_for_chemical = look_for_chemical - self.drop_chemical = drop_chemical + self.look_for_pheromone = look_for_pheromone + self.drop_pheromone = drop_pheromone self.energy = energy_0 #TODO: use self.sensitivity_0 = 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.sensitivity_max = sensitivity_max self.sensitivity_decay_rate = sensitivity_decay_rate @@ -87,7 +87,7 @@ class RandomWalkerAnt(Agent): if self.searching_food: for neighbor in self.front_neighbors: if self.model.grid.is_food(neighbor): - self.drop_chemical = "B" + self.drop_pheromone = "B" self.sensitivity = self.sensitivity_0 self.prev_pos = neighbor @@ -96,8 +96,8 @@ class RandomWalkerAnt(Agent): elif self.searching_nest: for neighbor in self.front_neighbors: if self.model.grid.is_nest(neighbor): - self.look_for_chemical = "A" # Is this a correct interpretation? - self.drop_chemical = "A" + self.look_for_pheromone = "A" # Is this a correct interpretation? + self.drop_pheromone = "A" self.sensitivity = self.sensitivity_0 #TODO: Do we flip the ant here or reset prev pos? @@ -107,16 +107,16 @@ class RandomWalkerAnt(Agent): # recruit new ants 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") + agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_pheromone="B", drop_pheromone="A") agent._next_pos = self.pos self.model.schedule.add(agent) self.model.grid.place_agent(agent, pos=neighbor) # follow positive gradient - if self.look_for_chemical is not None: - front_concentration = [self.model.grid.fields[self.look_for_chemical][cell] for cell in self.front_neighbors ] - front_concentration = self.sens_adj(front_concentration, self.look_for_chemical) - current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_chemical][self.pos], self.look_for_chemical) + if self.look_for_pheromone is not None: + 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_pheromone) + 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) index = np.argmax(gradient) if gradient[index] > 0: @@ -138,19 +138,19 @@ class RandomWalkerAnt(Agent): def step(self): self.sensitivity -= self.sensitivity_decay_rate self._choose_next_pos() - self._adjust_chemical_drop_rate() + self._adjust_pheromone_drop_rate() - def _adjust_chemical_drop_rate(self): - if(self.drop_chemical is not None): - self.chemical_drop_rate[self.drop_chemical] -= self.chemical_drop_rate[self.drop_chemical] * self.betas[self.drop_chemical] + def _adjust_pheromone_drop_rate(self): + if(self.drop_pheromone is not None): + 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 - if self.drop_chemical is not None: - self.model.grid.fields[self.drop_chemical][self.pos] += self.chemical_drop_rate[self.drop_chemical] + if self.drop_pheromone is not None: + self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate[self.drop_pheromone] def advance(self) -> None: - self.drop_chemicals() + self.drop_pheromones() self.prev_pos = self.pos self.model.grid.move_agent(self, self._next_pos) @@ -162,11 +162,11 @@ class RandomWalkerAnt(Agent): @property def searching_nest(self) -> bool: - return self.drop_chemical == "B" + return self.drop_pheromone == "B" @property def searching_food(self) -> bool: - return self.drop_chemical == "A" + return self.drop_pheromone == "A" @property def front_neighbors(self): diff --git a/main.py b/main.py index 4e30f8e..a4d19ab 100755 --- a/main.py +++ b/main.py @@ -15,6 +15,7 @@ from mesa.space import Coordinate def main(): check_pheromone_exponential_decay() check_ant_sensitivity_linear_decay() + check_ant_pheromone_exponential_decay() def check_pheromone_exponential_decay(): """ @@ -103,6 +104,51 @@ def check_ant_sensitivity_linear_decay(): 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 + """ + + from mesa.datacollection import DataCollector + + 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"] + + import matplotlib.pyplot as plt + import numpy as np + + + 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__": main() diff --git a/model.py b/model.py index a79c2a8..5145bfd 100644 --- a/model.py +++ b/model.py @@ -39,7 +39,7 @@ class ActiveWalkerModel(Model): } 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") + agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="A") self.schedule.add(agent) self.grid.place_agent(agent, pos=nest_position) From 30661542b54c755db31d33883c258c939b1d4f70 Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Thu, 18 May 2023 12:46:48 +0200 Subject: [PATCH 3/3] add num_max_agents, cleanup --- agent.py | 16 +++++++++------- main.py | 21 +-------------------- model.py | 7 ++++--- server.py | 4 ++-- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/agent.py b/agent.py index 784c891..95c94cb 100644 --- a/agent.py +++ b/agent.py @@ -88,6 +88,7 @@ class RandomWalkerAnt(Agent): for neighbor in self.front_neighbors: if self.model.grid.is_food(neighbor): self.drop_pheromone = "B" + self.look_for_pheromone = "A" self.sensitivity = self.sensitivity_0 self.prev_pos = neighbor @@ -100,17 +101,16 @@ class RandomWalkerAnt(Agent): self.drop_pheromone = "A" 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._next_pos = self.pos # recruit new ants 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_pheromone="B", drop_pheromone="A") - agent._next_pos = self.pos - self.model.schedule.add(agent) - self.model.grid.place_agent(agent, pos=neighbor) + 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 + self.model.schedule.add(agent) + self.model.grid.place_agent(agent, pos=neighbor) # follow positive gradient if self.look_for_pheromone is not None: @@ -176,7 +176,9 @@ class RandomWalkerAnt(Agent): assert(self.prev_pos is not None) all_neighbors = self.neighbors() 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 def front_neighbor(self): diff --git a/main.py b/main.py index a4d19ab..0e0c596 100755 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ from agent import RandomWalkerAnt import numpy as np import matplotlib.pyplot as plt from mesa.space import Coordinate +from mesa.datacollection import DataCollector def main(): check_pheromone_exponential_decay() @@ -22,9 +23,6 @@ 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 """ - - from mesa.datacollection import DataCollector - width = 21 height = width num_initial_roamers = 0 @@ -46,9 +44,6 @@ def check_pheromone_exponential_decay(): model.run_model() a_test = model.datacollector.get_model_vars_dataframe()["pheromone_a"] - import matplotlib.pyplot as plt - import numpy as np - plt.figure() xx = np.linspace(0,1000, 10000) yy = a_test[0]*np.exp(-model.decay_rates["A"]*xx) @@ -66,8 +61,6 @@ def check_ant_sensitivity_linear_decay(): 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 """ - from mesa.datacollection import DataCollector - width = 50 height = width num_initial_roamers = 1 @@ -91,9 +84,6 @@ def check_ant_sensitivity_linear_decay(): model.run_model() a_test = model.datacollector.get_agent_vars_dataframe().reset_index()["sensitivity"] - import matplotlib.pyplot as plt - import numpy as np - plt.figure() xx = np.linspace(0,1000, 10000) yy = a_test[0] - start*xx @@ -109,9 +99,6 @@ 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 """ - - from mesa.datacollection import DataCollector - width = 50 height = width num_initial_roamers = 1 @@ -134,10 +121,6 @@ def check_ant_pheromone_exponential_decay(): model.run_model() a_test = model.datacollector.get_agent_vars_dataframe().reset_index()["pheromone_drop_rate"] - import matplotlib.pyplot as plt - import numpy as np - - plt.figure() xx = np.linspace(0,1000, 10000) yy = a_test[0]*np.exp(-model.schedule.agents[0].betas["A"]*xx) @@ -148,8 +131,6 @@ def check_ant_pheromone_exponential_decay(): plt.show() - - if __name__ == "__main__": main() diff --git a/model.py b/model.py index 5145bfd..8634e61 100644 --- a/model.py +++ b/model.py @@ -39,9 +39,10 @@ class ActiveWalkerModel(Model): } for agent_id in self.get_unique_ids(num_initial_roamers): - agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="A") - self.schedule.add(agent) - self.grid.place_agent(agent, pos=nest_position) + 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.grid.place_agent(agent, pos=nest_position) for _ in range(num_food_sources): self.grid.add_food(food_size) diff --git a/server.py b/server.py index a81d5aa..4d14e63 100755 --- a/server.py +++ b/server.py @@ -23,9 +23,9 @@ def setup(params=None): if params is None: params = { "width": 50, "height": 50, - "num_max_agents" : 100, + "num_max_agents" : 1000, "nest_position" : (25,25), - "num_initial_roamers" : 5, + "num_initial_roamers" : 20, }