add initial project files
This commit is contained in:
parent
9a11387436
commit
808dc6f78c
@ -1,2 +1,10 @@
|
|||||||
# ants
|
# 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
|
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
59
agent.py
Normal 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
49
main.py
Executable 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
65
model.py
Normal 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
101
multihex.py
Normal 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
45
shortlist.md
Normal 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
|
Loading…
Reference in New Issue
Block a user