From 214135d063123c53fb792faa6331b8ab4c790231 Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Sat, 29 Apr 2023 11:00:42 +0200 Subject: [PATCH 1/2] added live-server visualization to project --- agent.py | 11 ++++-- model.py | 10 ++--- server.py | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 10 deletions(-) create mode 100755 server.py diff --git a/agent.py b/agent.py index 8ed99e6..4de387c 100644 --- a/agent.py +++ b/agent.py @@ -14,14 +14,14 @@ from typing import overload class RandomWalkerAnt(Agent): def __init__(self, unique_id, model, look_for_chemical=None, - energy_0=1, chemical_drop_rate_0=1, sensitvity_0=1, alpha=0.5) -> None: + energy_0=1, chemical_drop_rate_0=1, sensitvity_0=1, alpha=0.5, drop_chemical=None) -> None: super().__init__(unique_id=unique_id, model=model) self._next_pos : None | Coordinate = None self.prev_pos : None | Coordinate = None self.look_for_chemical = look_for_chemical - self.drop_chemical = None + self.drop_chemical = drop_chemical self.energy : float = energy_0 self.sensitvity : float = sensitvity_0 self.chemical_drop_rate : float = chemical_drop_rate_0 #TODO: check whether needs to be separated into A and B @@ -34,6 +34,10 @@ class RandomWalkerAnt(Agent): def step(self): # follow positive gradient + if self.prev_pos is None: + i = np.random.choice(range(6)) + self._next_pos = self.neighbors()[i] + return 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 ] gradient = front_concentration - np.repeat(self.model.grid.fields[self.look_for_chemical][self.pos], 3) @@ -61,8 +65,7 @@ class RandomWalkerAnt(Agent): def advance(self) -> None: self.drop_chemicals() self.prev_pos = self.pos - self.pos = self._next_pos - + self.model.grid.move_agent(self, self._next_pos) # TODO: find out how to decorate with property properly def neighbors(self, pos=None, include_center=False): diff --git a/model.py b/model.py index 44242d1..4885d1e 100644 --- a/model.py +++ b/model.py @@ -33,12 +33,12 @@ class ActiveWalkerModel(Model): self.nest_position : Coordinate = nest_position self.num_max_agents = num_max_agents - self.decay_rates = {"A" :1, - "B": 1, - } + self.decay_rates : dict[str, float] = {"A" :0.1, + "B": 0.1, + } for agent_id in self.get_unique_ids(num_initial_roamers): - agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_chemical="A") + agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_chemical="A", drop_chemical="A") self.schedule.add(agent) self.grid.place_agent(agent, pos=nest_position) @@ -64,8 +64,6 @@ class ActiveWalkerModel(Model): field = self.grid.fields[key] self.grid.fields[key] = field - self.decay_rates[key]*field - self.grid.step() # actually apply deposits on fields - self.datacollector.collect(self) if self.schedule.steps >= self.max_steps: diff --git a/server.py b/server.py new file mode 100755 index 0000000..f7edf1a --- /dev/null +++ b/server.py @@ -0,0 +1,109 @@ +#!/bin/python +""" +server.py - Part of ants project + +This file sets up the mesa built-in visualization server +and runs it on file execution. +For now it displays ant locations as well as pheromone A +concentrations on two seperate grids + +License: AGPL 3 (see end of file) +(C) Alexander Bocken, Viviane Fahrni, Grace Kragho +""" + +import numpy as np +from mesa.visualization.modules import CanvasHexGrid, ChartModule, CanvasGrid +from mesa.visualization.ModularVisualization import ModularServer +from mesa.visualization.UserParam import UserSettableParameter +from model import ActiveWalkerModel +from collections import defaultdict + +def setup(): + # Set the model parameters + params = { + "width": 50, "height": 50, + "num_max_agents" : 100, + "nest_position" : (25,25), + "num_initial_roamers" : 5, + } + + + class CanvasHexGridMultiAgents(CanvasHexGrid): + """ + A modification of CanvasHexGrid to not run visualization functions on all agents + but on all grid positions instead + """ + package_includes = ["HexDraw.js", "CanvasHexModule.js", "InteractionHandler.js"] + portrayal_method = None # Portrayal function + canvas_width = 500 + canvas_height = 500 + + def __init__(self, portrayal_method, grid_width, grid_height, canvas_width=500, canvas_height=500,): + super().__init__(portrayal_method, grid_width, grid_height, canvas_width, canvas_height) + + def render(self, model): + grid_state = defaultdict(list) + for x in range(model.grid.width): + for y in range(model.grid.height): + portrayal = self.portrayal_method(model, (x, y)) + if portrayal: + portrayal["x"] = x + portrayal["y"] = y + grid_state[portrayal["Layer"]].append(portrayal) + + return grid_state + + + def get_color(level, normalization): + """ + level: level to calculate color between white and black (linearly) + normalization: value for which we want full black color + """ + rgb = int(255 - level * 255 / normalization) + mono = f"{rgb:0{2}x}" # hex value of rgb value with fixed length 2 + rgb = f"#{3*mono}" + return rgb + + def portray_ant_density(model, pos): + return { + "Shape": "hex", + "r": 1, + "Filled": "true", + "Layer": 0, + "x": pos[0], + "y": pos[1], + "Color": get_color(level=len(model.grid[pos]), normalization=5) + } + + def portray_pheromone_density(model, pos): + return { + "Shape": "hex", + "r": 1, + "Filled": "true", + "Layer": 0, + "x": pos[0], + "y": pos[1], + "Color": get_color(level=model.grid.fields["A"][pos], normalization=3) + } + + + + width = params['width'] + height = params['height'] + pixel_ratio = 10 + grid_ants = CanvasHexGridMultiAgents(portray_ant_density, width, height, width*pixel_ratio, height*pixel_ratio) + grid_pheromones = CanvasHexGridMultiAgents(portray_pheromone_density, width, height, width*pixel_ratio, height*pixel_ratio) + return ModularServer(ActiveWalkerModel, [grid_ants, grid_pheromones], + "Active Random Walker Ants", params) + +if __name__ == "__main__": + server = setup() + server.launch() + +""" +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. + +You should have received a copy of the GNU Affero General Public License along with this program. If not, see +""" From 7e5c40ad1119c64412ca5034c5f23475cd9b3ea2 Mon Sep 17 00:00:00 2001 From: AlexBocken Date: Sat, 29 Apr 2023 11:02:55 +0200 Subject: [PATCH 2/2] add server visualization to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7ac7797..0cd5617 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,5 @@ For this, we decided to implement an enhanced version of [Active random walkers For now, wanted features can be found in our [shortlist](shortlist.md). For everything else start at [main py](main.py) + +For a live visualization of the project you can execute [server.py](server.py).