Merge branch 'live_server_visualization'
This merge implements server.py for a visualization of the model. For this implementation the mesa-provided CanvasHexGrid class needed to be adjusted. Mesa's own function runs a visualization function on all agents. For us it would be smarter to run the visualization function on all grid locations, this is what CanvasHexGridMultiAgents does. For now, concentrations are normalized using magic numbers. In the future it might be smarter to somehow dynamically adjust these normalizisation numbers to not threshold our visualization.
This commit is contained in:
commit
a22e832a0e
@ -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 now, wanted features can be found in our [shortlist](shortlist.md).
|
||||||
For everything else start at [main py](main.py)
|
For everything else start at [main py](main.py)
|
||||||
|
|
||||||
|
For a live visualization of the project you can execute [server.py](server.py).
|
||||||
|
11
agent.py
11
agent.py
@ -14,14 +14,14 @@ from typing import overload
|
|||||||
|
|
||||||
class RandomWalkerAnt(Agent):
|
class RandomWalkerAnt(Agent):
|
||||||
def __init__(self, unique_id, model, look_for_chemical=None,
|
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)
|
super().__init__(unique_id=unique_id, model=model)
|
||||||
|
|
||||||
self._next_pos : None | Coordinate = None
|
self._next_pos : None | Coordinate = None
|
||||||
|
|
||||||
self.prev_pos : None | Coordinate = None
|
self.prev_pos : None | Coordinate = None
|
||||||
self.look_for_chemical = look_for_chemical
|
self.look_for_chemical = look_for_chemical
|
||||||
self.drop_chemical = None
|
self.drop_chemical = drop_chemical
|
||||||
self.energy : float = energy_0
|
self.energy : float = energy_0
|
||||||
self.sensitvity : float = sensitvity_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
|
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):
|
def step(self):
|
||||||
# follow positive gradient
|
# 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:
|
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 ]
|
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)
|
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:
|
def advance(self) -> None:
|
||||||
self.drop_chemicals()
|
self.drop_chemicals()
|
||||||
self.prev_pos = self.pos
|
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
|
# TODO: find out how to decorate with property properly
|
||||||
def neighbors(self, pos=None, include_center=False):
|
def neighbors(self, pos=None, include_center=False):
|
||||||
|
8
model.py
8
model.py
@ -33,12 +33,12 @@ class ActiveWalkerModel(Model):
|
|||||||
self.nest_position : Coordinate = nest_position
|
self.nest_position : Coordinate = nest_position
|
||||||
self.num_max_agents = num_max_agents
|
self.num_max_agents = num_max_agents
|
||||||
|
|
||||||
self.decay_rates = {"A" :1,
|
self.decay_rates : dict[str, float] = {"A" :0.1,
|
||||||
"B": 1,
|
"B": 0.1,
|
||||||
}
|
}
|
||||||
|
|
||||||
for agent_id in self.get_unique_ids(num_initial_roamers):
|
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.schedule.add(agent)
|
||||||
self.grid.place_agent(agent, pos=nest_position)
|
self.grid.place_agent(agent, pos=nest_position)
|
||||||
|
|
||||||
@ -64,8 +64,6 @@ class ActiveWalkerModel(Model):
|
|||||||
field = self.grid.fields[key]
|
field = self.grid.fields[key]
|
||||||
self.grid.fields[key] = field - self.decay_rates[key]*field
|
self.grid.fields[key] = field - self.decay_rates[key]*field
|
||||||
|
|
||||||
self.grid.step() # actually apply deposits on fields
|
|
||||||
|
|
||||||
self.datacollector.collect(self)
|
self.datacollector.collect(self)
|
||||||
|
|
||||||
if self.schedule.steps >= self.max_steps:
|
if self.schedule.steps >= self.max_steps:
|
||||||
|
109
server.py
Executable file
109
server.py
Executable 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/>
|
||||||
|
"""
|
Loading…
Reference in New Issue
Block a user