create function for ants to die and for nonlinear sensitivy function

When ants hit a minimum sensitivy threshold (configurable), the ant is added to a dead list
and then removed from the model schedule and the environment in the model step.
The dead list is cleared every step

Nonlinear sensitivity using a logistic function is implemented.
This commit is contained in:
kaghog 2023-05-31 00:48:41 +02:00
parent 83a973f377
commit 61e92ae136
2 changed files with 63 additions and 7 deletions

View File

@ -19,7 +19,9 @@ 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 = 300 sensitivity_max = 300,
sensitivity_min = 0.001,
sensitivity_steepness = 1
) -> None: ) -> None:
super().__init__(unique_id=unique_id, model=model) super().__init__(unique_id=unique_id, model=model)
@ -35,11 +37,14 @@ class RandomWalkerAnt(Agent):
self.pheromone_drop_rate = pheromone_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_min = sensitivity_min
self.sensitivity_decay_rate = sensitivity_decay_rate self.sensitivity_decay_rate = sensitivity_decay_rate
self.sensitivity_steepness = sensitivity_steepness
self.betas = betas self.betas = betas
self.threshold : dict[str, float] = {"A": 0, "B": 0} 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:
""" """
returns the adjusted value of any property dependent on the current returns the adjusted value of any property dependent on the current
@ -60,19 +65,51 @@ class RandomWalkerAnt(Agent):
| |
0|________ 0|________
-----------------------> prop -----------------------> prop
For the nonlinear sensitivity, the idea is to use a logistic function that has
a characteristic sigmoidal shape that starts from a low value, increases rapidly,
and then gradually approaches a saturation level.
f(x) = L / (1 + exp(-k*(x - x0)))
f(x) = return value
L = sens_max
k is a parameter that controls the steepness of the curve. We can start with 1
A higher value of k leads to a steeper curve.
x0 is the midpoint of the curve, where the sensitivity starts to increase significantly.
We can make X0 the threshold value
""" """
# 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:
# TODO: proper nonlinear response, not just clamping #TODO: proper nonlinear response, not just clamping
non_linear_sens = True
if non_linear_sens:
L = self.sensitivity_max
k = self.sensitivity_steepness
mid = self.threshold[key]
if props > self.sensitivity_max: if props > self.sensitivity_max:
return self.sensitivity_max return self.sensitivity_max
#Should we still keep these conditions?
# if props > self.threshold[key]:
# return props
else:
adjusted_sensitivity = L / (1 + np.exp(-k * (props - mid)))
print(f'props: {props}, adjusted_value: {adjusted_sensitivity}')
return adjusted_sensitivity
else:
if props > self.sensitivity_max:
return self.sensitivity_max #Should we still keep these conditions
if props > self.threshold[key]: 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, key)) arr.append(self.sens_adj(prop, key))
@ -146,6 +183,10 @@ class RandomWalkerAnt(Agent):
self._choose_next_pos() self._choose_next_pos()
self._adjust_pheromone_drop_rate() self._adjust_pheromone_drop_rate()
#kill agent if sensitivity is low
if self.sensitivity < self.sensitivity_min:
self._kill_agent()
def _adjust_pheromone_drop_rate(self): def _adjust_pheromone_drop_rate(self):
if(self.drop_pheromone is not None): 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] self.pheromone_drop_rate[self.drop_pheromone] -= self.pheromone_drop_rate[self.drop_pheromone] * self.betas[self.drop_pheromone]
@ -155,6 +196,11 @@ class RandomWalkerAnt(Agent):
if self.drop_pheromone is not None: if self.drop_pheromone is not None:
self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate[self.drop_pheromone] self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate[self.drop_pheromone]
def _kill_agent(self):
#update dead_agent list
self.model.dead_agents.append(self)
def advance(self) -> None: def advance(self) -> None:
self.drop_pheromones() self.drop_pheromones()
self.model.grid.move_agent(self, self._next_pos) self.model.grid.move_agent(self, self._next_pos)

View File

@ -39,6 +39,8 @@ class ActiveWalkerModel(Model):
"B": 0.01, "B": 0.01,
} }
self.dead_agents = []
for agent_id in self.get_unique_ids(num_initial_roamers): for agent_id in self.get_unique_ids(num_initial_roamers):
if self.schedule.get_agent_count() < self.num_max_agents: 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") agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="A")
@ -75,6 +77,14 @@ class ActiveWalkerModel(Model):
if self.schedule.steps >= self.max_steps: if self.schedule.steps >= self.max_steps:
self.running = False self.running = False
#remove dead agents
for agent in self.dead_agents:
self.schedule.remove(agent)
self.grid.remove_agent(agent)
self.dead_agents.remove(agent)
self.dead_agents = []
#ToDo what happens when all agents die
def get_unique_id(self) -> int: def get_unique_id(self) -> int:
self._unique_id_counter += 1 self._unique_id_counter += 1
return self._unique_id_counter return self._unique_id_counter