Merge branch 'implementations_tests'
Implemented check for ants to follow positive pheromone gradient step() function has been improved. Previously, self.prev_pos (now: self._prev_pos) was set non-uniformly; sometimes in step() but also always in advance() which resulted in unwanted behavior. As self._prev_pos is now marked as private all assignments are done simultaneously with self._next_pos in step(), not advance(). Accordingly, future functions should strive not to access self._prev_pos outside of the agent if possible. Gradient following behaviour was additionally not observed since the sensitivity_max variable was set too low, resulting in the adjusted pheromone concentration gradients to not be present.
This commit is contained in:
commit
83a973f377
47
agent.py
47
agent.py
@ -19,13 +19,13 @@ class RandomWalkerAnt(Agent):
|
||||
alpha=0.6, drop_pheromone=None,
|
||||
betas : dict[str, float]={"A": 0.0512, "B": 0.0512},
|
||||
sensitivity_decay_rate=0.01,
|
||||
sensitivity_max = 1
|
||||
sensitivity_max = 300
|
||||
) -> None:
|
||||
|
||||
super().__init__(unique_id=unique_id, model=model)
|
||||
|
||||
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.drop_pheromone = drop_pheromone
|
||||
@ -37,7 +37,7 @@ class RandomWalkerAnt(Agent):
|
||||
self.sensitivity_max = sensitivity_max
|
||||
self.sensitivity_decay_rate = sensitivity_decay_rate
|
||||
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:
|
||||
@ -79,9 +79,11 @@ class RandomWalkerAnt(Agent):
|
||||
return np.array(arr)
|
||||
|
||||
def _choose_next_pos(self):
|
||||
if self.prev_pos is None:
|
||||
if self._prev_pos is None:
|
||||
i = np.random.choice(range(6))
|
||||
assert(self.pos is not self.neighbors()[i])
|
||||
self._next_pos = self.neighbors()[i]
|
||||
self._prev_pos = self.pos
|
||||
return
|
||||
|
||||
if self.searching_food:
|
||||
@ -91,7 +93,7 @@ class RandomWalkerAnt(Agent):
|
||||
self.look_for_pheromone = "A"
|
||||
self.sensitivity = self.sensitivity_0
|
||||
|
||||
self.prev_pos = neighbor
|
||||
self._prev_pos = neighbor
|
||||
self._next_pos = self.pos
|
||||
|
||||
elif self.searching_nest:
|
||||
@ -101,7 +103,7 @@ class RandomWalkerAnt(Agent):
|
||||
self.drop_pheromone = "A"
|
||||
self.sensitivity = self.sensitivity_0
|
||||
|
||||
self.prev_pos = neighbor
|
||||
self._prev_pos = neighbor
|
||||
self._next_pos = self.pos
|
||||
|
||||
# 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.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)
|
||||
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)
|
||||
if gradient[index] > 0:
|
||||
self._next_pos = self.front_neighbors[index]
|
||||
self._prev_pos = self.pos
|
||||
return
|
||||
|
||||
# do biased random walk
|
||||
p = np.random.uniform()
|
||||
if p < self.alpha:
|
||||
self._next_pos = self.front_neighbor
|
||||
self._prev_pos = self.pos
|
||||
else:
|
||||
# need copy() as we would otherwise remove the tuple from all possible lists (aka python "magic")
|
||||
other_neighbors = self.neighbors().copy()
|
||||
other_neighbors.remove(self.front_neighbor)
|
||||
random_index = np.random.choice(range(len(other_neighbors)))
|
||||
self._next_pos = other_neighbors[random_index]
|
||||
self._prev_pos = self.pos
|
||||
|
||||
|
||||
def step(self):
|
||||
@ -151,8 +157,8 @@ class RandomWalkerAnt(Agent):
|
||||
|
||||
def advance(self) -> None:
|
||||
self.drop_pheromones()
|
||||
self.prev_pos = self.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
|
||||
def neighbors(self, pos=None, include_center=False):
|
||||
@ -173,11 +179,24 @@ class RandomWalkerAnt(Agent):
|
||||
"""
|
||||
returns all three neighbors which the ant can see
|
||||
"""
|
||||
assert(self.prev_pos is not None)
|
||||
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))
|
||||
assert(len(front_neighbors) == 3) # not sure whether always the case, used for debugging
|
||||
|
||||
########## 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
|
||||
@ -186,11 +205,11 @@ class RandomWalkerAnt(Agent):
|
||||
returns neighbor of current pos
|
||||
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:
|
||||
# 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)
|
||||
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:
|
||||
return candidate
|
||||
|
||||
|
48
main.py
48
main.py
@ -13,10 +13,13 @@ import matplotlib.pyplot as plt
|
||||
from mesa.space import Coordinate
|
||||
from mesa.datacollection import DataCollector
|
||||
|
||||
from multihex import MultiHexGrid
|
||||
|
||||
def main():
|
||||
check_pheromone_exponential_decay()
|
||||
check_ant_sensitivity_linear_decay()
|
||||
check_ant_pheromone_exponential_decay()
|
||||
check_ants_follow_gradient()
|
||||
|
||||
def check_pheromone_exponential_decay():
|
||||
"""
|
||||
@ -131,6 +134,51 @@ def check_ant_pheromone_exponential_decay():
|
||||
|
||||
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__":
|
||||
main()
|
||||
|
||||
|
4
model.py
4
model.py
@ -22,7 +22,8 @@ class ActiveWalkerModel(Model):
|
||||
nest_position : Coordinate,
|
||||
num_food_sources=5,
|
||||
food_size=10,
|
||||
max_steps:int=1000) -> None:
|
||||
max_steps:int=1000,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
fields=["A", "B", "nests", "food"]
|
||||
self.schedule = SimultaneousActivation(self)
|
||||
@ -68,7 +69,6 @@ class ActiveWalkerModel(Model):
|
||||
for key in ("A", "B"):
|
||||
field = self.grid.fields[key]
|
||||
self.grid.fields[key] = field - self.decay_rates[key]*field
|
||||
# TODO: plot to check whether exponential
|
||||
|
||||
self.datacollector.collect(self)
|
||||
|
||||
|
@ -23,9 +23,9 @@ def setup(params=None):
|
||||
if params is None:
|
||||
params = {
|
||||
"width": 50, "height": 50,
|
||||
"num_max_agents" : 1000,
|
||||
"num_max_agents" : 100,
|
||||
"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>",
|
||||
grid_ants,
|
||||
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,
|
||||
lambda m: f"<h5>Normalization Value: {norm_pheromones(m)}</h5>"
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user