add initial project files

This commit is contained in:
Alexander Bocken 2023-04-26 23:45:14 +02:00
parent 9a11387436
commit 808dc6f78c
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
6 changed files with 327 additions and 0 deletions

View File

@ -1,2 +1,10 @@
# ants
A reimplementation of Schweitzer et al. 1996 as well as additional improvemnts for the Course Agent Based Modelling for Social Systems FS2023 ETH Zürich
For the course [Agent Based Modelling for Social Systems FS2023](https://www.vorlesungen.ethz.ch/Vorlesungsverzeichnis/lerneinheit.view?lerneinheitId=167292&semkez=2023S&ansicht=LEHRVERANSTALTUNGEN&lang=de) we were tasked to implement a model of our own (in groups).
For this, we decided to implement an enhanced version of [Active random walkers simulate trunk trail formation by ants (Schweitzer et al. 1996)](https://www.sciencedirect.com/science/article/pii/S030326479601670X?casa_token=fv82ToWDN3cAAAAA:wB5hHIlxnYBBvyuHb98YUFpXqWqGt50xDRnmAZ_UaMS5khR9IiH8K6m1b5gdqkAe1ACXx_lEy2U) using Python and Mesa.
For now, wanted features can be found in our [shortlist](shortlist.md).
For everything else start at [main py](main.py)

59
agent.py Normal file
View File

@ -0,0 +1,59 @@
"""
agent.py - Part of ants project
This model implements the actual agents on the grid (a.k.a. the ants)
License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kragho
"""
import numpy as np
from mesa.agent import Agent
from mesa.space import Coordinate
class RandomWalkerAnt(Agent):
def __init__(self, unique_id, model, do_follow_chemical_A=True,
energy_0=1, chemical_drop_rate_0=1, sensitvity_0=1, alpha=0.5)-> None:
super().__init__(unique_id=unique_id, model=model)
self._next_pos : None | Coordinate = None
self.prev_pos = None
self.do_follow_chemical_A : bool = True # False -> follow_chemical_B = True
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
self.alpha = alpha
def step(self):
pass
def advance(self) -> None:
self.pos = self._next_pos
@property
def front_neighbors(self):
if self.prev_pos is not None:
x, y = self.pos
x_prev, y_prev = self.prev_pos
dx, dy = x - x_prev, y - y_prev
front = [
(x, y + dy),
(x + dx, y),
(x + dx, y + dy),
]
return front #TODO: verify (do we need to sperate into even/odd?)
else:
# TODO: return all neighbors or raise Exception?
pass
"""
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/>
"""

49
main.py Executable file
View File

@ -0,0 +1,49 @@
#!/bin/python
"""
main.py - Part of ants project
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 Kragho
"""
from model import ActiveWalkerModel
from agent import RandomWalkerAnt
import numpy as np
import matplotlib.pyplot as plt
from mesa.space import Coordinate
def main():
width = 21
height = width
num_initial_roamers = 5
num_max_agents = 100
nest_position : Coordinate = (width //2, height //2)
max_steps = 100
model = ActiveWalkerModel(width=width, height=height,
num_initial_roamers=num_initial_roamers,
nest_position=nest_position,
num_max_agents=num_max_agents,
max_steps=max_steps)
# just initial testing of MultiHexGrid
for agent in model.grid.get_neighbors(pos=nest_position, include_center=True):
if agent.unique_id == 2:
agent.do_follow_chemical_A = False
agent.prev_pos = (9,10)
print(agent.front_neighbors)
print(agent.pos, agent.unique_id, agent.do_follow_chemical_A)
if __name__ == "__main__":
main()
"""
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/>
"""

65
model.py Normal file
View File

@ -0,0 +1,65 @@
"""
model.py - Part of ants project
This file implements the mesa model on which our ActiveRandomWalkerAnts
will act
License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kragho
"""
import numpy as np
from mesa.model import Model
from mesa.space import Coordinate, HexGrid, Iterable
from multihex import MultiHexGrid
from mesa.time import SimultaneousActivation
from mesa.datacollection import DataCollector
from agent import RandomWalkerAnt
class ActiveWalkerModel(Model):
def __init__(self, width : int, height : int , num_max_agents : int,
num_initial_roamers : int,
nest_position : Coordinate,
max_steps:int=1000) -> None:
super().__init__()
self.schedule = SimultaneousActivation(self)
self.grid = MultiHexGrid(width=width, height=height, torus=True) # TODO: replace with MultiHexGrid
self._unique_id_counter : int = -1 # only touch via get_unique_id() or get_unique_ids(num_ids)
self.max_steps = max_steps
self.nest_position : Coordinate = nest_position
self.num_max_agents = num_max_agents
for agent_id in self.get_unique_ids(num_initial_roamers):
agent = RandomWalkerAnt(unique_id=agent_id, model=self, do_follow_chemical_A=True)
self.schedule.add(agent)
self.grid.place_agent(agent, pos=nest_position)
self.datacollector = DataCollector(
model_reporters={},
agent_reporters={}
)
self.datacollector.collect(self) # keep at end of __init___
def step(self):
self.schedule.step()
self.datacollector.collect(self)
if self.schedule.steps >= self.max_steps:
self.running = False
def get_unique_id(self) -> int:
self._unique_id_counter += 1
return self._unique_id_counter
def get_unique_ids(self, num_ids : int):
for _ in range(num_ids):
yield self.get_unique_id()
"""
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/>
"""

101
multihex.py Normal file
View File

@ -0,0 +1,101 @@
"""
multihex.py - Part of ants project
This file impements a Mesa HexGrid while allowing for multiple agents to be
at the same location. The base for this code comes from the MultiGrid class
in mesa/space.py
License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kragho
"""
from mesa.space import HexGrid
from mesa.agent import Agent
import numpy as np
from mesa.space import Coordinate, accept_tuple_argument
import itertools
from typing import (
Any,
Callable,
Iterable,
Iterator,
List,
Sequence,
Tuple,
TypeVar,
Union,
cast,
overload,
)
MultiGridContent = list[Agent]
class MultiHexGrid(HexGrid):
"""Hexagonal grid where each cell can contain more than one agent.
Mostly based of mesa's HexGrid
Functions according to odd-q rules.
See http://www.redblobgames.com/grids/hexagons/#coordinates for more.
Properties:
width, height: The grid's width and height.
torus: Boolean which determines whether to treat the grid as a torus.
Methods:
get_neighbors: Returns the objects surrounding a given cell.
get_neighborhood: Returns the cells surrounding a given cell.
iter_neighbors: Iterates over position neighbors.
iter_neighborhood: Returns an iterator over cell coordinates that are
in the neighborhood of a certain point.
"""
grid: list[list[MultiGridContent]]
@staticmethod
def default_val() -> MultiGridContent:
"""Default value for new cell elements."""
return []
def place_agent(self, agent: Agent, pos: Coordinate) -> None:
"""Place the agent at the specified location, and set its pos variable."""
x, y = pos
if agent.pos is None or agent not in self._grid[x][y]:
self._grid[x][y].append(agent)
agent.pos = pos
if self._empties_built:
self._empties.discard(pos)
def remove_agent(self, agent: Agent) -> None:
"""Remove the agent from the given location and set its pos attribute to None."""
pos = agent.pos
x, y = pos
self._grid[x][y].remove(agent)
if self._empties_built and self.is_cell_empty(pos):
self._empties.add(pos)
agent.pos = None
@accept_tuple_argument
def iter_cell_list_contents(
self, cell_list: Iterable[Coordinate]
) -> Iterator[Agent]:
"""Returns an iterator of the agents contained in the cells identified
in `cell_list`; cells with empty content are excluded.
Args:
cell_list: Array-like of (x, y) tuples, or single tuple.
Returns:
An iterator of the agents contained in the cells identified in `cell_list`.
"""
return itertools.chain.from_iterable(
self._grid[x][y]
for x, y in itertools.filterfalse(self.is_cell_empty, cell_list)
)
"""
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/>
"""

45
shortlist.md Normal file
View File

@ -0,0 +1,45 @@
# SHORTLIST
- nonlinear response to concentration of pheromones (with upper and lower threshold)
-> needs a separation of sensitivity and internal energy
## More chaos
- what happens if you disrupt the trail with an obstacle?
- limited node capacity
# Model setup
## Agents
- previous_position
- position
- sensitivity
- internal energy
- pheromone drop rate (A/B)
- what chemical we´re looking for
- optional: how much food we have with us (and decrement to prevent dying if energy is low)
- alpha
### step function
- probablistic forward step based on concentrations and sensitvity
- follow highest concentration probabilistically and be random otherwise
- drop pheromones
- have we found food -> change behaviour and decrease food amount + reset stuff
- are we at the nest (having found food)? -> recruit new ants + reset stuff
- decrement sensitivity
- decrement energy (optional: only without food)
- do i need to die
## Model
### hexagonal grid
- pheromone a/b concentration
- nest location
- food location/concentration
- for later: node capacity
- N total ants
- a few other constants which need to be set
### step
advance ants (call ants step function)
decrease pheromone concentration on grid