add scalarfields on grid

This commit is contained in:
Alexander Bocken 2023-04-28 14:45:56 +02:00
parent 808dc6f78c
commit 1952198fad
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
4 changed files with 76 additions and 12 deletions

View File

@ -13,43 +13,57 @@ from mesa.space import Coordinate
class RandomWalkerAnt(Agent): class RandomWalkerAnt(Agent):
def __init__(self, unique_id, model, do_follow_chemical_A=True, 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: energy_0=1, chemical_drop_rate_0=1, sensitvity_0=1, alpha=0.5) -> 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 self.prev_pos : None | Coordinate = None
self.do_follow_chemical_A : bool = True # False -> follow_chemical_B = True self.do_follow_chemical_A : bool = True # False -> follow_chemical_B = True
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
self.alpha = alpha self.alpha = alpha
def sensitvity_to_concentration(self, prop : float) -> float:
# TODO
return prop
def step(self): def step(self):
# Calculate where next ant location should be and store in _next_pos
# TODO
pass
def drop_chemicals(self):
# drop chemicals (depending on current state) on concentration field
# TODO
# use self.model.grid.add_to_field(key, value, pos) to not interfere with other ants
pass pass
def advance(self) -> None: def advance(self) -> None:
self.drop_chemicals()
self.pos = self._next_pos self.pos = self._next_pos
@property @property
def front_neighbors(self): def front_neighbors(self):
if self.prev_pos is not None: if self.prev_pos is not None:
assert(self.pos is not None)
x, y = self.pos x, y = self.pos
x_prev, y_prev = self.prev_pos x_prev, y_prev = self.prev_pos
dx, dy = x - x_prev, y - y_prev dx, dy = x - x_prev, y - y_prev
front = [ front = np.array([
(x, y + dy), (x, y + dy),
(x + dx, y),
(x + dx, y + dy), (x + dx, y + dy),
] (x + dx, y),
])
return front #TODO: verify (do we need to sperate into even/odd?) return front #TODO: verify (do we need to sperate into even/odd?)
else: else:
# TODO: return all neighbors or raise Exception? # TODO: return all neighbors or raise Exception?
pass 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 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.

View File

@ -32,6 +32,7 @@ def main():
agent.do_follow_chemical_A = False agent.do_follow_chemical_A = False
agent.prev_pos = (9,10) agent.prev_pos = (9,10)
print(agent.front_neighbors) print(agent.front_neighbors)
print(agent.pos, agent.unique_id, agent.do_follow_chemical_A) print(agent.pos, agent.unique_id, agent.do_follow_chemical_A)

View File

@ -11,25 +11,37 @@ License: AGPL 3 (see end of file)
import numpy as np import numpy as np
from mesa.model import Model from mesa.model import Model
from mesa.space import Coordinate, HexGrid, Iterable from mesa.space import Coordinate, HexGrid, Iterable
from multihex import MultiHexGrid from multihex import MultiHexGrid, MultiHexGridScalarFields
from mesa.time import SimultaneousActivation from mesa.time import SimultaneousActivation
from mesa.datacollection import DataCollector from mesa.datacollection import DataCollector
from agent import RandomWalkerAnt from agent import RandomWalkerAnt
from agent import Pheromone
class ActiveWalkerModel(Model): class ActiveWalkerModel(Model):
# TODO: separate food and source into new agents?
# TODO: pheromone concentrations as well as agents?
def __init__(self, width : int, height : int , num_max_agents : int, def __init__(self, width : int, height : int , num_max_agents : int,
num_initial_roamers : int, num_initial_roamers : int,
nest_position : Coordinate, nest_position : Coordinate,
max_steps:int=1000) -> None: max_steps:int=1000) -> None:
super().__init__() super().__init__()
fields={"A" : True, # key : also have _next prop (for no interference in step)
"B": True,
"nests": False,
"food" : False,
}
self.schedule = SimultaneousActivation(self) self.schedule = SimultaneousActivation(self)
self.grid = MultiHexGrid(width=width, height=height, torus=True) # TODO: replace with MultiHexGrid self.grid = MultiHexGridScalarFields(width=width, height=height, torus=True, fields=fields)
self._unique_id_counter : int = -1 # only touch via get_unique_id() or get_unique_ids(num_ids) self._unique_id_counter = -1
self.max_steps = max_steps self.max_steps = max_steps
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,
"B": 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, do_follow_chemical_A=True) agent = RandomWalkerAnt(unique_id=agent_id, model=self, do_follow_chemical_A=True)
self.schedule.add(agent) self.schedule.add(agent)
@ -41,8 +53,18 @@ class ActiveWalkerModel(Model):
) )
self.datacollector.collect(self) # keep at end of __init___ self.datacollector.collect(self) # keep at end of __init___
def step(self): def step(self):
self.schedule.step() self.schedule.step() # step() and advance() all agents
# apply decay rate on pheromone levels
for key in ("A", "B"):
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) self.datacollector.collect(self)
if self.schedule.steps >= self.max_steps: if self.schedule.steps >= self.max_steps:

View File

@ -12,6 +12,7 @@ License: AGPL 3 (see end of file)
from mesa.space import HexGrid from mesa.space import HexGrid
from mesa.agent import Agent from mesa.agent import Agent
import numpy as np import numpy as np
import numpy.typing as npt
from mesa.space import Coordinate, accept_tuple_argument from mesa.space import Coordinate, accept_tuple_argument
import itertools import itertools
from typing import ( from typing import (
@ -91,6 +92,32 @@ class MultiHexGrid(HexGrid):
) )
class MultiHexGridScalarFields(MultiHexGrid):
def __init__(self, fields: dict[str, bool], width : int, height : int, torus : bool, scalar_initial_value : float=0) -> None:
super().__init__(width=width, height=height, torus=torus)
self._field_props = fields
self.fields : dict[str, npt.NDArray[np.float_]] = {}
for key, is_step_field in fields.items():
self.fields[key] = np.ones((width, height)).astype(float) * scalar_initial_value
if is_step_field:
self.fields[f"_next_{key}"] = np.zeros((width, height)).astype(float)
def reset_field(self, key : str) -> None:
self.fields[key] = np.zeros((self.width, self.height))
def add_to_field(self, field_key : str, value : float, pos : Coordinate) -> None:
if self._field_props[field_key]:
self.fields[f"_next_{field_key}"][pos] += value
else:
self.fields[field_key][pos] += value
def step(self) -> None:
for key, is_step_field in self._field_props.items():
if is_step_field:
self.fields[key] += self.fields[f"_next_{key}"]
self.reset_field(f"_next_{key}")
""" """
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 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.