added live-server visualization to project

This commit is contained in:
Alexander Bocken 2023-04-29 11:00:42 +02:00
parent a5e03b38ac
commit 214135d063
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
3 changed files with 120 additions and 10 deletions

View File

@ -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):

View File

@ -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:

109
server.py Executable file
View File

@ -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 <https://www.gnu.org/licenses/>
"""