more step and sensitivity improvements following Schweitzer et al. 1997)
This commit is contained in:
parent
044aab26ca
commit
e45d33bf8d
91
agent.py
91
agent.py
@ -13,66 +13,99 @@ 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_chemical=None,
|
||||||
energy_0=1, chemical_drop_rate_0=1, sensitvity_0=0.1,
|
energy_0=1,
|
||||||
|
chemical_drop_rate_0 : dict[str, float]={"A": 80, "B": 80},
|
||||||
|
sensitivity_0=0.99,
|
||||||
alpha=0.5, drop_chemical=None,
|
alpha=0.5, drop_chemical=None,
|
||||||
|
betas : dict[str, float]={"A": 0.0512, "B": 0.0512},
|
||||||
|
sensitivity_decay_rate=0.01,
|
||||||
|
sensitivity_max = 1
|
||||||
) -> 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_chemical = look_for_chemical
|
self.look_for_chemical = look_for_chemical
|
||||||
self.drop_chemical = drop_chemical
|
self.drop_chemical = drop_chemical
|
||||||
self.energy : float = energy_0
|
self.energy = energy_0 #TODO: use
|
||||||
self.sensitvity : float = sensitvity_0
|
self.sensitivity_0 = sensitivity_0
|
||||||
self.chemical_drop_rate : float = chemical_drop_rate_0 #TODO: check whether needs to be separated into A and B
|
self.sensitivity = self.sensitivity_0
|
||||||
|
self.chemical_drop_rate = chemical_drop_rate_0
|
||||||
self.alpha = alpha
|
self.alpha = alpha
|
||||||
|
self.sensitivity_max = sensitivity_max
|
||||||
|
self.sensitivity_decay_rate = sensitivity_decay_rate
|
||||||
|
self.betas = betas
|
||||||
|
self.threshold : dict[str, float] = {"A": 1, "B": 1}
|
||||||
|
|
||||||
|
|
||||||
def sens_adj(self, props) -> npt.NDArray[np.float_] | float:
|
def sens_adj(self, props, key) -> npt.NDArray[np.float_] | float:
|
||||||
|
"""
|
||||||
|
returns the adjusted value of any property dependent on the current
|
||||||
|
sensitivity.
|
||||||
|
The idea is to have a nonlinear response, where any opinion below a
|
||||||
|
threshold (here: self.threshold[key]) is ignored, otherwise it returns
|
||||||
|
the property
|
||||||
|
Long-term this function should be adjusted to return the property up
|
||||||
|
to a upper threshold as well.
|
||||||
|
|
||||||
|
|
||||||
|
returns ^
|
||||||
|
|
|
||||||
|
sens_max| __________
|
||||||
|
| /
|
||||||
|
| /
|
||||||
|
q^tr| /
|
||||||
|
|
|
||||||
|
0|________
|
||||||
|
-----------------------> prop
|
||||||
|
"""
|
||||||
# if props iterable create array, otherwise return single value
|
# if props iterable create array, otherwise return single value
|
||||||
try:
|
try:
|
||||||
iter(props)
|
iter(props)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
if props > self.sensitvity:
|
# TODO: proper nonlinear response, not just clamping
|
||||||
# TODO: nonlinear response
|
if props > self.sensitivity_max:
|
||||||
|
return self.sensitivity_max
|
||||||
|
if props > self.threshold[key]:
|
||||||
return props
|
return props
|
||||||
else:
|
else:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
arr : list[float] = []
|
arr : list[float] = []
|
||||||
for prop in props:
|
for prop in props:
|
||||||
arr.append(self.sens_adj(prop))
|
arr.append(self.sens_adj(prop, key))
|
||||||
return np.array(arr)
|
return np.array(arr)
|
||||||
|
|
||||||
|
def _choose_next_pos(self):
|
||||||
def step(self):
|
|
||||||
# TODO: sensitvity decay
|
|
||||||
|
|
||||||
if self.prev_pos is None:
|
if self.prev_pos is None:
|
||||||
i = np.random.choice(range(6))
|
i = np.random.choice(range(6))
|
||||||
self._next_pos = self.neighbors()[i]
|
self._next_pos = self.neighbors()[i]
|
||||||
return
|
return
|
||||||
|
|
||||||
# Ants dropping A look for food
|
if self.searching_food:
|
||||||
if self.drop_chemical == "A":
|
|
||||||
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_chemical = "B"
|
||||||
|
self.sensitivity = self.sensitivity_0
|
||||||
|
|
||||||
self.prev_pos = neighbor
|
self.prev_pos = neighbor
|
||||||
self._next_pos = self.pos
|
self._next_pos = self.pos
|
||||||
|
|
||||||
# Ants dropping B look for nest
|
elif self.searching_nest:
|
||||||
elif self.drop_chemical == "B":
|
|
||||||
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_chemical = "A" # Is this a correct interpretation?
|
||||||
self.drop_chemical = "A"
|
self.drop_chemical = "A"
|
||||||
|
self.sensitivity = self.sensitivity_0
|
||||||
|
|
||||||
#TODO: Do we flip the ant here or reset prev pos?
|
#TODO: Do we flip the ant here or reset prev pos?
|
||||||
# For now, flip ant just like at food
|
# 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
|
||||||
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")
|
agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_chemical="B", drop_chemical="A")
|
||||||
agent._next_pos = self.pos
|
agent._next_pos = self.pos
|
||||||
@ -82,8 +115,8 @@ class RandomWalkerAnt(Agent):
|
|||||||
# follow positive gradient
|
# follow positive gradient
|
||||||
if self.look_for_chemical is not None:
|
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.model.grid.fields[self.look_for_chemical][cell] for cell in self.front_neighbors ]
|
||||||
front_concentration = self.sens_adj(front_concentration)
|
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])
|
current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_chemical][self.pos], self.look_for_chemical)
|
||||||
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:
|
||||||
@ -101,10 +134,20 @@ class RandomWalkerAnt(Agent):
|
|||||||
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]
|
||||||
|
|
||||||
|
|
||||||
|
def step(self):
|
||||||
|
self.sensitivity -= self.sensitivity_decay_rate
|
||||||
|
self._choose_next_pos()
|
||||||
|
self._adjust_chemical_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 drop_chemicals(self) -> None:
|
def drop_chemicals(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_chemical is not None:
|
||||||
self.model.grid.fields[self.drop_chemical][self.pos] += self.chemical_drop_rate
|
self.model.grid.fields[self.drop_chemical][self.pos] += self.chemical_drop_rate[self.drop_chemical]
|
||||||
|
|
||||||
def advance(self) -> None:
|
def advance(self) -> None:
|
||||||
self.drop_chemicals()
|
self.drop_chemicals()
|
||||||
@ -117,6 +160,14 @@ class RandomWalkerAnt(Agent):
|
|||||||
pos = self.pos
|
pos = self.pos
|
||||||
return self.model.grid.get_neighborhood(pos, include_center=include_center)
|
return self.model.grid.get_neighborhood(pos, include_center=include_center)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def searching_nest(self) -> bool:
|
||||||
|
return self.drop_chemical == "B"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def searching_food(self) -> bool:
|
||||||
|
return self.drop_chemical == "A"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def front_neighbors(self):
|
def front_neighbors(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user