implemented test ant_follow_gradient, fixed step() function

This commit is contained in:
Alexander Bocken 2023-05-18 16:00:43 +02:00
parent 85f9ecdaec
commit 10279930e1
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
4 changed files with 87 additions and 20 deletions

View File

@ -19,13 +19,13 @@ class RandomWalkerAnt(Agent):
alpha=0.6, drop_pheromone=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 = 300
) -> None: ) -> None:
super().__init__(unique_id=unique_id, model=model) super().__init__(unique_id=unique_id, model=model)
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_pheromone = look_for_pheromone self.look_for_pheromone = look_for_pheromone
self.drop_pheromone = drop_pheromone self.drop_pheromone = drop_pheromone
@ -37,7 +37,7 @@ class RandomWalkerAnt(Agent):
self.sensitivity_max = sensitivity_max self.sensitivity_max = sensitivity_max
self.sensitivity_decay_rate = sensitivity_decay_rate self.sensitivity_decay_rate = sensitivity_decay_rate
self.betas = betas self.betas = betas
self.threshold : dict[str, float] = {"A": 1, "B": 1} self.threshold : dict[str, float] = {"A": 0, "B": 0}
def sens_adj(self, props, key) -> npt.NDArray[np.float_] | float: def sens_adj(self, props, key) -> npt.NDArray[np.float_] | float:
@ -79,9 +79,11 @@ class RandomWalkerAnt(Agent):
return np.array(arr) return np.array(arr)
def _choose_next_pos(self): def _choose_next_pos(self):
if self.prev_pos is None: if self._prev_pos is None:
i = np.random.choice(range(6)) i = np.random.choice(range(6))
assert(self.pos is not self.neighbors()[i])
self._next_pos = self.neighbors()[i] self._next_pos = self.neighbors()[i]
self._prev_pos = self.pos
return return
if self.searching_food: if self.searching_food:
@ -91,7 +93,7 @@ class RandomWalkerAnt(Agent):
self.look_for_pheromone = "A" self.look_for_pheromone = "A"
self.sensitivity = self.sensitivity_0 self.sensitivity = self.sensitivity_0
self.prev_pos = neighbor self._prev_pos = neighbor
self._next_pos = self.pos self._next_pos = self.pos
elif self.searching_nest: elif self.searching_nest:
@ -101,7 +103,7 @@ class RandomWalkerAnt(Agent):
self.drop_pheromone = "A" self.drop_pheromone = "A"
self.sensitivity = self.sensitivity_0 self.sensitivity = self.sensitivity_0
self.prev_pos = neighbor self._prev_pos = neighbor
self._next_pos = self.pos self._next_pos = self.pos
# recruit new ants # recruit new ants
@ -117,22 +119,26 @@ class RandomWalkerAnt(Agent):
front_concentration = [self.model.grid.fields[self.look_for_pheromone][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_pheromone) 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) 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).astype(np.float_)
# TODO: if two or more neighbors have same concentration randomize? Should be unlikely with floats though
index = np.argmax(gradient) index = np.argmax(gradient)
if gradient[index] > 0: if gradient[index] > 0:
self._next_pos = self.front_neighbors[index] self._next_pos = self.front_neighbors[index]
self._prev_pos = self.pos
return return
# do biased random walk # do biased random walk
p = np.random.uniform() p = np.random.uniform()
if p < self.alpha: if p < self.alpha:
self._next_pos = self.front_neighbor self._next_pos = self.front_neighbor
self._prev_pos = self.pos
else: else:
# need copy() as we would otherwise remove the tuple from all possible lists (aka python "magic") # need copy() as we would otherwise remove the tuple from all possible lists (aka python "magic")
other_neighbors = self.neighbors().copy() other_neighbors = self.neighbors().copy()
other_neighbors.remove(self.front_neighbor) other_neighbors.remove(self.front_neighbor)
random_index = np.random.choice(range(len(other_neighbors))) random_index = np.random.choice(range(len(other_neighbors)))
self._next_pos = other_neighbors[random_index] self._next_pos = other_neighbors[random_index]
self._prev_pos = self.pos
def step(self): def step(self):
@ -151,8 +157,8 @@ class RandomWalkerAnt(Agent):
def advance(self) -> None: def advance(self) -> None:
self.drop_pheromones() self.drop_pheromones()
self.prev_pos = self.pos
self.model.grid.move_agent(self, self._next_pos) self.model.grid.move_agent(self, self._next_pos)
self._next_pos = None # so that we rather crash than use wrong data
# TODO: find out how to decorate with property properly # TODO: find out how to decorate with property properly
def neighbors(self, pos=None, include_center=False): def neighbors(self, pos=None, include_center=False):
@ -173,12 +179,25 @@ class RandomWalkerAnt(Agent):
""" """
returns all three neighbors which the ant can see returns all three neighbors which the ant can see
""" """
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)
front_neighbors = 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 ########## DEBUG
try:
assert(self._prev_pos is not None)
assert(self._prev_pos is not self.pos)
assert(self._prev_pos in all_neighbors)
assert(len(front_neighbors) == 3)
except AssertionError:
print(f"{self._prev_pos=}")
print(f"{self.pos=}")
print(f"{all_neighbors=}")
print(f"{neighbors_at_the_back=}")
print(f"{front_neighbors=}")
raise AssertionError
else:
return front_neighbors
@property @property
def front_neighbor(self): def front_neighbor(self):
@ -186,11 +205,11 @@ class RandomWalkerAnt(Agent):
returns neighbor of current pos returns neighbor of current pos
which is towards the front of the ant which is towards the front of the ant
""" """
neighbors_prev_pos = self.neighbors(self.prev_pos) neighbors__prev_pos = self.neighbors(self._prev_pos)
for candidate in self.front_neighbors: for candidate in self.front_neighbors:
# neighbor in front direction only shares current pos as neighborhood with prev_pos # neighbor in front direction only shares current pos as neighborhood with _prev_pos
candidate_neighbors = self.model.grid.get_neighborhood(candidate) candidate_neighbors = self.model.grid.get_neighborhood(candidate)
overlap = [x for x in candidate_neighbors if x in neighbors_prev_pos] overlap = [x for x in candidate_neighbors if x in neighbors__prev_pos]
if len(overlap) == 1: if len(overlap) == 1:
return candidate return candidate

48
main.py
View File

@ -13,10 +13,13 @@ import matplotlib.pyplot as plt
from mesa.space import Coordinate from mesa.space import Coordinate
from mesa.datacollection import DataCollector from mesa.datacollection import DataCollector
from multihex import MultiHexGrid
def main(): def main():
check_pheromone_exponential_decay() check_pheromone_exponential_decay()
check_ant_sensitivity_linear_decay() check_ant_sensitivity_linear_decay()
check_ant_pheromone_exponential_decay() check_ant_pheromone_exponential_decay()
check_ants_follow_gradient()
def check_pheromone_exponential_decay(): def check_pheromone_exponential_decay():
""" """
@ -131,6 +134,51 @@ def check_ant_pheromone_exponential_decay():
plt.show() plt.show()
def check_ants_follow_gradient():
"""
Create a path of neighbours with a static gradient.
Observe whether ant correctly follows gradient once found. via matrix printouts
8 = ant
anything else: pheromone A density.
The ant does not drop any new pheromones for this test
"""
width, height = 20,20
params = {
"width": width, "height": height,
"num_max_agents": 1,
"num_food_sources": 0,
"nest_position": (10,10),
"num_initial_roamers": 1,
}
model = ActiveWalkerModel(**params)
def place_line(grid : MultiHexGrid, start_pos=None):
strength = 5
if start_pos is None:
start_pos = (9,9)
next_pos = start_pos
for _ in range(width):
grid.fields["A"][next_pos] = strength
strength += 0.01
next_pos = grid.get_neighborhood(next_pos)[0]
place_line(model.grid)
ant = model.schedule._agents[0]
ant.looking_for_pheromone = "A"
ant.drop_pheromone = None
ant.threshold["A"] = 0
ant.sensitivity_max = 100
#model.grid.fields["A"] = np.diag(np.ones(width))
model.decay_rates["A"] = 0
while model.schedule.steps < 100:
display_field = np.copy(model.grid.fields["A"])
display_field[ant.pos] = 8
print(display_field)
print(20*"#")
model.step()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -22,7 +22,8 @@ class ActiveWalkerModel(Model):
nest_position : Coordinate, nest_position : Coordinate,
num_food_sources=5, num_food_sources=5,
food_size=10, 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"]
self.schedule = SimultaneousActivation(self) self.schedule = SimultaneousActivation(self)
@ -68,7 +69,6 @@ class ActiveWalkerModel(Model):
for key in ("A", "B"): for key in ("A", "B"):
field = self.grid.fields[key] field = self.grid.fields[key]
self.grid.fields[key] = field - self.decay_rates[key]*field self.grid.fields[key] = field - self.decay_rates[key]*field
# TODO: plot to check whether exponential
self.datacollector.collect(self) self.datacollector.collect(self)

View File

@ -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" : 1000, "num_max_agents" : 100,
"nest_position" : (25,25), "nest_position" : (25,25),
"num_initial_roamers" : 20, "num_initial_roamers" : 5,
} }
@ -127,7 +127,7 @@ def setup(params=None):
[lambda m: "<h3>Ant density</h3><h5>Nest: Red, Food: Green</h5>", [lambda m: "<h3>Ant density</h3><h5>Nest: Red, Food: Green</h5>",
grid_ants, grid_ants,
lambda m: f"<h5>Normalization Value: {norm_ants(m)}</h5>", lambda m: f"<h5>Normalization Value: {norm_ants(m)}</h5>",
lambda m: "<h3>Pheromone Density</h3><h5>Pheromone A: Cyan, Pheromone B: Pink</h5>", lambda m: "<h3>Pheromone Density</h3><h5>Pheromone A: Cyan, Pheromone B: Magenta</h5>",
grid_pheromones, grid_pheromones,
lambda m: f"<h5>Normalization Value: {norm_pheromones(m)}</h5>" lambda m: f"<h5>Normalization Value: {norm_pheromones(m)}</h5>"
], ],