From fb1c8f5797d9bd2862f8a75029ec46eca1815eae Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Mon, 26 Jun 2023 15:54:22 +0200 Subject: [PATCH] Combine resistance and sensitivity/alpha --- agent.py | 68 +++++++++++++------ main.py | 196 +++++++++++++++++++++---------------------------------- model.py | 6 +- 3 files changed, 127 insertions(+), 143 deletions(-) diff --git a/agent.py b/agent.py index 105fcae..c76ebf2 100644 --- a/agent.py +++ b/agent.py @@ -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 diff --git a/main.py b/main.py index 1d91e90..870155c 100755 --- a/main.py +++ b/main.py @@ -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 + You should have received a copy of the GNU Affero General Public License along with this program. If not, see """ - - - -# |%%--%%| - -# Access the DataCollector -datacollector = model.datacollector - - -# |%%--%%| - -# Get the data from the DataCollector -model_data = datacollector.get_model_vars_dataframe() - -# |%%--%%| - -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() - -# |%%--%%| - -# 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) - - -# |%%--%%| <64kmoHYvCD|JEzmDy4wuX> - - - -# |%%--%%| - - - - - - - -# |%%--%%| - - - -# |%%--%%| diff --git a/model.py b/model.py index 357d95b..c858b1c 100644 --- a/model.py +++ b/model.py @@ -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