Combine resistance and sensitivity/alpha

This commit is contained in:
Alexander Bocken 2023-06-26 15:54:22 +02:00
parent 7c19031ca2
commit fb1c8f5797
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
3 changed files with 127 additions and 143 deletions

View File

@ -87,6 +87,24 @@ class RandomWalkerAnt(Agent):
return weights
def _choose_next_pos(self):
def _combine_weights(res_weights, walk_weights):
"""
If we have a resistance -> Infinity we want to have a likelihood -> 0 for this direction
Therefore we should multiply our two probabilities.
For the case of no resistance field this will return the normal walk_weights
res_weights : resistance weights: based on resistance field of neighbours
see _get_resistance_weights for more info
walk weights: In case of biased random walk (no positive pheromone gradient):
forward: alpha,
everywhere else: (1- alpaha)/5)
In case of positive pheromone gradient present in front:
max. positive gradient: self.sensitivity
everyhwere else: (1-self.sensitivity)/5
"""
combined = res_weights * walk_weights
normalized = combined / np.sum(combined)
return normalized
def _pick_from_remaining_five(remaining_five):
"""
"""
@ -96,7 +114,10 @@ class RandomWalkerAnt(Agent):
self._prev_pos = self.pos
if self._prev_pos is None:
weights = self._get_resistance_weights()
res_weights = self._get_resistance_weights()
walk_weights = np.ones(6)
weights = _combine_weights(res_weights, walk_weights)
i = np.random.choice(range(6),p=weights)
assert(self.pos is not self.neighbors()[i])
self._next_pos = self.neighbors()[i]
@ -155,28 +176,37 @@ class RandomWalkerAnt(Agent):
index = np.argmax(gradient)
if gradient[index] > 0:
# follow positive gradient with likelihood self.sensitivity
p = np.random.uniform()
if p < self.sensitivity:
self._next_pos = self.front_neighbors[index]
self._prev_pos = self.pos
else:
other_neighbors = self.neighbors().copy()
other_neighbors.remove(self.front_neighbors[index])
_pick_from_remaining_five(other_neighbors)
# follow positive gradient with likelihood self.sensitivity * resistance_weight (re-normalized)
all_neighbors_cells = self.neighbors()
highest_gradient_cell = self.front_neighbors[index]
highest_gradient_index_arr = np.where(all_neighbors_cells == highest_gradient_cell)
assert(len(highest_gradient_index_arr) == 1)
all_neighbors_index = highest_gradient_index_arr[0]
sens_weights = np.ones(6) * (1-self.sensitivity)/5
sens_weights[all_neighbors_index] = self.sensitivity
res_weights = self._get_resistance_weights()
weights = _combine_weights(res_weights, sens_weights)
self._next_pos = np.random.choice(all_neighbors_cells, p=weights)
self._prev_pos = self.pos
return
# do biased random walk
p = np.random.uniform()
# TODO: This completely neglects resistance, relevant?
if p < self.model.alpha:
self._next_pos = self.front_neighbor
self._prev_pos = self.pos
else:
other_neighbors = self.neighbors().copy()
other_neighbors.remove(self.front_neighbor)
_pick_from_remaining_five(other_neighbors)
all_neighbors_cells = self.neighbors()
front_index_arr = np.where(all_neighbors_cells == self.front_neighbor)
assert(len(front_index_arr) == 1 )
front_index = front_index_arr[0]
res_weights = self._get_resistance_weights()
walk_weights = np.ones(6) * (1-self.model.alpha) / 5
walk_weights[front_index] = self.model.alpha
weights = _combine_weights(res_weights, walk_weights)
self._nex_pos = np.random.choice(all_neighbors_cells, p=weights)
self._prev_pos = self.pos
def step(self):
self.sensitivity -= self.model.d_s

196
main.py
View File

@ -1,4 +1,3 @@
#!/bin/python
"""
main.py - Part of ants project
@ -7,9 +6,7 @@ execute via `python main.py` in terminal or only UNIX: `./main.py`
License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kagho
"""
import array
from model import ActiveWalkerModel
from agent import RandomWalkerAnt
import numpy as np
import matplotlib.pyplot as plt
from mesa.space import Coordinate
@ -17,13 +14,6 @@ from mesa.datacollection import DataCollector
#from multihex import MultiHexGrid
def main():
pass
# check_pheromone_exponential_decay()
# check_ant_sensitivity_linear_decay()
# check_ant_pheromone_exponential_decay()
# check_ants_follow_gradient()
def check_pheromone_exponential_decay():
"""
Check whether wanted exponential decay of pheromones on grid is done correctly
@ -183,126 +173,88 @@ def check_ants_follow_gradient():
model.step()
# if __name__ == "__main__":
# main()
from model import kwargs_paper_setup1 as kwargs
# kwargs["N_m"] = 10000
model = ActiveWalkerModel(**kwargs)
if __name__ == "__main__":
print("Test")
kwargs["resistance_map_type"] = "perlin"
print(kwargs)
model = ActiveWalkerModel(**kwargs)
from hexplot import plot_hexagon
# a = np.zeros_like(model.grid.fields['food'])
# a[np.nonzero(model.grid.fields['food'])] = 1
# plot_hexagon(a, title="Nest locations")
# plot_hexagon(model.grid.fields['res'], title="Resistance Map")
# from hexplot import plot_hexagon
# a = np.zeros_like(model.grid.fields['food'])
# a[np.nonzero(model.grid.fields['food'])] = 1
# plot_hexagon(a, title="Nest locations")
# plot_hexagon(model.grid.fields['res'], title="Resistance Map")
from tqdm import tqdm as progress_bar
for _ in progress_bar(range(model.max_steps)):
model.step()
# agent_densities = model.datacollector.get_model_vars_dataframe()["agent_dens"]
# mean_dens = np.mean(agent_densities)
# norm_dens = mean_dens/np.max(mean_dens)
# plot_hexagon(norm_dens, title="Ant density overall")
# plt.show()
# from tqdm import tqdm as progress_bar
#for _ in progress_bar(range(model.max_steps)):
# model.step()
# agent_densities = model.datacollector.get_model_vars_dataframe()["agent_dens"]
# mean_dens = np.mean(agent_densities)
# norm_dens = mean_dens/np.max(mean_dens)
# plot_hexagon(norm_dens, title="Ant density overall")
# plt.show()
# Access the DataCollector
#datacollector = model.datacollector
## Get the data from the DataCollector
#model_data = datacollector.get_model_vars_dataframe()
#print(model_data.columns)
## Plot the number of alive ants over time
#plt.plot(model_data.index, model_data['alive_ants'])
#plt.xlabel('Time')
#plt.ylabel('Number of Alive Ants') #this should probably be "active" ants, since it is not considering those in the nest
#plt.title('Number of Alive Ants Over Time')
#plt.grid(True)
#plt.show()
## Plot the number of sucessful walkers over time
#plt.plot(model_data.index, model_data['sucessful_walkers'])
#plt.xlabel('Time')
#plt.ylabel('Number of Sucessful Walkers')
#plt.title('Number of Sucessful Walkers Over Time')
#plt.grid(True)
#plt.show()
## Calculate the cumulative sum
#model_data['cumulative_sucessful_walkers'] = model_data['sucessful_walkers'].cumsum()
## Plot the cumulative sum of sucessful walkers over time
#plt.plot(model_data.index, model_data['cumulative_sucessful_walkers'])
#plt.xlabel('Time')
#plt.ylabel('Cumulative Sucessful Walkers')
#plt.title('Cumulative Sucessful Walkers Over Time')
#plt.grid(True)
#plt.show()
## Values over 100 are to be interpreted as walkers being sucessfull several times since the total max number of ants is 100
# # Connectivity measure
#def check_food_source_connectivity(food_sources, paths): #food_sources = nodes.is_nest, paths=result from BFS
# connected_food_sources = set()
# for source in food_sources:
# if source in paths:
# connected_food_sources.add(source)
# connectivity = len(connected_food_sources)
# return connectivity
# # Calculate connectivity through BFS
# current_paths = bfs(self.grid, self.grid.fields["nests"], 0.000001)
"""
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>
"""
# |%%--%%| <Z5Ra4Us5kN|y6CRYNrY9x>
# Access the DataCollector
datacollector = model.datacollector
# |%%--%%| <y6CRYNrY9x|v2PfrSWbzG>
# Get the data from the DataCollector
model_data = datacollector.get_model_vars_dataframe()
# |%%--%%| <v2PfrSWbzG|74OaeOltqi>
print(model_data.columns)
# |%%--%%| <74OaeOltqi|WpQLCA0RuP>
# Plot the number of alive ants over time
plt.plot(model_data.index, model_data['alive_ants'])
plt.xlabel('Time')
plt.ylabel('Number of Alive Ants') #this should probably be "active" ants, since it is not considering those in the nest
plt.title('Number of Alive Ants Over Time')
plt.grid(True)
plt.show()
# |%%--%%| <WpQLCA0RuP|UufL3yaROS>
# Plot the number of sucessful walkers over time
plt.plot(model_data.index, model_data['sucessful_walkers'])
plt.xlabel('Time')
plt.ylabel('Number of Sucessful Walkers')
plt.title('Number of Sucessful Walkers Over Time')
plt.grid(True)
plt.show()
# |%%--%%| <UufL3yaROS|mgJWQ0bqG1>
# Calculate the cumulative sum
model_data['cumulative_sucessful_walkers'] = model_data['sucessful_walkers'].cumsum()
# Plot the cumulative sum of sucessful walkers over time
plt.plot(model_data.index, model_data['cumulative_sucessful_walkers'])
plt.xlabel('Time')
plt.ylabel('Cumulative Sucessful Walkers')
plt.title('Cumulative Sucessful Walkers Over Time')
plt.grid(True)
plt.show()
# Values over 100 are to be interpreted as walkers being sucessfull several times since the total max number of ants is 100
# |%%--%%| <mgJWQ0bqG1|64kmoHYvCD>
# Connectivity measure
def check_food_source_connectivity(food_sources, paths): #food_sources = nodes.is_nest, paths=result from BFS
connected_food_sources = set()
for source in food_sources:
if source in paths:
connected_food_sources.add(source)
connectivity = len(connected_food_sources)
return connectivity
# Calculate connectivity through BFS
current_paths = bfs(self.grid, self.grid.fields["nests"], 0.000001)
# |%%--%%| <64kmoHYvCD|JEzmDy4wuX>
# |%%--%%| <JEzmDy4wuX|U9vmSFZUyD>
# |%%--%%| <U9vmSFZUyD|r0xVXEqlAh>
# |%%--%%| <r0xVXEqlAh|6K80EwwmVN>

View File

@ -107,16 +107,18 @@ class ActiveWalkerModel(Model):
self.grid = MultiHexGridScalarFields(width=width, height=height, torus=True, fields=fields)
if resistance_map_type is None:
print("No resistance field")
self.grid.fields["res"] = np.ones((width, height)).astype(float)
elif resistance_map_type == "perlin":
# perlin generates isotropic noise which may or may not be a good choice
# perlin generates anisotropic noise which may or may not be a good choice
# pip3 install git+https://github.com/pvigier/perlin-numpy
from perlin_numpy import (
generate_fractal_noise_2d,
generate_perlin_noise_2d,
)
noise = generate_perlin_noise_2d(shape=(width,height), res=((10,10)))
normalized_noise = (noise - np.min(noise))/(np.max(noise) - np.min(noise))
# normalized to mean=1, min=0, and max=2
normalized_noise = (noise - np.min(noise))/(np.max(noise) - np.min(noise)) * 2
self.grid.fields["res"] = normalized_noise
else:
# possible other noise types: simplex or value