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:
parent
83a973f377
commit
61e92ae136
48
agent.py
48
agent.py
@ -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)
|
||||||
|
10
model.py
10
model.py
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user