General Debugging. Moved to nomenclature from paper for ease of

understanding
Resistance map implementation
This commit is contained in:
Alexander Bocken 2023-06-20 15:00:14 +02:00
parent 83a973f377
commit 8f2771463d
Signed by: Alexander
GPG Key ID: 1D237BE83F9B05E8
6 changed files with 285 additions and 75 deletions

117
agent.py
View File

@ -5,6 +5,12 @@ This model implements the actual agents on the grid (a.k.a. the ants)
License: AGPL 3 (see end of file) License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kagho (C) Alexander Bocken, Viviane Fahrni, Grace Kagho
"""
"""
TO DISCUSS:
Is the separation of energy and sensitivity useful?
""" """
import numpy as np import numpy as np
import numpy.typing as npt import numpy.typing as npt
@ -12,14 +18,10 @@ from mesa.agent import Agent
from mesa.space import Coordinate from mesa.space import Coordinate
class RandomWalkerAnt(Agent): class RandomWalkerAnt(Agent):
def __init__(self, unique_id, model, look_for_pheromone=None, def __init__(self, unique_id, model,
energy_0=1, look_for_pheromone=None,
pheromone_drop_rate_0 : dict[str, float]={"A": 80, "B": 80}, drop_pheromone=None,
sensitivity_0=0.99, sensitivity_max = 30000,
alpha=0.6, drop_pheromone=None,
betas : dict[str, float]={"A": 0.0512, "B": 0.0512},
sensitivity_decay_rate=0.01,
sensitivity_max = 300
) -> None: ) -> None:
super().__init__(unique_id=unique_id, model=model) super().__init__(unique_id=unique_id, model=model)
@ -27,18 +29,12 @@ class RandomWalkerAnt(Agent):
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_pheromone = look_for_pheromone self.look_for_pheromone : str|None = look_for_pheromone
self.drop_pheromone = drop_pheromone self.drop_pheromone : str|None = drop_pheromone
self.energy = energy_0 #TODO: use self.energy : float = self.model.e_0
self.sensitivity_0 = sensitivity_0 self.sensitivity : float = self.model.s_0
self.sensitivity = self.sensitivity_0 self.pheromone_drop_rate : float = self.model.q_0
self.pheromone_drop_rate = pheromone_drop_rate_0
self.alpha = alpha
self.sensitivity_max = sensitivity_max self.sensitivity_max = sensitivity_max
self.sensitivity_decay_rate = sensitivity_decay_rate
self.betas = betas
self.threshold : dict[str, float] = {"A": 0, "B": 0}
def sens_adj(self, props, key) -> npt.NDArray[np.float_] | float: def sens_adj(self, props, key) -> npt.NDArray[np.float_] | float:
""" """
@ -68,7 +64,7 @@ class RandomWalkerAnt(Agent):
# TODO: proper nonlinear response, not just clamping # TODO: proper nonlinear response, not just clamping
if props > self.sensitivity_max: if props > self.sensitivity_max:
return self.sensitivity_max return self.sensitivity_max
if props > self.threshold[key]: if props > self.model.q_tr:
return props return props
else: else:
return 0 return 0
@ -78,9 +74,29 @@ class RandomWalkerAnt(Agent):
arr.append(self.sens_adj(prop, key)) arr.append(self.sens_adj(prop, key))
return np.array(arr) return np.array(arr)
def _get_resistance_weights(self, positions=None):
if positions is None:
positions = self.neighbors()
# bit round-about but self.model.grid.fields['res'][positions]
# gets interpreted as slices, not multiple singular positions
resistance = np.array([ self.model.grid.fields['res'][x,y] for x,y in positions ])
easiness = np.max(self.model.grid.fields['res']) - resistance + 1e-15 # + epsilon to not divide by zero
weights = easiness/ np.sum(easiness)
return weights
def _choose_next_pos(self): def _choose_next_pos(self):
def _pick_from_remaining_five(remaining_five):
"""
"""
weights = self._get_resistance_weights(remaining_five)
random_index = np.random.choice(range(len(remaining_five)), p=weights)
self._next_pos = remaining_five[random_index]
self._prev_pos = self.pos
if self._prev_pos is None: if self._prev_pos is None:
i = np.random.choice(range(6)) weights = self._get_resistance_weights()
i = np.random.choice(range(6),p=weights)
assert(self.pos is not self.neighbors()[i]) assert(self.pos is not self.neighbors()[i])
self._next_pos = self.neighbors()[i] self._next_pos = self.neighbors()[i]
self._prev_pos = self.pos self._prev_pos = self.pos
@ -89,71 +105,94 @@ class RandomWalkerAnt(Agent):
if self.searching_food: if self.searching_food:
for neighbor in self.front_neighbors: for neighbor in self.front_neighbors:
if self.model.grid.is_food(neighbor): if self.model.grid.is_food(neighbor):
self.model.grid.fields['food'][neighbor] -= 1 # eat
#resets
self.pheromone_drop_rate = self.model.q_0
self.sensitivity = self.model.s_0
self.energy = self.model.e_0
#now look for other pheromone
self.drop_pheromone = "B" self.drop_pheromone = "B"
self.look_for_pheromone = "A" self.look_for_pheromone = "A"
self.sensitivity = self.sensitivity_0
self._prev_pos = neighbor self._prev_pos = neighbor
self._next_pos = self.pos self._next_pos = self.pos
return
elif self.searching_nest: elif self.searching_nest:
for neighbor in self.front_neighbors: for neighbor in self.front_neighbors:
if self.model.grid.is_nest(neighbor): if self.model.grid.is_nest(neighbor):
#resets
self.pheromone_drop_rate = self.model.q_0
self.sensitivity = self.model.s_0
self.energy = self.model.e_0
self.look_for_pheromone = "A" # Is this a correct interpretation? self.look_for_pheromone = "A" # Is this a correct interpretation?
self.drop_pheromone = "A" self.drop_pheromone = "A"
self.sensitivity = self.sensitivity_0
self._prev_pos = neighbor self._prev_pos = neighbor
self._next_pos = self.pos self._next_pos = self.pos
# recruit new ants # recruit new ants
for agent_id in self.model.get_unique_ids(self.model.num_new_recruits): for agent_id in self.model.get_unique_ids(self.model.N_r):
if self.model.schedule.get_agent_count() < self.model.num_max_agents: if self.model.schedule.get_agent_count() < self.model.N_m:
agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_pheromone="B", drop_pheromone="A") agent = RandomWalkerAnt(unique_id=agent_id, model=self.model, look_for_pheromone="B", drop_pheromone="A")
agent._next_pos = self.pos agent._next_pos = self.pos
self.model.schedule.add(agent) self.model.schedule.add(agent)
self.model.grid.place_agent(agent, pos=neighbor) self.model.grid.place_agent(agent, pos=neighbor)
return
# follow positive gradient # follow positive gradient with likelihood self.sensitivity
if self.look_for_pheromone is not None: if self.look_for_pheromone is not None:
# Calculate gradient
front_concentration = [self.model.grid.fields[self.look_for_pheromone][cell] for cell in self.front_neighbors ] front_concentration = [self.model.grid.fields[self.look_for_pheromone][cell] for cell in self.front_neighbors ]
front_concentration = self.sens_adj(front_concentration, self.look_for_pheromone) front_concentration = self.sens_adj(front_concentration, self.look_for_pheromone)
current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_pheromone][self.pos], self.look_for_pheromone) current_pos_concentration = self.sens_adj(self.model.grid.fields[self.look_for_pheromone][self.pos], self.look_for_pheromone)
gradient = front_concentration - np.repeat(current_pos_concentration, 3).astype(np.float_) gradient = front_concentration - np.repeat(current_pos_concentration, 3).astype(np.float_)
# TODO: if two or more neighbors have same concentration randomize? Should be unlikely with floats though
index = np.argmax(gradient) index = np.argmax(gradient)
if gradient[index] > 0: if gradient[index] > 0:
self._next_pos = self.front_neighbors[index] # follow positive gradient with likelihood self.sensitivity
self._prev_pos = self.pos p = np.random.uniform()
if p < self.sensitivity:
self._next_pos = self.front_neighbors[index]
self._prev_pos = self.pos
else:
other_neighbors = self.neighbors().copy()
other_neighbors.remove(self.front_neighbors[index])
_pick_from_remaining_five(other_neighbors)
return return
# do biased random walk # do biased random walk
p = np.random.uniform() p = np.random.uniform()
if p < self.alpha: # TODO: This completely neglects resistance, relevant?
if p < self.model.alpha:
self._next_pos = self.front_neighbor self._next_pos = self.front_neighbor
self._prev_pos = self.pos self._prev_pos = self.pos
else: else:
# need copy() as we would otherwise remove the tuple from all possible lists (aka python "magic")
other_neighbors = self.neighbors().copy() other_neighbors = self.neighbors().copy()
other_neighbors.remove(self.front_neighbor) other_neighbors.remove(self.front_neighbor)
random_index = np.random.choice(range(len(other_neighbors))) _pick_from_remaining_five(other_neighbors)
self._next_pos = other_neighbors[random_index]
self._prev_pos = self.pos
def step(self): def step(self):
self.sensitivity -= self.sensitivity_decay_rate self.sensitivity -= self.model.d_s
self._choose_next_pos() self.energy -= self.model.grid.fields['res'][self.pos] * self.model.d_e
self._adjust_pheromone_drop_rate() # Die and get removed if no energy
if self.energy < self.model.e_min:
self.model.schedule.remove(self)
else:
self._choose_next_pos()
self._adjust_pheromone_drop_rate()
def _adjust_pheromone_drop_rate(self): def _adjust_pheromone_drop_rate(self):
if(self.drop_pheromone is not None): if(self.drop_pheromone is not None):
self.pheromone_drop_rate[self.drop_pheromone] -= self.pheromone_drop_rate[self.drop_pheromone] * self.betas[self.drop_pheromone] self.pheromone_drop_rate -= self.pheromone_drop_rate * self.model.beta
def drop_pheromones(self) -> None: def drop_pheromones(self) -> None:
# should only be called in advance() as we do not use hidden fields # should only be called in advance() as we do not use hidden fields
if self.drop_pheromone is not None: if self.drop_pheromone is not None:
self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate[self.drop_pheromone] self.model.grid.fields[self.drop_pheromone][self.pos] += self.pheromone_drop_rate
def advance(self) -> None: def advance(self) -> None:
self.drop_pheromones() self.drop_pheromones()

31
hexplot.py Executable file
View File

@ -0,0 +1,31 @@
#!/bin/python
import numpy as np
import matplotlib.pyplot as plt
def plot_hexagon(A, title=None):
X, Y = np.meshgrid(range(A.shape[0]), range(A.shape[-1]))
X, Y = X*2, Y*2
# Turn this into a hexagonal grid
for i, k in enumerate(X):
if i % 2 == 1:
X[i] += 1
Y[:,i] += 1
fig, ax = plt.subplots()
im = ax.hexbin(
X.reshape(-1),
Y.reshape(-1),
C=A.reshape(-1),
gridsize=int(A.shape[0]/2)
)
# the rest of the code is adjustable for best output
ax.set_aspect(1)
ax.set(xlim=(-4, X.max()+4,), ylim=(-4, Y.max()+4))
ax.axis(False)
plt.colorbar(im)
if(title is not None):
plt.title(title)
plt.show(block=False)

34
main.py
View File

@ -6,6 +6,7 @@ execute via `python main.py` in terminal or only UNIX: `./main.py`
License: AGPL 3 (see end of file) License: AGPL 3 (see end of file)
(C) Alexander Bocken, Viviane Fahrni, Grace Kagho (C) Alexander Bocken, Viviane Fahrni, Grace Kagho
""" """
import array
from model import ActiveWalkerModel from model import ActiveWalkerModel
from agent import RandomWalkerAnt from agent import RandomWalkerAnt
import numpy as np import numpy as np
@ -16,10 +17,11 @@ from mesa.datacollection import DataCollector
from multihex import MultiHexGrid from multihex import MultiHexGrid
def main(): def main():
check_pheromone_exponential_decay() pass
check_ant_sensitivity_linear_decay() # check_pheromone_exponential_decay()
check_ant_pheromone_exponential_decay() # check_ant_sensitivity_linear_decay()
check_ants_follow_gradient() # check_ant_pheromone_exponential_decay()
# check_ants_follow_gradient()
def check_pheromone_exponential_decay(): def check_pheromone_exponential_decay():
""" """
@ -107,6 +109,7 @@ def check_ant_pheromone_exponential_decay():
num_initial_roamers = 1 num_initial_roamers = 1
num_max_agents = 100 num_max_agents = 100
nest_position : Coordinate = (width //2, height //2) nest_position : Coordinate = (width //2, height //2)
num_food_sources = 0;
max_steps = 1000 max_steps = 1000
model = ActiveWalkerModel(width=width, height=height, model = ActiveWalkerModel(width=width, height=height,
@ -179,8 +182,27 @@ def check_ants_follow_gradient():
model.step() model.step()
if __name__ == "__main__": # if __name__ == "__main__":
main() # main()
from model import kwargs_paper_setup1 as kwargs
model = ActiveWalkerModel(**kwargs)
from hexplot import plot_hexagon
# a = np.zeros_like(model.grid.fields['food'])
# a[np.nonzero(model.grid.fields['food'])] = 1
# plot_hexagon(a, title="Nest locations")
# plot_hexagon(model.grid.fields['res'], title="Resistance Map")
from tqdm import tqdm as progress_bar
for _ in progress_bar(range(model.max_steps)):
model.step()
# agent_densities = model.datacollector.get_model_vars_dataframe()["agent_dens"]
# mean_dens = np.mean(agent_densities)
# norm_dens = mean_dens/np.max(mean_dens)
# plot_hexagon(norm_dens, title="Ant density overall")
# plt.show()

123
model.py
View File

@ -16,40 +16,131 @@ from mesa.time import SimultaneousActivation
from mesa.datacollection import DataCollector from mesa.datacollection import DataCollector
from agent import RandomWalkerAnt from agent import RandomWalkerAnt
kwargs_paper_setup1 = {
"width": 100,
"height": 100,
"N_0": 20,
"N_m": 100,
"N_r": 5,
"alpha": 0.6,
"gamma": 0.001,
"beta": 0.0512,
"d_s": 0.001,
"d_e": 0.001,
"s_0": 0.99,
"e_0": 0.99,
"q_0": 80,
"q_tr": 1,
"e_min": 0,
"nest_position": (49,49),
"N_f": 5,
"food_size" : 55,
"max_steps": 8000,
"resistance_map_type" : None,
}
kwargs_paper_setup2 = {
"width": 100,
"height": 100,
"N_0": 20,
"N_m": 100,
"N_r": 5,
"alpha": 0.6,
"gamma": 0.01,
"beta": 0.0512,
"d_s": 0.001,
"d_e": 0.001,
"s_0": 0.99,
"e_0": 0.99,
"q_0": 80,
"q_tr": 1,
"e_min": 0,
"nest_position": (49,49),
"N_f": 5,
"food_size" : 550,
"max_steps": 8000,
"resistance_map_type" : None,
}
class ActiveWalkerModel(Model): class ActiveWalkerModel(Model):
def __init__(self, width : int, height : int , num_max_agents : int, def __init__(self, width : int, height : int,
num_initial_roamers : int, N_0 : int, # number of initial roamers
N_m : int, # max number of ants
N_r : int, # number of new recruits
alpha : float, #biased random walk
beta : float, # decay rate drop rate
gamma : float, # decay rate pheromone concentration fields
d_s : float, # decay rate sensitvity
d_e : float, # decay rate energy
s_0 : float, # sensitvity reset
e_0 : float, # energy reset
q_0 : float, # initial pheromone level
q_tr : float, # threshold under which ant cannot distinguish concentrations
e_min : float, # energy at which walker dies
nest_position : Coordinate, nest_position : Coordinate,
num_food_sources=5, N_f=5, #num food sources
food_size=10, food_size= 55,
max_steps:int=1000, max_steps:int=1000,
resistance_map_type=None,
) -> None: ) -> None:
super().__init__() super().__init__()
fields=["A", "B", "nests", "food"]
self.N_m : int = N_m # max number of ants
self.N_r : int = N_r # number of new recruits
self.alpha : float = alpha # biased random walk if no gradient
self.gamma : float = gamma # decay rate pheromone concentration fields
self.beta : float = beta # decay rate drop rate
self.d_s : float = d_s # decay rate sensitvity
self.d_e : float = d_e # decay rate energy (get's multiplied with resistance)
self.s_0 : float = s_0 # sensitvity reset
self.e_0 : float = e_0 # energy reset
self.q_0 : float = q_0 # pheromone drop rate reset
self.q_tr : float = q_tr # threshold under which ant cannot distinguish concentrations
self.e_min : float = e_min # energy at which walker dies
self.N_f : int = N_f #num food sources
fields=["A", "B", "nests", "food", "res"]
self.schedule = SimultaneousActivation(self) self.schedule = SimultaneousActivation(self)
self.grid = MultiHexGridScalarFields(width=width, height=height, torus=True, fields=fields) self.grid = MultiHexGridScalarFields(width=width, height=height, torus=True, fields=fields)
if resistance_map_type is None:
self.grid.fields["res"] = np.ones((width, height)).astype(float)
elif resistance_map_type == "perlin":
# perlin generates isotropic noise which may or may not be a good choice
# pip3 install git+https://github.com/pvigier/perlin-numpy
from perlin_numpy import (
generate_fractal_noise_2d,
generate_perlin_noise_2d,
)
noise = generate_perlin_noise_2d(shape=(width,height), res=((10,10)))
normalized_noise = (noise - np.min(noise))/(np.max(noise) - np.min(noise))
self.grid.fields["res"] = normalized_noise
else:
# possible other noise types: simplex or value
raise NotImplemented(f"{resistance_map_type=} is not implemented.")
self._unique_id_counter = -1 self._unique_id_counter = -1
self.max_steps = max_steps self.max_steps = max_steps
self.grid.add_nest(nest_position) self.grid.add_nest(nest_position)
self.num_max_agents = num_max_agents
self.num_new_recruits = 5
self.decay_rates : dict[str, float] = {"A" :0.01, for agent_id in self.get_unique_ids(N_0):
"B": 0.01, if self.schedule.get_agent_count() < self.N_m:
}
for agent_id in self.get_unique_ids(num_initial_roamers):
if self.schedule.get_agent_count() < self.num_max_agents:
agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="A") agent = RandomWalkerAnt(unique_id=agent_id, model=self, look_for_pheromone="A", drop_pheromone="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)
for _ in range(num_food_sources): for _ in range(N_f):
self.grid.add_food(food_size) self.grid.add_food(food_size)
self.datacollector = DataCollector( self.datacollector = DataCollector(
model_reporters={}, # model_reporters={"agent_dens": lambda m: m.agent_density()},
model_reporters = {"pheromone_a": lambda m: m.grid.fields["A"],
"pheromone_b": lambda m: m.grid.fields["B"],
},
agent_reporters={} agent_reporters={}
) )
self.datacollector.collect(self) # keep at end of __init___ self.datacollector.collect(self) # keep at end of __init___
@ -68,7 +159,7 @@ class ActiveWalkerModel(Model):
# apply decay rate on pheromone levels # apply decay rate on pheromone levels
for key in ("A", "B"): for key in ("A", "B"):
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.gamma*field
self.datacollector.collect(self) self.datacollector.collect(self)

View File

@ -107,7 +107,8 @@ class MultiHexGridScalarFields(MultiHexGrid):
def is_food(self, pos): def is_food(self, pos):
assert('food' in self.fields.keys()) assert('food' in self.fields.keys())
return bool(self.fields['food'][pos]) # account for potential float imprecision and use epsilon = 1e-3
return self.fields['food'][pos] > 1e-3
def add_food(self, size : int , pos=None): def add_food(self, size : int , pos=None):
""" """
@ -127,7 +128,7 @@ class MultiHexGridScalarFields(MultiHexGrid):
while(self.is_nest(pos) or self.is_food(pos)): while(self.is_nest(pos) or self.is_food(pos)):
pos = select_random_place() pos = select_random_place()
self.fields['food'][pos] = size self.fields['food'][pos] = int(size)
def is_nest(self, pos : Coordinate) -> bool: def is_nest(self, pos : Coordinate) -> bool:
assert('nests' in self.fields.keys()) assert('nests' in self.fields.keys())

View File

@ -22,10 +22,12 @@ def setup(params=None):
# Set the model parameters # Set the model parameters
if params is None: if params is None:
params = { params = {
"max_steps": 3000,
"width": 50, "height": 50, "width": 50, "height": 50,
"num_max_agents" : 100, "N_m" : 100,
"nest_position" : (25,25), "nest_position" : (25,25),
"num_initial_roamers" : 5, "N_0" : 5,
"resistance_map_type": "perlin",
} }
@ -85,12 +87,9 @@ def setup(params=None):
"Color": col, "Color": col,
} }
def get_max_grid_val(model, key): def portray_resistance_map(model, pos, norm=1):
return np.max(model.grid.fields[key]) col = get_color(level=model.grid.fields['res'][pos], normalization=norm)
col = f"rgb({col}, {col}, {col})"
def portray_pheromone_density(model, pos, norm):
col_a = get_color(level=model.grid.fields["A"][pos], normalization=norm)
col_b = get_color(level=model.grid.fields["B"][pos], normalization=norm)
return { return {
"Shape": "hex", "Shape": "hex",
"r": 1, "r": 1,
@ -98,7 +97,26 @@ def setup(params=None):
"Layer": 0, "Layer": 0,
"x": pos[0], "x": pos[0],
"y": pos[1], "y": pos[1],
"Color": f"rgb({col_a}, {col_b}, 255)" "Color": col,
}
def get_max_grid_val(model, key):
return np.max(model.grid.fields[key])
def portray_pheromone_density(model, pos, norm):
col_a = get_color(level=model.grid.fields["A"][pos], normalization=norm)
col_b = get_color(level=model.grid.fields["B"][pos], normalization=norm)
res_min, res_max = np.min(model.grid.fields['res']), np.max(model.grid.fields['res'])
ease = 1 - model.grid.fields['res'][pos]
col_ease = get_color(level=ease, normalization=np.max(model.grid.fields['res']))
return {
"Shape": "hex",
"r": 1,
"Filled": "true",
"Layer": 0,
"x": pos[0],
"y": pos[1],
"Color": f"rgb({col_a}, {col_b}, {col_ease})"
} }
@ -109,6 +127,9 @@ def setup(params=None):
grid_ants = CanvasHexGridMultiAgents(portray_ant_density, grid_ants = CanvasHexGridMultiAgents(portray_ant_density,
width, height, width*pixel_ratio, height*pixel_ratio, width, height, width*pixel_ratio, height*pixel_ratio,
norm_method=lambda m: 5) norm_method=lambda m: 5)
grid_resistance_map = CanvasHexGridMultiAgents(portray_resistance_map,
width, height, width*pixel_ratio, height*pixel_ratio,
norm_method=lambda m: 1)
def norm_ants(model): def norm_ants(model):
return 5 return 5
@ -127,14 +148,19 @@ def setup(params=None):
[lambda m: "<h3>Ant density</h3><h5>Nest: Red, Food: Green</h5>", [lambda m: "<h3>Ant density</h3><h5>Nest: Red, Food: Green</h5>",
grid_ants, grid_ants,
lambda m: f"<h5>Normalization Value: {norm_ants(m)}</h5>", lambda m: f"<h5>Normalization Value: {norm_ants(m)}</h5>",
lambda m: "<h3>Pheromone Density</h3><h5>Pheromone A: Cyan, Pheromone B: Magenta</h5>", lambda m: "<h3>Pheromone Density</h3><h5>Pheromone A: Cyan, Pheromone B: Magenta, Resistance Map: Yellow</h5>",
grid_pheromones, grid_pheromones,
lambda m: f"<h5>Normalization Value: {norm_pheromones(m)}</h5>" lambda m: f"<h5>Normalization Value: {norm_pheromones(m)}</h5>",
], ],
"Active Random Walker Ants", params) "Active Random Walker Ants", params)
if __name__ == "__main__": if __name__ == "__main__":
server = setup() from model import kwargs_paper_setup1
kwargs_paper1_perlin = kwargs_paper_setup1
kwargs_paper1_perlin["height"] = 50
kwargs_paper1_perlin["width"] = 50
kwargs_paper1_perlin["resistance_map_type"] = "perlin"
server = setup(params=kwargs_paper1_perlin)
server.launch() server.launch()
""" """