From 876bb4910b1bb71b117ff80898177a886eec7b99 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 11:14:26 +0100 Subject: [PATCH 01/58] agent creation spearated --- abm/simulation/sims.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index c3fecf9c..d8f05233 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -346,15 +346,12 @@ def agent_agent_collision(self, agent1, agent2): else: # ghost mode is on, we do nothing on collision pass - def create_agents(self): - """Creating agents according to how the simulation class was initialized""" - i = 0 - while i < self.N: - x = np.random.randint(self.agent_radii, self.WIDTH - self.agent_radii) - y = np.random.randint(self.agent_radii, self.HEIGHT - self.agent_radii) - orient = np.random.uniform(0, 2*np.pi) + def add_new_agent(self, id, x, y, orient): + """Adding a single new agent into agent sprites""" + agent_proven = False + while not agent_proven: agent = Agent( - id=i, + id=id, radius=self.agent_radii, position=(x, y), orientation=orient, @@ -372,7 +369,16 @@ def create_agents(self): ) if self.proove_sprite(agent): self.agents.add(agent) - i += 1 + agent_proven = True + + def create_agents(self): + """Creating agents according to how the simulation class was initialized""" + for i in range(self.N): + x = np.random.randint(self.agent_radii, self.WIDTH - self.agent_radii) + y = np.random.randint(self.agent_radii, self.HEIGHT - self.agent_radii) + orient = np.random.uniform(0, 2*np.pi) + self.add_new_agent(i, x, y, orient) + def create_resources(self): """Creating resource patches according to how the simulation class was initialized""" From cd1f6d83a29fcc9e089767a03e7c3c0df1bd7992 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 11:14:41 +0100 Subject: [PATCH 02/58] add playground default params --- abm/contrib/playgroundtool.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 abm/contrib/playgroundtool.py diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py new file mode 100644 index 00000000..371a02a9 --- /dev/null +++ b/abm/contrib/playgroundtool.py @@ -0,0 +1,35 @@ +"""parameters for interactive playground simulations""" + +default_params = { + "N": 5, + "T": 100, + "v_field_res": 1200, + "width": 500, + "height": 500, + "framerate": 30, + "window_pad": 30, + "with_visualization": True, + "show_vis_field": False, + "show_vis_field_return": True, + "pooling_time": 0, + "pooling_prob":0, + "agent_radius": 10, + "N_resc": 10, + "min_resc_perpatch": 200, + "max_resc_perpatch": 201, + "min_resc_quality": 0.25, + "max_resc_quality": 0.25, + "patch_radius": 45, + "regenerate_patches": True, + "agent_consumption": 1, + "teleport_exploit": False, + "vision_range": 500, + "agent_fov": 0.5, + "visual_exclusion": True, + "show_vision_range": False, + "use_ifdb_logging": False, + "save_csv_files": False, + "ghost_mode": True, + "patchwise_exclusion": True, + "parallel": False +} \ No newline at end of file From 1ada16c51398d22cfedb76cea3bd26d0cee57d7e Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 11:14:56 +0100 Subject: [PATCH 03/58] add initial playground class --- abm/simulation/isims.py | 62 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 abm/simulation/isims.py diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py new file mode 100644 index 00000000..36754793 --- /dev/null +++ b/abm/simulation/isims.py @@ -0,0 +1,62 @@ +"""Implementing an interactive Playground Simulation class where the model parameters can be tuned real time""" + +import pygame +import numpy as np +import sys + +from abm.agent import supcalc +from abm.agent.agent import Agent +from abm.environment.rescource import Rescource +from abm.contrib import colors, ifdb_params +from abm.contrib import playgroundtool as pgt +from abm.simulation.sims import Simulation +from pygame_widgets.slider import Slider +from pygame_widgets.button import Button +from pygame_widgets.textbox import TextBox +from pygame_widgets.dropdown import Dropdown +import pygame_widgets +from abm.monitoring import ifdb +from abm.monitoring import env_saver +from math import atan2 +import os +import uuid + +from datetime import datetime + +# loading env variables from dotenv file +from dotenv import dotenv_values + +EXP_NAME = os.getenv("EXPERIMENT_NAME", "") +root_abm_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +env_path = os.path.join(root_abm_dir, f"{EXP_NAME}.env") + +envconf = dotenv_values(env_path) + + +class PlaygroundSimulation(Simulation): + def __init__(self): + super().__init__(**pgt.default_params) + self.vis_area_end_width = 2 * self.window_pad + self.WIDTH + self.vis_area_end_height = 2 * self.window_pad + self.HEIGHT + self.action_area_width = 400 + self.action_area_height = 800 + self.full_width = self.WIDTH + self.action_area_width + 2 * self.window_pad + self.full_height = self.action_area_height + + self.quit_term = False + self.screen = pygame.display.set_mode([self.full_width, self.full_height], pygame.RESIZABLE) + + # pygame widgets + self.slider_height = 20 + self.action_area_pad = 30 + self.textbox_width = 100 + self.slider_width = self.action_area_width - 2 * self.action_area_pad - self.textbox_width - 15 + self.slider_start_x = self.vis_area_end_width + self.action_area_pad + self.textbox_start_x = self.slider_start_x + self.slider_width + 15 + + slider_i = 1 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.framerate_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=5, max=60, step=1, initial=self.framerate) + self.framerate_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) From 546dc0fe29db04c728cb4654f8cd41f6f41aac98 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 11:15:17 +0100 Subject: [PATCH 04/58] add entrypoint and bump version --- abm/app.py | 6 ++++++ setup.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/abm/app.py b/abm/app.py index 2ced0ab4..cc75f629 100644 --- a/abm/app.py +++ b/abm/app.py @@ -1,4 +1,5 @@ from abm.simulation.sims import Simulation +from abm.simulation.isims import PlaygroundSimulation import os # loading env variables from dotenv file @@ -41,3 +42,8 @@ def start(parallel=False): parallel=parallel ) sim.start() + + +def start_playground(): + sim = PlaygroundSimulation() + sim.start() diff --git a/setup.py b/setup.py index 966965b1..7b32d43f 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name='P34 ABM', description='Agent based model framework to simulate collectively foraging agents relying on their private and social' 'visual cues. Written in pygame and python 3.7+', - version='0.0.1', + version='1.1.1', url='https://github.com/scioip34/ABM', maintainer='David Mezey and Dominik Deffner @ SCIoI', packages=find_packages(exclude=['tests']), @@ -33,6 +33,7 @@ entry_points={ 'console_scripts': [ 'abm-start=abm.app:start', + 'playground-start=abm.app:start_playground' ] }, classifiers=[ From 611fcdfadd89158a1631a58df935e830edb6ee4e Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 11:16:37 +0100 Subject: [PATCH 05/58] separate draw and display flip --- abm/simulation/sims.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index d8f05233..1e66051f 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -379,7 +379,6 @@ def create_agents(self): orient = np.random.uniform(0, 2*np.pi) self.add_new_agent(i, x, y, orient) - def create_resources(self): """Creating resource patches according to how the simulation class was initialized""" for i in range(self.N_resc): @@ -505,8 +504,6 @@ def draw_frame(self, stats, stats_pos): # showing visual fields of the agents self.show_visual_fields(stats, stats_pos) - pygame.display.flip() - def start(self): start_time = datetime.now() @@ -655,6 +652,7 @@ def start(self): # Draw environment and agents if self.with_visualization: self.draw_frame(stats, stats_pos) + pygame.display.flip() # Monitoring with IFDB if self.save_in_ifd: From c52b293b8d0a513e0441112e64ba4adab6482ad4 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 12:54:18 +0100 Subject: [PATCH 06/58] proving agent position is optional --- abm/simulation/sims.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 1e66051f..45073e76 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -346,7 +346,7 @@ def agent_agent_collision(self, agent1, agent2): else: # ghost mode is on, we do nothing on collision pass - def add_new_agent(self, id, x, y, orient): + def add_new_agent(self, id, x, y, orient, with_proove=True): """Adding a single new agent into agent sprites""" agent_proven = False while not agent_proven: @@ -367,7 +367,11 @@ def add_new_agent(self, id, x, y, orient): visual_exclusion=self.visual_exclusion, patchwise_exclusion=self.patchwise_exclusion ) - if self.proove_sprite(agent): + if with_proove: + if self.proove_sprite(agent): + self.agents.add(agent) + agent_proven = True + else: self.agents.add(agent) agent_proven = True From 26d5d7f88724db4ea42f8a6fd1763cc978c49a85 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 12:54:50 +0100 Subject: [PATCH 07/58] simulation restructured for inheritance --- abm/simulation/sims.py | 85 +++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 45073e76..5485ebe1 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -406,46 +406,47 @@ def create_vis_field_graph(self): stats_pos = (int(self.window_pad), int(self.window_pad)) return stats, stats_pos - def interact_with_event(self, event): + def interact_with_event(self, events): """Carry out functionality according to user's interaction""" - # Exit if requested - if event.type == pygame.QUIT: - sys.exit() - - # Change orientation with mouse wheel - if event.type == pygame.MOUSEWHEEL: - if event.y == -1: - event.y = 0 - for ag in self.agents: - ag.move_with_mouse(pygame.mouse.get_pos(), event.y, 1 - event.y) - - # Pause on Space - if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: - self.is_paused = not self.is_paused - - # Speed up on s and down on f. reset default framerate with d - if event.type == pygame.KEYDOWN and event.key == pygame.K_s: - self.framerate -= 1 - if self.framerate < 1: - self.framerate = 1 - if event.type == pygame.KEYDOWN and event.key == pygame.K_f: - self.framerate += 1 - if self.framerate > 35: - self.framerate = 35 - if event.type == pygame.KEYDOWN and event.key == pygame.K_d: - self.framerate = self.framerate_orig - - # Continuous mouse events (move with cursor) - if pygame.mouse.get_pressed()[0]: - try: + for event in events: + # Exit if requested + if event.type == pygame.QUIT: + sys.exit() + + # Change orientation with mouse wheel + if event.type == pygame.MOUSEWHEEL: + if event.y == -1: + event.y = 0 for ag in self.agents: - ag.move_with_mouse(event.pos, 0, 0) - except AttributeError: + ag.move_with_mouse(pygame.mouse.get_pos(), event.y, 1 - event.y) + + # Pause on Space + if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: + self.is_paused = not self.is_paused + + # Speed up on s and down on f. reset default framerate with d + if event.type == pygame.KEYDOWN and event.key == pygame.K_s: + self.framerate -= 1 + if self.framerate < 1: + self.framerate = 1 + if event.type == pygame.KEYDOWN and event.key == pygame.K_f: + self.framerate += 1 + if self.framerate > 35: + self.framerate = 35 + if event.type == pygame.KEYDOWN and event.key == pygame.K_d: + self.framerate = self.framerate_orig + + # Continuous mouse events (move with cursor) + if pygame.mouse.get_pressed()[0]: + try: + for ag in self.agents: + ag.move_with_mouse(event.pos, 0, 0) + except AttributeError: + for ag in self.agents: + ag.move_with_mouse(pygame.mouse.get_pos(), 0, 0) + else: for ag in self.agents: - ag.move_with_mouse(pygame.mouse.get_pos(), 0, 0) - else: - for ag in self.agents: - ag.is_moved_with_cursor = False + ag.is_moved_with_cursor = False def decide_on_vis_field_visibility(self, turned_on_vfield): """Deciding f the visual field needs to be shown or not""" @@ -519,7 +520,7 @@ def start(self): self.create_resources() # Creating surface to show visual fields - stats, stats_pos = self.create_vis_field_graph() + self.stats, self.stats_pos = self.create_vis_field_graph() # local var to decide when to show visual fields turned_on_vfield = 0 @@ -527,9 +528,9 @@ def start(self): # Main Simulation loop until dedicated simulation time while self.t < self.T: - for event in pygame.event.get(): - # Carry out interaction according to user activity - self.interact_with_event(event) + events = pygame.event.get() + # Carry out interaction according to user activity + self.interact_with_event(events) # deciding if vis field needs to be shown in this timestep turned_on_vfield = self.decide_on_vis_field_visibility(turned_on_vfield) @@ -655,7 +656,7 @@ def start(self): # Draw environment and agents if self.with_visualization: - self.draw_frame(stats, stats_pos) + self.draw_frame(self.stats, self.stats_pos) pygame.display.flip() # Monitoring with IFDB From fc8ddcfd650ef221d3e17c8bf0967d6e415f01d4 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 12:55:01 +0100 Subject: [PATCH 08/58] default params updated --- abm/contrib/playgroundtool.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index 371a02a9..192fe13b 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -2,7 +2,7 @@ default_params = { "N": 5, - "T": 100, + "T": 1000, "v_field_res": 1200, "width": 500, "height": 500, @@ -14,12 +14,12 @@ "pooling_time": 0, "pooling_prob":0, "agent_radius": 10, - "N_resc": 10, + "N_resc": 3, "min_resc_perpatch": 200, "max_resc_perpatch": 201, "min_resc_quality": 0.25, "max_resc_quality": 0.25, - "patch_radius": 45, + "patch_radius": 30, "regenerate_patches": True, "agent_consumption": 1, "teleport_exploit": False, From 3840fffc488ae1a086ba6e66940e559f33350cd7 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 12:55:15 +0100 Subject: [PATCH 09/58] agent and resource number tunable --- abm/simulation/isims.py | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 36754793..609530f0 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -60,3 +60,69 @@ def __init__(self): self.slider_height, min=5, max=60, step=1, initial=self.framerate) self.framerate_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 2 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.N_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=1, max=25, step=1, initial=self.N) + self.N_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 3 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.NRES_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=1, max=10, step=1, initial=self.N_resc) + self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + + def draw_frame(self, stats, stats_pos): + """Overwritten method of sims drawframe adding possibility to update pygame widgets""" + super().draw_frame(stats, stats_pos) + self.framerate_textbox.setText(f"framerate: {self.framerate}") + self.N_textbox.setText(f"N: {self.N}") + self.NRES_textbox.setText(f"$N_R$: {self.N_resc}") + self.framerate_textbox.draw() + self.framerate_slider.draw() + self.N_textbox.draw() + self.N_slider.draw() + self.NRES_textbox.draw() + self.NRES_slider.draw() + + def interact_with_event(self, events): + """Carry out functionality according to user's interaction""" + super().interact_with_event(events) + pygame_widgets.update(events) + self.framerate = self.framerate_slider.getValue() + self.N = self.N_slider.getValue() + self.N_resc = self.NRES_slider.getValue() + if self.N != len(self.agents): + self.act_on_N_mismatch() + if self.N_resc != len(self.rescources): + self.act_on_NRES_mismatch() + + def act_on_N_mismatch(self): + """method is called if the requested amount of agents is not the same as what the playground already has""" + if self.N > len(self.agents): + diff = self.N - len(self.agents) + for i in range(diff): + ag_id = len(self.agents) - 1 + x = np.random.randint(self.agent_radii, self.WIDTH - self.agent_radii) + y = np.random.randint(self.agent_radii, self.HEIGHT - self.agent_radii) + orient = np.random.uniform(0, 2 * np.pi) + self.add_new_agent(ag_id, x, y, orient, with_proove=False) + else: + while self.N < len(self.agents): + for i, ag in enumerate(self.agents): + if i == len(self.agents)-1: + ag.kill() + self.stats, self.stats_pos = self.create_vis_field_graph() + + def act_on_NRES_mismatch(self): + """method is called if the requested amount of patches is not the same as what the playground already has""" + if self.N_resc > len(self.rescources): + diff = self.N_resc - len(self.rescources) + for i in range(diff): + self.add_new_resource_patch() + else: + while self.N_resc < len(self.rescources): + for i, res in enumerate(self.rescources): + if i == len(self.rescources)-1: + res.kill() \ No newline at end of file From 67d81932f11f7243c506b4097823092421f48966 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 13:12:47 +0100 Subject: [PATCH 10/58] tunable FOV --- abm/simulation/isims.py | 21 ++++++++++++++++++++- abm/simulation/sims.py | 3 ++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 609530f0..6593eb48 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -54,6 +54,7 @@ def __init__(self): self.slider_start_x = self.vis_area_end_width + self.action_area_pad self.textbox_start_x = self.slider_start_x + self.slider_width + 15 + ## First Slider column slider_i = 1 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.framerate_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, @@ -72,19 +73,28 @@ def __init__(self): self.slider_height, min=1, max=10, step=1, initial=self.N_resc) self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 4 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.FOV_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=1, step=0.05, initial=self.agent_fov[1]/np.pi) + self.FOV_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) def draw_frame(self, stats, stats_pos): """Overwritten method of sims drawframe adding possibility to update pygame widgets""" super().draw_frame(stats, stats_pos) self.framerate_textbox.setText(f"framerate: {self.framerate}") self.N_textbox.setText(f"N: {self.N}") - self.NRES_textbox.setText(f"$N_R$: {self.N_resc}") + self.NRES_textbox.setText(f"N_R: {self.N_resc}") + self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio*100)}%") self.framerate_textbox.draw() self.framerate_slider.draw() self.N_textbox.draw() self.N_slider.draw() self.NRES_textbox.draw() self.NRES_slider.draw() + self.FOV_textbox.draw() + self.FOV_slider.draw() def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -93,10 +103,19 @@ def interact_with_event(self, events): self.framerate = self.framerate_slider.getValue() self.N = self.N_slider.getValue() self.N_resc = self.NRES_slider.getValue() + self.fov_ratio = self.FOV_slider.getValue() if self.N != len(self.agents): self.act_on_N_mismatch() if self.N_resc != len(self.rescources): self.act_on_NRES_mismatch() + if self.fov_ratio != self.agent_fov[1]/np.pi: + self.update_agent_fovs() + + def update_agent_fovs(self): + """Updateing the FOV of agents according to acquired value from slider""" + self.agent_fov = (-self.fov_ratio*np.pi, self.fov_ratio*np.pi) + for agent in self.agents: + agent.FOV = self.agent_fov def act_on_N_mismatch(self): """method is called if the requested amount of agents is not the same as what the playground already has""" diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 5485ebe1..98e603c1 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -138,7 +138,8 @@ def __init__(self, N, T, v_field_res=800, width=600, height=480, self.agent_consumption = agent_consumption self.teleport_exploit = teleport_exploit self.vision_range = vision_range - self.agent_fov = (-agent_fov * np.pi, agent_fov * np.pi) + self.fov_ratio = agent_fov + self.agent_fov = (-self.fov_ratio * np.pi, self.fov_ratio * np.pi) self.visual_exclusion = visual_exclusion self.ghost_mode = ghost_mode self.patchwise_exclusion = patchwise_exclusion From 62a168f1a3deab5380761555ae4c8467c8432c21 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:05:19 +0100 Subject: [PATCH 11/58] patch size tunable --- abm/contrib/playgroundtool.py | 2 +- abm/environment/rescource.py | 4 ++-- abm/simulation/isims.py | 37 +++++++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index 192fe13b..e587dab0 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -2,7 +2,7 @@ default_params = { "N": 5, - "T": 1000, + "T": 100000, "v_field_res": 1200, "width": 500, "height": 500, diff --git a/abm/environment/rescource.py b/abm/environment/rescource.py index 715c95e5..fa5b5a9b 100644 --- a/abm/environment/rescource.py +++ b/abm/environment/rescource.py @@ -65,8 +65,8 @@ def __init__(self, id, radius, position, env_size, color, window_pad, resc_units ) self.mask = pygame.mask.from_surface(self.image) self.rect = self.image.get_rect() - self.rect.x = self.position[0] - self.rect.y = self.position[1] + self.rect.centerx = self.center[0] + self.rect.centery = self.center[1] font = pygame.font.Font(None, 25) text = font.render(f"{self.radius}", True, colors.BLACK) self.image.blit(text, (0, 0)) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 6593eb48..971b084f 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -79,14 +79,21 @@ def __init__(self): self.slider_height, min=0, max=1, step=0.05, initial=self.agent_fov[1]/np.pi) self.FOV_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 5 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.RESradius_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=10, max=100, step=5, initial=self.resc_radius) + self.RESradius_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) def draw_frame(self, stats, stats_pos): """Overwritten method of sims drawframe adding possibility to update pygame widgets""" super().draw_frame(stats, stats_pos) - self.framerate_textbox.setText(f"framerate: {self.framerate}") + self.framerate_textbox.setText(f"Framerate: {self.framerate}") self.N_textbox.setText(f"N: {self.N}") self.NRES_textbox.setText(f"N_R: {self.N_resc}") self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio*100)}%") + self.RESradius_textbox.setText(f"R_R: {int(self.resc_radius)}") self.framerate_textbox.draw() self.framerate_slider.draw() self.N_textbox.draw() @@ -95,6 +102,8 @@ def draw_frame(self, stats, stats_pos): self.NRES_slider.draw() self.FOV_textbox.draw() self.FOV_slider.draw() + self.RESradius_textbox.draw() + self.RESradius_slider.draw() def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -110,6 +119,21 @@ def interact_with_event(self, events): self.act_on_NRES_mismatch() if self.fov_ratio != self.agent_fov[1]/np.pi: self.update_agent_fovs() + if self.resc_radius != self.RESradius_slider.getValue(): + self.resc_radius = self.RESradius_slider.getValue() + self.update_res_radius() + + def update_res_radius(self): + """Changing the resource patch radius according to slider value""" + # adjusting number of patches + for res in self.rescources: + # # update position + res.position[0] = res.center[0] - self.resc_radius + res.position[1] = res.center[1] - self.resc_radius + # self.center = (self.position[0] + self.radius, self.position[1] + self.radius) + res.radius = self.resc_radius + res.rect.x = res.position[0] + res.rect.y = res.position[1] def update_agent_fovs(self): """Updateing the FOV of agents according to acquired value from slider""" @@ -139,7 +163,16 @@ def act_on_NRES_mismatch(self): if self.N_resc > len(self.rescources): diff = self.N_resc - len(self.rescources) for i in range(diff): - self.add_new_resource_patch() + sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi + print(sum_area, 0.5 * self.WIDTH * self.HEIGHT) + if sum_area > 0.5 * self.WIDTH * self.HEIGHT: + while sum_area > 0.5 * self.WIDTH * self.HEIGHT: + self.resc_radius -= 5 + self.RESradius_slider.setValue(self.resc_radius) + sum_area = (len(self.rescources)+1) * self.resc_radius * np.pi * np.pi + self.update_res_radius() + else: + self.add_new_resource_patch() else: while self.N_resc < len(self.rescources): for i, res in enumerate(self.rescources): From cea1faab4618dd7107be8fac6da944006b0e2dfc Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:10:05 +0100 Subject: [PATCH 12/58] prepare dec parameter tuning --- abm/agent/agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/abm/agent/agent.py b/abm/agent/agent.py index 448b8577..44f26241 100644 --- a/abm/agent/agent.py +++ b/abm/agent/agent.py @@ -95,6 +95,8 @@ def __init__(self, id, radius, position, orientation, env_size, color, v_field_r self.g_u = decision_params.g_u self.B_u = decision_params.B_u self.u_max = decision_params.u_max + self.F_n = decision_params.F_N + self.F_r = decision_params.F_R # Pooling attributes self.time_spent_pooling = 0 # time units currently spent with pooling the status of given position (changes @@ -133,7 +135,7 @@ def calc_I_priv(self): collected_unit = self.collected_r - self.collected_r_before # calculating private info by weighting these - self.I_priv = decision_params.F_N * np.max(self.novelty) + decision_params.F_R * collected_unit + self.I_priv = self.F_N * np.max(self.novelty) + self.F_R * collected_unit def move_with_mouse(self, mouse, left_state, right_state): """Moving the agent with the mouse cursor, and rotating""" From 276a74917917aca7e5166a29f36f5236cd65832d Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:10:13 +0100 Subject: [PATCH 13/58] cleanup --- abm/simulation/isims.py | 1 - 1 file changed, 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 971b084f..a212c8da 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -164,7 +164,6 @@ def act_on_NRES_mismatch(self): diff = self.N_resc - len(self.rescources) for i in range(diff): sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi - print(sum_area, 0.5 * self.WIDTH * self.HEIGHT) if sum_area > 0.5 * self.WIDTH * self.HEIGHT: while sum_area > 0.5 * self.WIDTH * self.HEIGHT: self.resc_radius -= 5 From 478452418fb50686566d3f806d7ad677b3007016 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:26:22 +0100 Subject: [PATCH 14/58] Tunable Epsw --- abm/agent/agent.py | 4 ++-- abm/simulation/isims.py | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/abm/agent/agent.py b/abm/agent/agent.py index 44f26241..50bd6e2d 100644 --- a/abm/agent/agent.py +++ b/abm/agent/agent.py @@ -95,8 +95,8 @@ def __init__(self, id, radius, position, orientation, env_size, color, v_field_r self.g_u = decision_params.g_u self.B_u = decision_params.B_u self.u_max = decision_params.u_max - self.F_n = decision_params.F_N - self.F_r = decision_params.F_R + self.F_N = decision_params.F_N + self.F_R = decision_params.F_R # Pooling attributes self.time_spent_pooling = 0 # time units currently spent with pooling the status of given position (changes diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index a212c8da..b4ff3411 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -85,6 +85,13 @@ def __init__(self): self.slider_height, min=10, max=100, step=5, initial=self.resc_radius) self.RESradius_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 6 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.Eps_w = 2 + self.Epsw_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_w) + self.Epsw_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) def draw_frame(self, stats, stats_pos): """Overwritten method of sims drawframe adding possibility to update pygame widgets""" @@ -94,6 +101,7 @@ def draw_frame(self, stats, stats_pos): self.NRES_textbox.setText(f"N_R: {self.N_resc}") self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio*100)}%") self.RESradius_textbox.setText(f"R_R: {int(self.resc_radius)}") + self.Epsw_textbox.setText(f"E_w: {self.Eps_w:.2f}") self.framerate_textbox.draw() self.framerate_slider.draw() self.N_textbox.draw() @@ -104,6 +112,8 @@ def draw_frame(self, stats, stats_pos): self.FOV_slider.draw() self.RESradius_textbox.draw() self.RESradius_slider.draw() + self.Epsw_textbox.draw() + self.Epsw_slider.draw() def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -122,6 +132,14 @@ def interact_with_event(self, events): if self.resc_radius != self.RESradius_slider.getValue(): self.resc_radius = self.RESradius_slider.getValue() self.update_res_radius() + if self.Eps_w != self.Epsw_slider.getValue(): + self.Eps_w = self.Epsw_slider.getValue() + self.update_agent_decision_params() + + def update_agent_decision_params(self): + """Updateing agent decision parameters according to changed slider values""" + for ag in self.agents: + ag.Eps_w = self.Eps_w def update_res_radius(self): """Changing the resource patch radius according to slider value""" @@ -151,6 +169,8 @@ def act_on_N_mismatch(self): y = np.random.randint(self.agent_radii, self.HEIGHT - self.agent_radii) orient = np.random.uniform(0, 2 * np.pi) self.add_new_agent(ag_id, x, y, orient, with_proove=False) + self.update_agent_decision_params() + self.update_agent_fovs() else: while self.N < len(self.agents): for i, ag in enumerate(self.agents): From 249eec78e96f5ad621ef58499af83fdb4f2d5698 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:30:54 +0100 Subject: [PATCH 15/58] Tunable Epsu --- abm/simulation/isims.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index b4ff3411..f8a28fec 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -92,6 +92,13 @@ def __init__(self): self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_w) self.Epsw_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 7 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.Eps_u = 1 + self.Epsu_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_u) + self.Epsu_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) def draw_frame(self, stats, stats_pos): """Overwritten method of sims drawframe adding possibility to update pygame widgets""" @@ -102,6 +109,7 @@ def draw_frame(self, stats, stats_pos): self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio*100)}%") self.RESradius_textbox.setText(f"R_R: {int(self.resc_radius)}") self.Epsw_textbox.setText(f"E_w: {self.Eps_w:.2f}") + self.Epsu_textbox.setText(f"E_u: {self.Eps_u:.2f}") self.framerate_textbox.draw() self.framerate_slider.draw() self.N_textbox.draw() @@ -114,6 +122,8 @@ def draw_frame(self, stats, stats_pos): self.RESradius_slider.draw() self.Epsw_textbox.draw() self.Epsw_slider.draw() + self.Epsu_textbox.draw() + self.Epsu_slider.draw() def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -135,11 +145,15 @@ def interact_with_event(self, events): if self.Eps_w != self.Epsw_slider.getValue(): self.Eps_w = self.Epsw_slider.getValue() self.update_agent_decision_params() + if self.Eps_u != self.Epsu_slider.getValue(): + self.Eps_u = self.Epsu_slider.getValue() + self.update_agent_decision_params() def update_agent_decision_params(self): """Updateing agent decision parameters according to changed slider values""" for ag in self.agents: ag.Eps_w = self.Eps_w + ag.Eps_u = self.Eps_u def update_res_radius(self): """Changing the resource patch radius according to slider value""" From 7e96c4564a886f44cc3cdec6268844ccc81f65a8 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:45:49 +0100 Subject: [PATCH 16/58] Improved FOV visualization --- abm/simulation/isims.py | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index f8a28fec..cc8f7d6f 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -210,4 +210,43 @@ def act_on_NRES_mismatch(self): while self.N_resc < len(self.rescources): for i, res in enumerate(self.rescources): if i == len(self.rescources)-1: - res.kill() \ No newline at end of file + res.kill() + + def draw_visual_fields(self): + """Visualizing the range of vision for agents as opaque circles around the agents""" + for agent in self.agents: + FOV = agent.FOV + + # Show limits of FOV + if 0 < FOV[1] < np.pi: + + # Center and radius of pie chart + cx, cy, r = agent.position[0] + agent.radius, agent.position[1] + agent.radius, 100 + + angle = (2 * FOV[1]) / np.pi * 360 + p = [(cx, cy)] + # Get points on arc + angles = [agent.orientation + FOV[0], agent.orientation + FOV[1]] + step_size = (angles[1] - angles[0]) / 50 + angles_array = np.arange(angles[0], angles[1] + step_size, step_size) + for n in angles_array: + x = cx + int(r * np.cos(n)) + y = cy + int(r * - np.sin(n)) + p.append((x, y)) + p.append((cx, cy)) + + image = pygame.Surface([self.vis_area_end_width, self.vis_area_end_height]) + image.fill(colors.BACKGROUND) + image.set_colorkey(colors.BACKGROUND) + image.set_alpha(10) + pygame.draw.polygon(image, colors.GREEN, p) + self.screen.blit(image, (0, 0)) + + elif FOV[1] == np.pi: + image = pygame.Surface([self.vis_area_end_width, self.vis_area_end_height]) + image.fill(colors.BACKGROUND) + image.set_colorkey(colors.BACKGROUND) + image.set_alpha(10) + cx, cy, r = agent.position[0] + agent.radius, agent.position[1] + agent.radius, 100 + pygame.draw.circle(image, colors.GREEN, (cx, cy), r) + self.screen.blit(image, (0, 0)) From 63504b8740bc2de117a916aa511e1ecee302d86b Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 18 Feb 2022 14:46:15 +0100 Subject: [PATCH 17/58] unlimited visual range --- abm/contrib/playgroundtool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index e587dab0..c34bbe94 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -23,7 +23,7 @@ "regenerate_patches": True, "agent_consumption": 1, "teleport_exploit": False, - "vision_range": 500, + "vision_range": 5000, "agent_fov": 0.5, "visual_exclusion": True, "show_vision_range": False, From 4eb7efc59849a9b4a9be8bc78fc39ac060e34cc0 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Sat, 19 Feb 2022 15:18:45 +0100 Subject: [PATCH 18/58] higher number of patches possible --- abm/simulation/isims.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index cc8f7d6f..885ff640 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -70,7 +70,7 @@ def __init__(self): slider_i = 3 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.NRES_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=1, max=10, step=1, initial=self.N_resc) + self.slider_height, min=1, max=100, step=1, initial=self.N_resc) self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) slider_i = 4 @@ -198,8 +198,8 @@ def act_on_NRES_mismatch(self): diff = self.N_resc - len(self.rescources) for i in range(diff): sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi - if sum_area > 0.5 * self.WIDTH * self.HEIGHT: - while sum_area > 0.5 * self.WIDTH * self.HEIGHT: + if sum_area > 0.3 * self.WIDTH * self.HEIGHT: + while sum_area > 0.3 * self.WIDTH * self.HEIGHT: self.resc_radius -= 5 self.RESradius_slider.setValue(self.resc_radius) sum_area = (len(self.rescources)+1) * self.resc_radius * np.pi * np.pi From f7dd6ee2ea6dd6012c5331c6ac85112f5b6986b4 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Sat, 19 Feb 2022 16:54:50 +0100 Subject: [PATCH 19/58] total resource unit number tunable --- abm/simulation/isims.py | 66 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 885ff640..72efbe0e 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -3,6 +3,7 @@ import pygame import numpy as np import sys +from math import floor, ceil from abm.agent import supcalc from abm.agent.agent import Agent @@ -99,6 +100,27 @@ def __init__(self): self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_u) self.Epsu_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + slider_i = 8 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.SUM_res = self.get_total_resource() + self.SUMR_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=self.SUM_res+200, step=100, initial=self.SUM_res) + self.SUMR_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + + + def update_SUMR(self): + self.SUM_res = self.get_total_resource() + self.SUMR_slider.min = self.N_resc + self.SUMR_slider.max = 2 * self.SUM_res + self.SUMR_slider.setValue(self.SUM_res) + + def get_total_resource(self): + """Calculating the total number of resource units in the arena""" + SUMR = 0 + for res in self.rescources: + SUMR += res.resc_units + return SUMR def draw_frame(self, stats, stats_pos): """Overwritten method of sims drawframe adding possibility to update pygame widgets""" @@ -110,6 +132,9 @@ def draw_frame(self, stats, stats_pos): self.RESradius_textbox.setText(f"R_R: {int(self.resc_radius)}") self.Epsw_textbox.setText(f"E_w: {self.Eps_w:.2f}") self.Epsu_textbox.setText(f"E_u: {self.Eps_u:.2f}") + if self.SUM_res == 0: + self.update_SUMR() + self.SUMR_textbox.setText(f"SUM R: {self.SUM_res:.2f}") self.framerate_textbox.draw() self.framerate_slider.draw() self.N_textbox.draw() @@ -124,6 +149,8 @@ def draw_frame(self, stats, stats_pos): self.Epsw_slider.draw() self.Epsu_textbox.draw() self.Epsu_slider.draw() + self.SUMR_textbox.draw() + self.SUMR_slider.draw() def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -148,6 +175,28 @@ def interact_with_event(self, events): if self.Eps_u != self.Epsu_slider.getValue(): self.Eps_u = self.Epsu_slider.getValue() self.update_agent_decision_params() + if self.SUM_res != self.SUMR_slider.getValue(): + self.SUM_res = self.SUMR_slider.getValue() + self.distribute_sumR() + + def distribute_sumR(self): + """If the amount of requestedtotal amount changes we decrease the amount of resource of all resources in a way that + the original resource ratios remain the same""" + resource_ratios = [] + remaining_pecents=[] + current_sum_res = self.get_total_resource() + for ri, res in enumerate(self.rescources): + resource_ratios.append(res.resc_units/current_sum_res) + remaining_pecents.append(res.resc_left/res.resc_units) + + # now changing the amount of all and remaining resources according to new sumres + for ri, res in enumerate(self.rescources): + res.resc_units = resource_ratios[ri] * self.SUM_res + res.resc_left = remaining_pecents[ri] * res.resc_units + res.update() + + self.min_resc_units = floor(self.SUM_res / self.N_resc) + self.max_resc_units = max(ceil(self.SUM_res / self.N_resc), floor(self.SUM_res / self.N_resc)+1) def update_agent_decision_params(self): """Updateing agent decision parameters according to changed slider values""" @@ -155,9 +204,22 @@ def update_agent_decision_params(self): ag.Eps_w = self.Eps_w ag.Eps_u = self.Eps_u + def pop_resource(self): + for res in self.rescources: + res.kill() + break + self.N_resc = len(self.rescources) + self.NRES_slider.setValue(self.N_resc) + def update_res_radius(self): """Changing the resource patch radius according to slider value""" # adjusting number of patches + sum_area = len(self.rescources) * self.resc_radius * self.resc_radius * np.pi + if sum_area > 0.3 * self.WIDTH * self.HEIGHT: + while sum_area > 0.3 * self.WIDTH * self.HEIGHT: + self.pop_resource() + sum_area = len(self.rescources) * self.resc_radius * self.resc_radius * np.pi + for res in self.rescources: # # update position res.position[0] = res.center[0] - self.resc_radius @@ -166,6 +228,7 @@ def update_res_radius(self): res.radius = self.resc_radius res.rect.x = res.position[0] res.rect.y = res.position[1] + res.update() def update_agent_fovs(self): """Updateing the FOV of agents according to acquired value from slider""" @@ -202,7 +265,7 @@ def act_on_NRES_mismatch(self): while sum_area > 0.3 * self.WIDTH * self.HEIGHT: self.resc_radius -= 5 self.RESradius_slider.setValue(self.resc_radius) - sum_area = (len(self.rescources)+1) * self.resc_radius * np.pi * np.pi + sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi self.update_res_radius() else: self.add_new_resource_patch() @@ -211,6 +274,7 @@ def act_on_NRES_mismatch(self): for i, res in enumerate(self.rescources): if i == len(self.rescources)-1: res.kill() + self.update_SUMR() def draw_visual_fields(self): """Visualizing the range of vision for agents as opaque circles around the agents""" From 7affded2df84441b8e707acfed1996b23eb610f1 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 09:26:57 +0100 Subject: [PATCH 20/58] float precision fixed --- abm/simulation/sims.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 98e603c1..1876c5c3 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -268,7 +268,7 @@ def draw_agent_stats(self, font_size=15, spacing=0): for agent in self.agents: status = [ f"ID: {agent.id}", - f"res.: {agent.collected_r}", + f"res.: {agent.collected_r:.2f}", f"ori.: {agent.orientation:.2f}", f"w: {agent.w:.2f}" ] From 2588006edf41f615991835049280872320984af8 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 10:43:54 +0100 Subject: [PATCH 21/58] add help messages --- abm/contrib/playgroundtool.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index c34bbe94..640b6a22 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -32,4 +32,31 @@ "ghost_mode": True, "patchwise_exclusion": True, "parallel": False +} + +help_messages = { + 'framerate': ''' + + Framerate [fps]: + + The framerate is a parameter that defines how often + (per second) is the state of the simulation is + updated. In case the simulation has many agents/resources + it might happen that the requested + framerate is impossible to keep with the given hardware. + In that case the maximal possible framerate + will be kept. + + ''', + 'N':''' + + Number of agents, N [pcs]: + + The number of agent controls how many individuals the + group consists of in terms of foraging agents visualized + as colorful circles with a white line showing their ori- + entations. The field of view of the agents are shown + with a green circle slice. + + ''' } \ No newline at end of file From 3a6aa17107e5b9ffa9864e5f1d98f41d1828d9be Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 10:45:08 +0100 Subject: [PATCH 22/58] add help buttons and inhibition sliders, change slider heights --- abm/simulation/isims.py | 135 ++++++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 26 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 72efbe0e..a24b914c 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -37,6 +37,8 @@ class PlaygroundSimulation(Simulation): def __init__(self): super().__init__(**pgt.default_params) + self.help_message = "" + self.is_help_shown = False self.vis_area_end_width = 2 * self.window_pad + self.WIDTH self.vis_area_end_height = 2 * self.window_pad + self.HEIGHT self.action_area_width = 400 @@ -46,14 +48,19 @@ def __init__(self): self.quit_term = False self.screen = pygame.display.set_mode([self.full_width, self.full_height], pygame.RESIZABLE) + self.help_buttons = [] # pygame widgets - self.slider_height = 20 - self.action_area_pad = 30 + self.slider_height = 10 + self.textbox_height = 20 + self.help_height = self.textbox_height + self.help_width = self.help_height + self.action_area_pad = 40 self.textbox_width = 100 self.slider_width = self.action_area_width - 2 * self.action_area_pad - self.textbox_width - 15 self.slider_start_x = self.vis_area_end_width + self.action_area_pad self.textbox_start_x = self.slider_start_x + self.slider_width + 15 + self.help_start_x = self.textbox_start_x + self.textbox_width + 15 ## First Slider column slider_i = 1 @@ -61,52 +68,97 @@ def __init__(self): self.framerate_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=5, max=60, step=1, initial=self.framerate) self.framerate_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.framerate_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.framerate_help.onClick = lambda: self.show_help('framerate', self.framerate_help) + self.framerate_help.onRelease = lambda: self.unshow_help(self.framerate_help) + self.help_buttons.append(self.framerate_help) + slider_i = 2 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.N_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=1, max=25, step=1, initial=self.N) + self.slider_height, min=1, max=25, step=1, initial=self.N) self.N_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.N_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.N_help.onClick = lambda: self.show_help('N', self.N_help) + self.N_help.onRelease = lambda: self.unshow_help(self.N_help) + self.help_buttons.append(self.N_help) + slider_i = 3 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.NRES_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=1, max=100, step=1, initial=self.N_resc) + self.slider_height, min=1, max=100, step=1, initial=self.N_resc) self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) slider_i = 4 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.FOV_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=0, max=1, step=0.05, initial=self.agent_fov[1]/np.pi) + self.slider_height, min=0, max=1, step=0.05, initial=self.agent_fov[1] / np.pi) self.FOV_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) slider_i = 5 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.RESradius_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=10, max=100, step=5, initial=self.resc_radius) self.RESradius_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) slider_i = 6 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.Eps_w = 2 self.Epsw_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_w) + self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_w) self.Epsw_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) slider_i = 7 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.Eps_u = 1 self.Epsu_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_u) + self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_u) self.Epsu_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) slider_i = 8 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.S_wu = 0 + self.SWU_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=2, step=0.1, initial=self.S_wu) + self.SWU_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + slider_i = 9 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) + self.S_uw = 0 + self.SUW_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, + self.slider_height, min=0, max=2, step=0.1, initial=self.S_uw) + self.SUW_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + slider_i = 10 + slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.SUM_res = self.get_total_resource() self.SUMR_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=0, max=self.SUM_res+200, step=100, initial=self.SUM_res) self.SUMR_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, - self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) + self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + + def show_help(self, help_decide_str, pressed_button): + for hb in self.help_buttons: + hb.inactiveColour = colors.GREY + if not self.is_paused: + self.is_paused = True + self.is_help_shown = True + self.help_message = pgt.help_messages[help_decide_str] + pressed_button.inactiveColour = colors.GREEN + + def unshow_help(self, pressed_button): + for hb in self.help_buttons: + hb.inactiveColour = colors.GREY + self.is_help_shown = False + if self.is_paused: + self.is_paused = False + pressed_button.inactiveColour = colors.GREY def update_SUMR(self): @@ -128,10 +180,12 @@ def draw_frame(self, stats, stats_pos): self.framerate_textbox.setText(f"Framerate: {self.framerate}") self.N_textbox.setText(f"N: {self.N}") self.NRES_textbox.setText(f"N_R: {self.N_resc}") - self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio*100)}%") + self.FOV_textbox.setText(f"FOV: {int(self.fov_ratio * 100)}%") self.RESradius_textbox.setText(f"R_R: {int(self.resc_radius)}") self.Epsw_textbox.setText(f"E_w: {self.Eps_w:.2f}") self.Epsu_textbox.setText(f"E_u: {self.Eps_u:.2f}") + self.SUW_textbox.setText(f"S_uw: {self.S_uw:.2f}") + self.SWU_textbox.setText(f"S_wu: {self.S_wu:.2f}") if self.SUM_res == 0: self.update_SUMR() self.SUMR_textbox.setText(f"SUM R: {self.SUM_res:.2f}") @@ -151,6 +205,27 @@ def draw_frame(self, stats, stats_pos): self.Epsu_slider.draw() self.SUMR_textbox.draw() self.SUMR_slider.draw() + self.SUW_slider.draw() + self.SUW_textbox.draw() + self.SWU_textbox.draw() + self.SWU_slider.draw() + for hb in self.help_buttons: + hb.draw() + if self.is_help_shown: + self.draw_help_message() + + def draw_help_message(self): + image = pygame.Surface([self.vis_area_end_width, self.vis_area_end_height]) + image.fill(colors.BACKGROUND) + image.set_alpha(200) + line_height = 20 + font = pygame.font.Font(None, line_height) + status = self.help_message.split("\n") + for i, stat_i in enumerate(status): + text_color = colors.BLACK + text = font.render(stat_i, True, text_color) + image.blit(text, (self.window_pad, i * line_height)) + self.screen.blit(image, (0, 0)) def interact_with_event(self, events): """Carry out functionality according to user's interaction""" @@ -164,7 +239,7 @@ def interact_with_event(self, events): self.act_on_N_mismatch() if self.N_resc != len(self.rescources): self.act_on_NRES_mismatch() - if self.fov_ratio != self.agent_fov[1]/np.pi: + if self.fov_ratio != self.agent_fov[1] / np.pi: self.update_agent_fovs() if self.resc_radius != self.RESradius_slider.getValue(): self.resc_radius = self.RESradius_slider.getValue() @@ -178,16 +253,22 @@ def interact_with_event(self, events): if self.SUM_res != self.SUMR_slider.getValue(): self.SUM_res = self.SUMR_slider.getValue() self.distribute_sumR() + if self.S_uw != self.SUW_slider.getValue(): + self.S_uw = self.SUW_slider.getValue() + self.update_agent_decision_params() + if self.S_wu != self.SWU_slider.getValue(): + self.S_wu = self.SWU_slider.getValue() + self.update_agent_decision_params() def distribute_sumR(self): """If the amount of requestedtotal amount changes we decrease the amount of resource of all resources in a way that the original resource ratios remain the same""" resource_ratios = [] - remaining_pecents=[] + remaining_pecents = [] current_sum_res = self.get_total_resource() for ri, res in enumerate(self.rescources): - resource_ratios.append(res.resc_units/current_sum_res) - remaining_pecents.append(res.resc_left/res.resc_units) + resource_ratios.append(res.resc_units / current_sum_res) + remaining_pecents.append(res.resc_left / res.resc_units) # now changing the amount of all and remaining resources according to new sumres for ri, res in enumerate(self.rescources): @@ -196,13 +277,15 @@ def distribute_sumR(self): res.update() self.min_resc_units = floor(self.SUM_res / self.N_resc) - self.max_resc_units = max(ceil(self.SUM_res / self.N_resc), floor(self.SUM_res / self.N_resc)+1) + self.max_resc_units = max(ceil(self.SUM_res / self.N_resc), floor(self.SUM_res / self.N_resc) + 1) def update_agent_decision_params(self): """Updateing agent decision parameters according to changed slider values""" for ag in self.agents: ag.Eps_w = self.Eps_w ag.Eps_u = self.Eps_u + ag.S_uw = self.S_uw + ag.S_wu = self.S_wu def pop_resource(self): for res in self.rescources: @@ -232,7 +315,7 @@ def update_res_radius(self): def update_agent_fovs(self): """Updateing the FOV of agents according to acquired value from slider""" - self.agent_fov = (-self.fov_ratio*np.pi, self.fov_ratio*np.pi) + self.agent_fov = (-self.fov_ratio * np.pi, self.fov_ratio * np.pi) for agent in self.agents: agent.FOV = self.agent_fov @@ -251,7 +334,7 @@ def act_on_N_mismatch(self): else: while self.N < len(self.agents): for i, ag in enumerate(self.agents): - if i == len(self.agents)-1: + if i == len(self.agents) - 1: ag.kill() self.stats, self.stats_pos = self.create_vis_field_graph() @@ -260,19 +343,19 @@ def act_on_NRES_mismatch(self): if self.N_resc > len(self.rescources): diff = self.N_resc - len(self.rescources) for i in range(diff): - sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi + sum_area = (len(self.rescources) + 1) * self.resc_radius * self.resc_radius * np.pi if sum_area > 0.3 * self.WIDTH * self.HEIGHT: while sum_area > 0.3 * self.WIDTH * self.HEIGHT: self.resc_radius -= 5 self.RESradius_slider.setValue(self.resc_radius) - sum_area = (len(self.rescources)+1) * self.resc_radius * self.resc_radius * np.pi + sum_area = (len(self.rescources) + 1) * self.resc_radius * self.resc_radius * np.pi self.update_res_radius() else: self.add_new_resource_patch() else: while self.N_resc < len(self.rescources): for i, res in enumerate(self.rescources): - if i == len(self.rescources)-1: + if i == len(self.rescources) - 1: res.kill() self.update_SUMR() From abef6058687a4af1c02f4a94f34f8417a1cd0b80 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 10:45:19 +0100 Subject: [PATCH 23/58] cleanup --- abm/simulation/isims.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index a24b914c..a34e5aa1 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -139,7 +139,7 @@ def __init__(self): slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.SUM_res = self.get_total_resource() self.SUMR_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=0, max=self.SUM_res+200, step=100, initial=self.SUM_res) + self.slider_height, min=0, max=self.SUM_res + 200, step=100, initial=self.SUM_res) self.SUMR_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) From 77dea0cfbc622da81ec779218045de8b7864a3f0 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 11:14:38 +0100 Subject: [PATCH 24/58] additional help messages and buttons --- abm/contrib/playgroundtool.py | 58 +++++++++++++++++++++++++++++++++-- abm/simulation/isims.py | 37 ++++++++++++++++++++++ 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index 640b6a22..d7df4b75 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -26,7 +26,7 @@ "vision_range": 5000, "agent_fov": 0.5, "visual_exclusion": True, - "show_vision_range": False, + "show_vision_range": True, "use_ifdb_logging": False, "save_csv_files": False, "ghost_mode": True, @@ -58,5 +58,59 @@ entations. The field of view of the agents are shown with a green circle slice. - ''' + ''', + 'N_res':''' + + Number of resource patches, N_res [pcs]: + + The number of resource patches in the environment. + Each resource patch can be exploited by agents and they + contain a given amount of resource units. The quality + of the patch controls how fast (unit/time) can be + the resource patch exploited by a single agent. Resource + patches are shown as gray circles on the screen. + + ''', + 'FOV': ''' + + Field of View, FOV [%]: + + The amount (in percent) of visible area around individual + agents. In case this is 0, the agents are blind. In case + it is 100, the agents have a full 360° FOV. + + ''', + 'RES': ''' + + Resource Radius, R [px]: + + The radius of resource patches in pixels. In case the overall + covered resource area on the arena exceeds 30% of the total space + the number of resource patches will be automatically decreased. + + ''', + 'Epsw': ''' + + Social Excitability, E_w [a.U.]: + + The parameter controls how socially excitable agents are, i.e. how + much a unit of social information can increase/bias the decision + process of the agent towards socially guided behavior (Relocation). + In case this parameter is 0, agents do not integrate social cues + at all. The larger this parameter is, the faster individuals respond + to visible exploiting agents and the farther social cues are triggering + relocation movement. + + ''', + 'Epsu': ''' + + Individual Preference Factor, E_u [a.U.]: + + The parameter controls how much a unit of exploited resource biases + the agent towards further exploitation of resources. + In case this parameter is low, agents will get picky + with resource patches, i.e. they stop exploiting low-quality + patches after an initial sampling period. + + ''', } \ No newline at end of file diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index a34e5aa1..59b41c68 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -95,18 +95,39 @@ def __init__(self): self.slider_height, min=1, max=100, step=1, initial=self.N_resc) self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.NRES_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.NRES_help.onClick = lambda: self.show_help('N_res', self.NRES_help) + self.NRES_help.onRelease = lambda: self.unshow_help(self.NRES_help) + self.help_buttons.append(self.NRES_help) + slider_i = 4 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.FOV_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=0, max=1, step=0.05, initial=self.agent_fov[1] / np.pi) self.FOV_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.FOV_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.FOV_help.onClick = lambda: self.show_help('FOV', self.FOV_help) + self.FOV_help.onRelease = lambda: self.unshow_help(self.FOV_help) + self.help_buttons.append(self.FOV_help) + slider_i = 5 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.RESradius_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=10, max=100, step=5, initial=self.resc_radius) self.RESradius_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.RES_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.RES_help.onClick = lambda: self.show_help('RES', self.RES_help) + self.RES_help.onRelease = lambda: self.unshow_help(self.RES_help) + self.help_buttons.append(self.RES_help) + slider_i = 6 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.Eps_w = 2 @@ -114,6 +135,13 @@ def __init__(self): self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_w) self.Epsw_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.Epsw_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.Epsw_help.onClick = lambda: self.show_help('Epsw', self.Epsw_help) + self.Epsw_help.onRelease = lambda: self.unshow_help(self.Epsw_help) + self.help_buttons.append(self.Epsw_help) + slider_i = 7 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.Eps_u = 1 @@ -121,6 +149,13 @@ def __init__(self): self.slider_height, min=0, max=5, step=0.1, initial=self.Eps_u) self.Epsu_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.Epsu_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.Epsu_help.onClick = lambda: self.show_help('Epsu', self.Epsu_help) + self.Epsu_help.onRelease = lambda: self.unshow_help(self.Epsu_help) + self.help_buttons.append(self.Epsu_help) + slider_i = 8 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.S_wu = 0 @@ -128,6 +163,7 @@ def __init__(self): self.slider_height, min=0, max=2, step=0.1, initial=self.S_wu) self.SWU_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + slider_i = 9 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.S_uw = 0 @@ -135,6 +171,7 @@ def __init__(self): self.slider_height, min=0, max=2, step=0.1, initial=self.S_uw) self.SUW_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + slider_i = 10 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.SUM_res = self.get_total_resource() From e8368d5642e6145300f1e255185be8d08ccd0edb Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 12:42:00 +0100 Subject: [PATCH 25/58] additional help messages and buttons --- abm/contrib/playgroundtool.py | 128 +++++++++++++++++++++------------- abm/simulation/isims.py | 19 +++++ 2 files changed, 97 insertions(+), 50 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index d7df4b75..e96c17b4 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -37,80 +37,108 @@ help_messages = { 'framerate': ''' - Framerate [fps]: - - The framerate is a parameter that defines how often - (per second) is the state of the simulation is - updated. In case the simulation has many agents/resources - it might happen that the requested - framerate is impossible to keep with the given hardware. - In that case the maximal possible framerate - will be kept. + Framerate [fps]: + + The framerate is a parameter that defines how often + (per second) is the state of the simulation is + updated. In case the simulation has many agents/resources + it might happen that the requested + framerate is impossible to keep with the given hardware. + In that case the maximal possible framerate + will be kept. ''', 'N':''' - Number of agents, N [pcs]: - - The number of agent controls how many individuals the - group consists of in terms of foraging agents visualized - as colorful circles with a white line showing their ori- - entations. The field of view of the agents are shown - with a green circle slice. + Number of agents, N [pcs]: + + The number of agent controls how many individuals the + group consists of in terms of foraging agents visualized + as colorful circles with a white line showing their ori- + entations. The field of view of the agents are shown + with a green circle slice. ''', 'N_res':''' - Number of resource patches, N_res [pcs]: - - The number of resource patches in the environment. - Each resource patch can be exploited by agents and they - contain a given amount of resource units. The quality - of the patch controls how fast (unit/time) can be - the resource patch exploited by a single agent. Resource - patches are shown as gray circles on the screen. + Number of resource patches, N_res [pcs]: + + The number of resource patches in the environment. + Each resource patch can be exploited by agents and they + contain a given amount of resource units. The quality + of the patch controls how fast (unit/time) can be + the resource patch exploited by a single agent. Resource + patches are shown as gray circles on the screen. ''', 'FOV': ''' - Field of View, FOV [%]: - - The amount (in percent) of visible area around individual - agents. In case this is 0, the agents are blind. In case - it is 100, the agents have a full 360° FOV. + Field of View, FOV [%]: + + The amount (in percent) of visible area around individual + agents. In case this is 0, the agents are blind. In case + it is 100, the agents have a full 360° FOV. ''', 'RES': ''' - Resource Radius, R [px]: - - The radius of resource patches in pixels. In case the overall - covered resource area on the arena exceeds 30% of the total space - the number of resource patches will be automatically decreased. + Resource Radius, R [px]: + + The radius of resource patches in pixels. In case the overall + covered resource area on the arena exceeds 30% of the total space + the number of resource patches will be automatically decreased. ''', 'Epsw': ''' - Social Excitability, E_w [a.U.]: - - The parameter controls how socially excitable agents are, i.e. how - much a unit of social information can increase/bias the decision - process of the agent towards socially guided behavior (Relocation). - In case this parameter is 0, agents do not integrate social cues - at all. The larger this parameter is, the faster individuals respond - to visible exploiting agents and the farther social cues are triggering - relocation movement. + Social Excitability, E_w [a.U.]: + + The parameter controls how socially excitable agents are, i.e. how + much a unit of social information can increase/bias the decision + process of the agent towards socially guided behavior (Relocation). + In case this parameter is 0, agents do not integrate social cues + at all. The larger this parameter is, the faster individuals respond + to visible exploiting agents and the farther social cues are triggering + relocation movement. ''', 'Epsu': ''' - Individual Preference Factor, E_u [a.U.]: - - The parameter controls how much a unit of exploited resource biases - the agent towards further exploitation of resources. - In case this parameter is low, agents will get picky - with resource patches, i.e. they stop exploiting low-quality - patches after an initial sampling period. + Individual Preference Factor, E_u [a.U.]: + + The parameter controls how much a unit of exploited resource biases + the agent towards further exploitation of resources. + In case this parameter is low, agents will get picky + with resource patches, i.e. they stop exploiting low-quality + patches after an initial sampling period. ''', + 'SWU': ''' + + W to U Cross-inhibition strength, S_wu [a.U.]: + + The parameter controls how much socially guided behvaior and + integration of social cues inhibit individual exploitation + behavior. Note, that thsi inhibition is only present if the + social integrator w is above it's decision threshold. + + ''', + 'SUW': ''' + + U to W Cross-inhibition strength, S_uw [a.U.]: + + The parameter controls how much individual exploitation behvaior and + integration of individual information inhibits social + behavior. Note, that thsi inhibition is only present if the + individual integrator u is above it's decision threshold. + + ''', + 'SUMR': ''' + + Total number of resource units, SUM_r [pcs]: + + The parameter controls how many resource units should be overall + distributed in the environment. + + ''' } \ No newline at end of file diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 59b41c68..ea144e63 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -163,6 +163,12 @@ def __init__(self): self.slider_height, min=0, max=2, step=0.1, initial=self.S_wu) self.SWU_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.SWU_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.SWU_help.onClick = lambda: self.show_help('SWU', self.SWU_help) + self.SWU_help.onRelease = lambda: self.unshow_help(self.SWU_help) + self.help_buttons.append(self.SWU_help) slider_i = 9 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -171,6 +177,13 @@ def __init__(self): self.slider_height, min=0, max=2, step=0.1, initial=self.S_uw) self.SUW_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.SUW_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.SUW_help.onClick = lambda: self.show_help('SUW', self.SUW_help) + self.SUW_help.onRelease = lambda: self.unshow_help(self.SUW_help) + self.help_buttons.append(self.SUW_help) + slider_i = 10 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -179,6 +192,12 @@ def __init__(self): self.slider_height, min=0, max=self.SUM_res + 200, step=100, initial=self.SUM_res) self.SUMR_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) + self.SUMR_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) + self.SUMR_help.onClick = lambda: self.show_help('SUMR', self.SUMR_help) + self.SUMR_help.onRelease = lambda: self.unshow_help(self.SUMR_help) + self.help_buttons.append(self.SUMR_help) def show_help(self, help_decide_str, pressed_button): for hb in self.help_buttons: From 75769d9d39a8cb2cac24fda545f8291071db95b3 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 12:43:10 +0100 Subject: [PATCH 26/58] remove framerate drawing --- abm/simulation/isims.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index ea144e63..1880efb8 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -270,6 +270,9 @@ def draw_frame(self, stats, stats_pos): if self.is_help_shown: self.draw_help_message() + def draw_framerate(self): + pass + def draw_help_message(self): image = pygame.Surface([self.vis_area_end_width, self.vis_area_end_height]) image.fill(colors.BACKGROUND) From f6e7525f4eadc7365b9e848fed4f546af506f885 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 13:00:32 +0100 Subject: [PATCH 27/58] added some global stats --- abm/simulation/isims.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 1880efb8..68100365 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -37,6 +37,8 @@ class PlaygroundSimulation(Simulation): def __init__(self): super().__init__(**pgt.default_params) + self.prev_overall_coll_r = 0 + self.overall_col_r = 0 self.help_message = "" self.is_help_shown = False self.vis_area_end_width = 2 * self.window_pad + self.WIDTH @@ -269,10 +271,29 @@ def draw_frame(self, stats, stats_pos): hb.draw() if self.is_help_shown: self.draw_help_message() + self.draw_global_stats() def draw_framerate(self): pass + def draw_global_stats(self): + image = pygame.Surface([self.vis_area_end_width, self.full_height]) + image.fill(colors.BACKGROUND) + image.set_colorkey(colors.BACKGROUND) + image.set_alpha(200) + line_height = 20 + font = pygame.font.Font(None, line_height) + status = [] + self.overall_col_r = np.sum([ag.collected_r for ag in self.agents]) + status.append(f"Total collected units: {self.overall_col_r:.2f} U") + status.append(f"Exploitation Rate: {self.prev_overall_coll_r - self.overall_col_r:.2f} U/timestep") + for i, stat_i in enumerate(status): + text_color = colors.BLACK + text = font.render(stat_i, True, text_color) + image.blit(text, (self.window_pad, self.vis_area_end_height + i * line_height)) + self.screen.blit(image, (0, 0)) + self.prev_overall_coll_r = self.overall_col_r + def draw_help_message(self): image = pygame.Surface([self.vis_area_end_width, self.vis_area_end_height]) image.fill(colors.BACKGROUND) From 55d54db7ef38d198308650d1332a30744b39bb79 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 13:28:10 +0100 Subject: [PATCH 28/58] some global stats --- abm/contrib/ifdb_params.py | 16 ++++++++++------ abm/simulation/isims.py | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/abm/contrib/ifdb_params.py b/abm/contrib/ifdb_params.py index 9758e2c5..3add918e 100644 --- a/abm/contrib/ifdb_params.py +++ b/abm/contrib/ifdb_params.py @@ -3,6 +3,7 @@ from dotenv import dotenv_values EXP_NAME = os.getenv("EXPERIMENT_NAME", "") +WRITE_EACH_POINT = os.getenv("WRITE_EACH_POINT") root_abm_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) env_path = os.path.join(root_abm_dir, f"{EXP_NAME}.env") @@ -14,13 +15,16 @@ INFLUX_PSWD = "password" INFLUX_DB_NAME = "home" -T = float(int(envconf["T"])) -if T <= 1000: - write_batch_size = T +if WRITE_EACH_POINT is not None: + write_batch_size = 1 else: - if T % 1000 != 0: - raise Exception("Simulation time (T) must be dividable by 1000 or smaller than 1000!") - write_batch_size = 1000 + T = float(int(envconf["T"])) + if T <= 1000: + write_batch_size = T + else: + if T % 1000 != 0: + raise Exception("Simulation time (T) must be dividable by 1000 or smaller than 1000!") + write_batch_size = 1000 # SAVE_DIR is counted from the ABM parent directory. SAVE_DIR = envconf.get("SAVE_ROOT_DIR", "abm/data/simulation_data") diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 68100365..401bdd9a 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -286,7 +286,7 @@ def draw_global_stats(self): status = [] self.overall_col_r = np.sum([ag.collected_r for ag in self.agents]) status.append(f"Total collected units: {self.overall_col_r:.2f} U") - status.append(f"Exploitation Rate: {self.prev_overall_coll_r - self.overall_col_r:.2f} U/timestep") + status.append(f"Exploitation Rate: {self.overall_coll_r - self.prev_overall_col_r:.2f} U/timestep") for i, stat_i in enumerate(status): text_color = colors.BLACK text = font.render(stat_i, True, text_color) From 8b696add03a73edfc849ee33fa7e2f13a88b993f Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 14:20:59 +0100 Subject: [PATCH 29/58] fix bug --- abm/simulation/isims.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 401bdd9a..c6c28f91 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -286,7 +286,7 @@ def draw_global_stats(self): status = [] self.overall_col_r = np.sum([ag.collected_r for ag in self.agents]) status.append(f"Total collected units: {self.overall_col_r:.2f} U") - status.append(f"Exploitation Rate: {self.overall_coll_r - self.prev_overall_col_r:.2f} U/timestep") + status.append(f"Exploitation Rate: {self.overall_col_r - self.prev_overall_coll_r:.2f} U/timestep") for i, stat_i in enumerate(status): text_color = colors.BLACK text = font.render(stat_i, True, text_color) From 2481306fd7ebc1d3e7c593ff8e5c0b1483419857 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 15:54:47 +0100 Subject: [PATCH 30/58] add video path --- abm/contrib/playgroundtool.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index e96c17b4..8d20fa9f 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -1,5 +1,7 @@ """parameters for interactive playground simulations""" +VIDEO_SAVE_DIR = "abm/data/videos" + default_params = { "N": 5, "T": 100000, @@ -27,7 +29,7 @@ "agent_fov": 0.5, "visual_exclusion": True, "show_vision_range": True, - "use_ifdb_logging": False, + "use_ifdb_logging": True, "save_csv_files": False, "ghost_mode": True, "patchwise_exclusion": True, From 5da61b347a73794314c04bb5e515cfc84db3a17a Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 15:54:59 +0100 Subject: [PATCH 31/58] video can be saved --- abm/simulation/isims.py | 93 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 82 insertions(+), 11 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index c6c28f91..8313b347 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -1,26 +1,22 @@ """Implementing an interactive Playground Simulation class where the model parameters can be tuned real time""" +import shutil import pygame import numpy as np -import sys from math import floor, ceil -from abm.agent import supcalc -from abm.agent.agent import Agent -from abm.environment.rescource import Rescource from abm.contrib import colors, ifdb_params from abm.contrib import playgroundtool as pgt from abm.simulation.sims import Simulation from pygame_widgets.slider import Slider from pygame_widgets.button import Button from pygame_widgets.textbox import TextBox -from pygame_widgets.dropdown import Dropdown +from abm.monitoring.ifdb import pad_to_n_digits + import pygame_widgets -from abm.monitoring import ifdb -from abm.monitoring import env_saver -from math import atan2 import os -import uuid +import cv2 + from datetime import datetime @@ -41,8 +37,15 @@ def __init__(self): self.overall_col_r = 0 self.help_message = "" self.is_help_shown = False + self.is_recording = False + self.save_video = False + self.video_save_path = os.path.join(root_abm_dir, pgt.VIDEO_SAVE_DIR) + self.image_save_path = os.path.join(self.video_save_path, "tmp") + shutil.rmtree(self.image_save_path) + os.makedirs(self.image_save_path, exist_ok=True) self.vis_area_end_width = 2 * self.window_pad + self.WIDTH self.vis_area_end_height = 2 * self.window_pad + self.HEIGHT + self.global_stats_start = self.vis_area_end_height self.action_area_width = 400 self.action_area_height = 800 self.full_width = self.WIDTH + self.action_area_width + 2 * self.window_pad @@ -57,6 +60,9 @@ def __init__(self): self.textbox_height = 20 self.help_height = self.textbox_height self.help_width = self.help_height + self.function_button_width = 100 + self.function_button_height = 20 + self.function_button_pad = 20 self.action_area_pad = 40 self.textbox_width = 100 self.slider_width = self.action_area_width - 2 * self.action_area_pad - self.textbox_width - 15 @@ -64,6 +70,20 @@ def __init__(self): self.textbox_start_x = self.slider_start_x + self.slider_width + 15 self.help_start_x = self.textbox_start_x + self.textbox_width + 15 + ## Function Button Row + self.function_buttons = [] + function_button_start = self.window_pad + self.start_button = Button(self.screen, function_button_start, self.vis_area_end_height, self.function_button_width, + self.function_button_height, text='Start/Stop', fontSize=self.function_button_height - 2, + inactiveColour=colors.GREEN, borderThickness=1, onClick=lambda: self.start_stop()) + self.function_buttons.append(self.start_button) + function_button_start += self.function_button_width + self.function_button_pad + self.record_button = Button(self.screen, function_button_start, self.vis_area_end_height, self.function_button_width, + self.function_button_height, text='Record', fontSize=self.function_button_height - 2, + inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.start_stop_record()) + self.function_buttons.append(self.record_button) + self.global_stats_start += self.function_button_height + self.window_pad + ## First Slider column slider_i = 1 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -201,6 +221,26 @@ def __init__(self): self.SUMR_help.onRelease = lambda: self.unshow_help(self.SUMR_help) self.help_buttons.append(self.SUMR_help) + def start_stop_record(self): + """Start or stop the recording of the simulation into a vdieo""" + if not self.is_recording: + self.is_recording = True + self.record_button.inactiveColour = colors.RED + else: + self.is_recording = False + self.save_video = True + self.record_button.inactiveColour = colors.GREY + self.help_message = "SAVING VIDEO..." + self.draw_help_message() + + + def start_stop(self): + self.is_paused = not self.is_paused + if self.start_button.inactiveColour != colors.GREY: + self.start_button.inactiveColour = colors.GREY + else: + self.start_button.inactiveColour = colors.GREEN + def show_help(self, help_decide_str, pressed_button): for hb in self.help_buttons: hb.inactiveColour = colors.GREY @@ -218,7 +258,6 @@ def unshow_help(self, pressed_button): self.is_paused = False pressed_button.inactiveColour = colors.GREY - def update_SUMR(self): self.SUM_res = self.get_total_resource() self.SUMR_slider.min = self.N_resc @@ -269,9 +308,17 @@ def draw_frame(self, stats, stats_pos): self.SWU_slider.draw() for hb in self.help_buttons: hb.draw() + for fb in self.function_buttons: + fb.draw() if self.is_help_shown: self.draw_help_message() self.draw_global_stats() + if self.save_video: + self.help_message = "\n\n\n SAVING VIDEO, PLEASE WAIT!" + self.draw_help_message() + pygame.display.flip() + self.saved_images_to_video() + self.save_video = False def draw_framerate(self): pass @@ -290,7 +337,7 @@ def draw_global_stats(self): for i, stat_i in enumerate(status): text_color = colors.BLACK text = font.render(stat_i, True, text_color) - image.blit(text, (self.window_pad, self.vis_area_end_height + i * line_height)) + image.blit(text, (self.window_pad, self.global_stats_start + i * line_height)) self.screen.blit(image, (0, 0)) self.prev_overall_coll_r = self.overall_col_r @@ -339,6 +386,10 @@ def interact_with_event(self, events): if self.S_wu != self.SWU_slider.getValue(): self.S_wu = self.SWU_slider.getValue() self.update_agent_decision_params() + if self.is_recording: + filename = f"{pad_to_n_digits(self.t, n=6)}.jpeg" + path = os.path.join(self.image_save_path, filename) + pygame.image.save(self.screen, path) def distribute_sumR(self): """If the amount of requestedtotal amount changes we decrease the amount of resource of all resources in a way that @@ -477,3 +528,23 @@ def draw_visual_fields(self): cx, cy, r = agent.position[0] + agent.radius, agent.position[1] + agent.radius, 100 pygame.draw.circle(image, colors.GREEN, (cx, cy), r) self.screen.blit(image, (0, 0)) + + def saved_images_to_video(self): + timestamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + image_folder = self.image_save_path + video_name = os.path.join(self.video_save_path, f'recording_{timestamp}.mp4') + + images = sorted([img for img in os.listdir(image_folder) if img.endswith(".jpeg")]) + frame = cv2.imread(os.path.join(image_folder, images[0])) + height, width, layers = frame.shape + + fourcc = cv2.VideoWriter_fourcc(*'mp4v') + video = cv2.VideoWriter(video_name, fourcc, self.framerate, (width, height)) + + for image in images: + video.write(cv2.imread(os.path.join(image_folder, image))) + + cv2.destroyAllWindows() + video.release() + shutil.rmtree(self.image_save_path) + os.makedirs(self.image_save_path, exist_ok=True) \ No newline at end of file From 7999512ce0025c69923db44034f6b7e2945112a3 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Mon, 21 Feb 2022 15:55:13 +0100 Subject: [PATCH 32/58] add opencv in setup.py --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7b32d43f..9b8f7905 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,8 @@ 'matplotlib', 'python-dotenv', 'pandas', - 'influxdb' + 'influxdb', + 'opencv-python' ], extras_require={ 'test': [ From 8d6a73f94baa761ea551c5084557bc3a0319b7bf Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 09:56:04 +0100 Subject: [PATCH 33/58] fix path bug --- abm/simulation/isims.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 8313b347..37297967 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -41,7 +41,8 @@ def __init__(self): self.save_video = False self.video_save_path = os.path.join(root_abm_dir, pgt.VIDEO_SAVE_DIR) self.image_save_path = os.path.join(self.video_save_path, "tmp") - shutil.rmtree(self.image_save_path) + if os.path.isdir(self.image_save_path): + shutil.rmtree(self.image_save_path) os.makedirs(self.image_save_path, exist_ok=True) self.vis_area_end_width = 2 * self.window_pad + self.WIDTH self.vis_area_end_height = 2 * self.window_pad + self.HEIGHT From a3c5e60b0c2149c22733945061ef5f0f07b23f22 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 09:56:13 +0100 Subject: [PATCH 34/58] fix agent id bug --- abm/simulation/isims.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 37297967..638cf691 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -456,7 +456,7 @@ def act_on_N_mismatch(self): if self.N > len(self.agents): diff = self.N - len(self.agents) for i in range(diff): - ag_id = len(self.agents) - 1 + ag_id = len(self.agents) x = np.random.randint(self.agent_radii, self.WIDTH - self.agent_radii) y = np.random.randint(self.agent_radii, self.HEIGHT - self.agent_radii) orient = np.random.uniform(0, 2 * np.pi) From 8bced801ebc214099e7abf8ab43a9d996af154a3 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 10:26:32 +0100 Subject: [PATCH 35/58] show statis only on click --- abm/simulation/sims.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 1876c5c3..6cac619d 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -263,9 +263,10 @@ def draw_framerate(self): def draw_agent_stats(self, font_size=15, spacing=0): """Showing agent information when paused""" - if self.is_paused: - font = pygame.font.Font(None, font_size) - for agent in self.agents: + # if self.is_paused: + font = pygame.font.Font(None, font_size) + for agent in self.agents: + if agent.is_moved_with_cursor: status = [ f"ID: {agent.id}", f"res.: {agent.collected_r:.2f}", @@ -442,12 +443,18 @@ def interact_with_event(self, events): try: for ag in self.agents: ag.move_with_mouse(event.pos, 0, 0) + for res in self.rescources: + res.update_clicked_status(event.pos) except AttributeError: for ag in self.agents: ag.move_with_mouse(pygame.mouse.get_pos(), 0, 0) else: for ag in self.agents: ag.is_moved_with_cursor = False + ag.draw_update() + for res in self.rescources: + res.is_clicked = False + res.update() def decide_on_vis_field_visibility(self, turned_on_vfield): """Deciding f the visual field needs to be shown or not""" From 41ea8cba7f74b7a7d075eda0e13b5e635f91b5f3 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 10:26:48 +0100 Subject: [PATCH 36/58] show statis only on click --- abm/environment/rescource.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/abm/environment/rescource.py b/abm/environment/rescource.py index fa5b5a9b..99fc3f5a 100644 --- a/abm/environment/rescource.py +++ b/abm/environment/rescource.py @@ -5,6 +5,7 @@ import numpy as np from abm.contrib import colors + class Rescource(pygame.sprite.Sprite): """ Rescource class that includes all private parameters of the rescource patch and all methods necessary to exploit @@ -43,6 +44,7 @@ def __init__(self, id, radius, position, env_size, color, window_pad, resc_units self.color = color self.resc_left_color = colors.DARK_GREY self.unit_per_timestep = quality # saved + self.is_clicked = False # Environment related parameters self.WIDTH = env_size[0] # env width @@ -67,9 +69,19 @@ def __init__(self, id, radius, position, env_size, color, window_pad, resc_units self.rect = self.image.get_rect() self.rect.centerx = self.center[0] self.rect.centery = self.center[1] - font = pygame.font.Font(None, 25) - text = font.render(f"{self.radius}", True, colors.BLACK) - self.image.blit(text, (0, 0)) + if self.is_clicked: + font = pygame.font.Font(None, 25) + text = font.render(f"{self.radius}", True, colors.BLACK) + self.image.blit(text, (0, 0)) + + def update_clicked_status(self, mouse): + """Checking if the resource patch was clicked on a mouse event""" + if self.rect.collidepoint(mouse): + self.is_clicked = True + self.update() + else: + self.is_clicked = False + self.update() def update(self): # Initial Visualization of rescource @@ -87,10 +99,11 @@ def update(self): self.rect.centerx = self.center[0] self.rect.centery = self.center[1] self.mask = pygame.mask.from_surface(self.image) - font = pygame.font.Font(None, 18) - text = font.render(f"{self.resc_left:.2f}, Q{self.unit_per_timestep:.2f}", True, colors.BLACK) - self.image.blit(text, (0, 0)) - text_rect = text.get_rect(center=self.rect.center) + if self.is_clicked: + font = pygame.font.Font(None, 18) + text = font.render(f"{self.resc_left:.2f}, Q{self.unit_per_timestep:.2f}", True, colors.BLACK) + self.image.blit(text, (0, 0)) + text_rect = text.get_rect(center=self.rect.center) def deplete(self, rescource_units): """depeting the given patch with given rescource units""" @@ -101,11 +114,10 @@ def deplete(self, rescource_units): if self.resc_left >= rescource_units: self.resc_left -= rescource_units depleted_units = rescource_units - else: # can not deplete more than what is left + else: # can not deplete more than what is left depleted_units = self.resc_left self.resc_left = 0 if self.resc_left > 0: return depleted_units, False else: return depleted_units, True - From 22712d7ea79fe97b1a59b62d6e3206a0f0c74190 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 10:26:58 +0100 Subject: [PATCH 37/58] show statis only on click --- abm/agent/agent.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/abm/agent/agent.py b/abm/agent/agent.py index 50bd6e2d..82cf355b 100644 --- a/abm/agent/agent.py +++ b/abm/agent/agent.py @@ -52,6 +52,7 @@ def __init__(self, id, radius, position, orientation, env_size, color, v_field_r self.position = np.array(position, dtype=np.float64) # saved self.orientation = orientation # saved self.color = color + self.selected_color = colors.LIGHT_BLUE self.v_field_res = v_field_res self.pooling_time = pooling_time self.pooling_prob = pooling_prob @@ -266,9 +267,15 @@ def draw_update(self): self.image = pygame.Surface([self.radius * 2, self.radius * 2]) self.image.fill(colors.BACKGROUND) self.image.set_colorkey(colors.BACKGROUND) - pygame.draw.circle( - self.image, self.color, (self.radius, self.radius), self.radius - ) + if self.is_moved_with_cursor: + pygame.draw.circle( + self.image, self.selected_color, (self.radius, self.radius), self.radius + ) + else: + pygame.draw.circle( + self.image, self.color, (self.radius, self.radius), self.radius + ) + # showing agent orientation with a line towards agent orientation pygame.draw.line(self.image, colors.BACKGROUND, (self.radius, self.radius), ((1 + np.cos(self.orientation)) * self.radius, (1 - np.sin(self.orientation)) * self.radius), From c6c9986c801d9a1796942066374a898d2f9a1307 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 10:47:21 +0100 Subject: [PATCH 38/58] cleanup and comment --- abm/simulation/isims.py | 159 +++++++++++++++++++++++++--------------- 1 file changed, 101 insertions(+), 58 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 638cf691..10f55ff0 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -17,7 +17,6 @@ import os import cv2 - from datetime import datetime # loading env variables from dotenv file @@ -33,28 +32,43 @@ class PlaygroundSimulation(Simulation): def __init__(self): super().__init__(**pgt.default_params) - self.prev_overall_coll_r = 0 - self.overall_col_r = 0 + # fixing the total number of possible resources, so it is redistributed with changing NR + self.SUM_res_fixed = True + self.prev_overall_coll_r = 0 # total collected resources in previous step + self.overall_col_r = 0 # total collected resources by all agents + self.SUM_res = self.get_total_resource() # total possible amount of resources self.help_message = "" self.is_help_shown = False self.is_recording = False - self.save_video = False - self.video_save_path = os.path.join(root_abm_dir, pgt.VIDEO_SAVE_DIR) - self.image_save_path = os.path.join(self.video_save_path, "tmp") + self.save_video = False # trigger to save video from screenshots + self.video_save_path = os.path.join(root_abm_dir, pgt.VIDEO_SAVE_DIR) # path to save video + self.image_save_path = os.path.join(self.video_save_path, "tmp") # path from collect screenshots + # enabling paths if os.path.isdir(self.image_save_path): shutil.rmtree(self.image_save_path) os.makedirs(self.image_save_path, exist_ok=True) + # GUI parameters + # visualization area of the simulation self.vis_area_end_width = 2 * self.window_pad + self.WIDTH self.vis_area_end_height = 2 * self.window_pad + self.HEIGHT + # starting global statistics text self.global_stats_start = self.vis_area_end_height + # start of sliders self.action_area_width = 400 self.action_area_height = 800 + # full window parameters self.full_width = self.WIDTH + self.action_area_width + 2 * self.window_pad self.full_height = self.action_area_height self.quit_term = False self.screen = pygame.display.set_mode([self.full_width, self.full_height], pygame.RESIZABLE) + + # button groups self.help_buttons = [] + self.function_buttons = [] + # sliders and other gui elements + self.sliders = [] + self.slider_texts = [] # pygame widgets self.slider_height = 10 @@ -72,17 +86,28 @@ def __init__(self): self.help_start_x = self.textbox_start_x + self.textbox_width + 15 ## Function Button Row - self.function_buttons = [] - function_button_start = self.window_pad - self.start_button = Button(self.screen, function_button_start, self.vis_area_end_height, self.function_button_width, - self.function_button_height, text='Start/Stop', fontSize=self.function_button_height - 2, + function_button_start_x = self.window_pad + self.start_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.function_button_width, + self.function_button_height, text='Start/Stop', + fontSize=self.function_button_height - 2, inactiveColour=colors.GREEN, borderThickness=1, onClick=lambda: self.start_stop()) self.function_buttons.append(self.start_button) - function_button_start += self.function_button_width + self.function_button_pad - self.record_button = Button(self.screen, function_button_start, self.vis_area_end_height, self.function_button_width, - self.function_button_height, text='Record', fontSize=self.function_button_height - 2, - inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.start_stop_record()) + function_button_start_x += self.function_button_width + self.function_button_pad + self.record_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.function_button_width, + self.function_button_height, text='Record', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREY, borderThickness=1, + onClick=lambda: self.start_stop_record()) self.function_buttons.append(self.record_button) + function_button_start_x += self.function_button_width + self.function_button_pad + self.fix_SUM_res_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.function_button_width, + self.function_button_height, text='Fix Total Units', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.fix_SUM_res()) + self.function_buttons.append(self.fix_SUM_res_button) self.global_stats_start += self.function_button_height + self.window_pad ## First Slider column @@ -98,6 +123,8 @@ def __init__(self): self.framerate_help.onClick = lambda: self.show_help('framerate', self.framerate_help) self.framerate_help.onRelease = lambda: self.unshow_help(self.framerate_help) self.help_buttons.append(self.framerate_help) + self.sliders.append(self.framerate_slider) + self.slider_texts.append(self.framerate_textbox) slider_i = 2 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -106,11 +133,13 @@ def __init__(self): self.N_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.N_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.N_help.onClick = lambda: self.show_help('N', self.N_help) self.N_help.onRelease = lambda: self.unshow_help(self.N_help) self.help_buttons.append(self.N_help) + self.sliders.append(self.N_slider) + self.slider_texts.append(self.N_textbox) slider_i = 3 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -119,11 +148,13 @@ def __init__(self): self.NRES_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.NRES_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.NRES_help.onClick = lambda: self.show_help('N_res', self.NRES_help) self.NRES_help.onRelease = lambda: self.unshow_help(self.NRES_help) self.help_buttons.append(self.NRES_help) + self.sliders.append(self.NRES_slider) + self.slider_texts.append(self.NRES_textbox) slider_i = 4 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -132,11 +163,13 @@ def __init__(self): self.FOV_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.FOV_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.FOV_help.onClick = lambda: self.show_help('FOV', self.FOV_help) self.FOV_help.onRelease = lambda: self.unshow_help(self.FOV_help) self.help_buttons.append(self.FOV_help) + self.sliders.append(self.FOV_slider) + self.slider_texts.append(self.FOV_textbox) slider_i = 5 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -145,11 +178,13 @@ def __init__(self): self.RESradius_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.RES_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.RES_help.onClick = lambda: self.show_help('RES', self.RES_help) self.RES_help.onRelease = lambda: self.unshow_help(self.RES_help) self.help_buttons.append(self.RES_help) + self.sliders.append(self.RESradius_slider) + self.slider_texts.append(self.RESradius_textbox) slider_i = 6 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -159,11 +194,13 @@ def __init__(self): self.Epsw_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.Epsw_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.Epsw_help.onClick = lambda: self.show_help('Epsw', self.Epsw_help) self.Epsw_help.onRelease = lambda: self.unshow_help(self.Epsw_help) self.help_buttons.append(self.Epsw_help) + self.sliders.append(self.Epsw_slider) + self.slider_texts.append(self.Epsw_textbox) slider_i = 7 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -173,11 +210,13 @@ def __init__(self): self.Epsu_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.Epsu_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.Epsu_help.onClick = lambda: self.show_help('Epsu', self.Epsu_help) self.Epsu_help.onRelease = lambda: self.unshow_help(self.Epsu_help) self.help_buttons.append(self.Epsu_help) + self.sliders.append(self.Epsu_slider) + self.slider_texts.append(self.Epsu_textbox) slider_i = 8 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -187,11 +226,13 @@ def __init__(self): self.SWU_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.SWU_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.SWU_help.onClick = lambda: self.show_help('SWU', self.SWU_help) self.SWU_help.onRelease = lambda: self.unshow_help(self.SWU_help) self.help_buttons.append(self.SWU_help) + self.sliders.append(self.SWU_slider) + self.slider_texts.append(self.SWU_textbox) slider_i = 9 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) @@ -201,26 +242,36 @@ def __init__(self): self.SUW_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.SUW_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.SUW_help.onClick = lambda: self.show_help('SUW', self.SUW_help) self.SUW_help.onRelease = lambda: self.unshow_help(self.SUW_help) self.help_buttons.append(self.SUW_help) - + self.sliders.append(self.SUW_slider) + self.slider_texts.append(self.SUW_textbox) slider_i = 10 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) - self.SUM_res = self.get_total_resource() self.SUMR_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, self.slider_height, min=0, max=self.SUM_res + 200, step=100, initial=self.SUM_res) self.SUMR_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.SUMR_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, - text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, - borderThickness=1, ) + text='?', fontSize=self.help_height - 2, inactiveColour=colors.GREY, + borderThickness=1, ) self.SUMR_help.onClick = lambda: self.show_help('SUMR', self.SUMR_help) self.SUMR_help.onRelease = lambda: self.unshow_help(self.SUMR_help) self.help_buttons.append(self.SUMR_help) + self.sliders.append(self.SUMR_slider) + self.slider_texts.append(self.SUMR_textbox) + + def fix_SUM_res(self): + """Fixing total amount of possible resources so it will not change with changing the number of patches""" + self.SUM_res_fixed = not self.SUM_res_fixed + if self.SUM_res_fixed: + self.fix_SUM_res_button.inactiveColour = colors.GREEN + else: + self.fix_SUM_res_button.inactiveColour = colors.GREY def start_stop_record(self): """Start or stop the recording of the simulation into a vdieo""" @@ -234,8 +285,8 @@ def start_stop_record(self): self.help_message = "SAVING VIDEO..." self.draw_help_message() - def start_stop(self): + """Switch to start or stop the simulation""" self.is_paused = not self.is_paused if self.start_button.inactiveColour != colors.GREY: self.start_button.inactiveColour = colors.GREY @@ -243,6 +294,7 @@ def start_stop(self): self.start_button.inactiveColour = colors.GREEN def show_help(self, help_decide_str, pressed_button): + """Switch to show help message""" for hb in self.help_buttons: hb.inactiveColour = colors.GREY if not self.is_paused: @@ -252,6 +304,7 @@ def show_help(self, help_decide_str, pressed_button): pressed_button.inactiveColour = colors.GREEN def unshow_help(self, pressed_button): + """Switch to erease help message from screen""" for hb in self.help_buttons: hb.inactiveColour = colors.GREY self.is_help_shown = False @@ -260,6 +313,7 @@ def unshow_help(self, pressed_button): pressed_button.inactiveColour = colors.GREY def update_SUMR(self): + """Updating the total possible resource units if necessary""" self.SUM_res = self.get_total_resource() self.SUMR_slider.min = self.N_resc self.SUMR_slider.max = 2 * self.SUM_res @@ -287,26 +341,10 @@ def draw_frame(self, stats, stats_pos): if self.SUM_res == 0: self.update_SUMR() self.SUMR_textbox.setText(f"SUM R: {self.SUM_res:.2f}") - self.framerate_textbox.draw() - self.framerate_slider.draw() - self.N_textbox.draw() - self.N_slider.draw() - self.NRES_textbox.draw() - self.NRES_slider.draw() - self.FOV_textbox.draw() - self.FOV_slider.draw() - self.RESradius_textbox.draw() - self.RESradius_slider.draw() - self.Epsw_textbox.draw() - self.Epsw_slider.draw() - self.Epsu_textbox.draw() - self.Epsu_slider.draw() - self.SUMR_textbox.draw() - self.SUMR_slider.draw() - self.SUW_slider.draw() - self.SUW_textbox.draw() - self.SWU_textbox.draw() - self.SWU_slider.draw() + for sl in self.sliders: + sl.draw() + for slt in self.slider_texts: + slt.draw() for hb in self.help_buttons: hb.draw() for fb in self.function_buttons: @@ -315,9 +353,11 @@ def draw_frame(self, stats, stats_pos): self.draw_help_message() self.draw_global_stats() if self.save_video: - self.help_message = "\n\n\n SAVING VIDEO, PLEASE WAIT!" + # Showing the help message before the screen freezes + self.help_message = "\n\n\n Saving video, please wait..." self.draw_help_message() pygame.display.flip() + # Save video (freezes update process for a while) self.saved_images_to_video() self.save_video = False @@ -489,7 +529,10 @@ def act_on_NRES_mismatch(self): for i, res in enumerate(self.rescources): if i == len(self.rescources) - 1: res.kill() - self.update_SUMR() + if not self.SUM_res_fixed: + self.update_SUMR() + else: + self.distribute_sumR() def draw_visual_fields(self): """Visualizing the range of vision for agents as opaque circles around the agents""" @@ -548,4 +591,4 @@ def saved_images_to_video(self): cv2.destroyAllWindows() video.release() shutil.rmtree(self.image_save_path) - os.makedirs(self.image_save_path, exist_ok=True) \ No newline at end of file + os.makedirs(self.image_save_path, exist_ok=True) From a644de3af81f38469102ed69558a3cdd7b52657c Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 11:09:04 +0100 Subject: [PATCH 39/58] cleanup and show recording status --- abm/simulation/isims.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 10f55ff0..a77245ed 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -96,17 +96,18 @@ def __init__(self): function_button_start_x += self.function_button_width + self.function_button_pad self.record_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, self.function_button_width, - self.function_button_height, text='Record', + self.function_button_height, text='Record Video', fontSize=self.function_button_height - 2, inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.start_stop_record()) self.function_buttons.append(self.record_button) function_button_start_x += self.function_button_width + self.function_button_pad self.fix_SUM_res_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, - self.function_button_width, - self.function_button_height, text='Fix Total Units', - fontSize=self.function_button_height - 2, - inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.fix_SUM_res()) + self.function_button_width, + self.function_button_height, text='Fix Total Units', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREEN, borderThickness=1, + onClick=lambda: self.fix_SUM_res()) self.function_buttons.append(self.fix_SUM_res_button) self.global_stats_start += self.function_button_height + self.window_pad @@ -278,10 +279,16 @@ def start_stop_record(self): if not self.is_recording: self.is_recording = True self.record_button.inactiveColour = colors.RED + self.record_button.string = "Stop Recording" + self.record_button.text = self.record_button.font.render(self.record_button.string, True, + self.record_button.textColour) else: self.is_recording = False self.save_video = True self.record_button.inactiveColour = colors.GREY + self.record_button.string = "Record Video" + self.record_button.text = self.record_button.font.render(self.record_button.string, True, + self.record_button.textColour) self.help_message = "SAVING VIDEO..." self.draw_help_message() @@ -352,6 +359,8 @@ def draw_frame(self, stats, stats_pos): if self.is_help_shown: self.draw_help_message() self.draw_global_stats() + if self.is_recording: + self.draw_record_circle() if self.save_video: # Showing the help message before the screen freezes self.help_message = "\n\n\n Saving video, please wait..." @@ -361,6 +370,18 @@ def draw_frame(self, stats, stats_pos): self.saved_images_to_video() self.save_video = False + def draw_record_circle(self): + """Drawing a red circle to show that the frame is recording""" + if self.t % 60 < 30: + circle_rad = int(self.window_pad / 4) + image = pygame.Surface([2 * circle_rad, 2 * circle_rad]) + image.fill(colors.BACKGROUND) + image.set_colorkey(colors.BACKGROUND) + pygame.draw.circle( + image, colors.RED, (circle_rad, circle_rad), circle_rad + ) + self.screen.blit(image, (circle_rad, circle_rad)) + def draw_framerate(self): pass From 8f06428f684f6c7454bc57b9468bd2bdeb639693 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Tue, 22 Feb 2022 14:40:43 +0100 Subject: [PATCH 40/58] add show all stats button --- abm/agent/agent.py | 1 + abm/environment/rescource.py | 3 ++- abm/simulation/isims.py | 31 +++++++++++++++++++++++++++++++ abm/simulation/sims.py | 9 +++++++-- 4 files changed, 41 insertions(+), 3 deletions(-) diff --git a/abm/agent/agent.py b/abm/agent/agent.py index 82cf355b..29549553 100644 --- a/abm/agent/agent.py +++ b/abm/agent/agent.py @@ -60,6 +60,7 @@ def __init__(self, id, radius, position, orientation, env_size, color, v_field_r self.vision_range = vision_range self.visual_exclusion = visual_exclusion self.FOV = FOV + self.show_stats = False # Non-initialisable private attributes self.velocity = 0 # agent absolute velocity # saved diff --git a/abm/environment/rescource.py b/abm/environment/rescource.py index 99fc3f5a..5e1c213b 100644 --- a/abm/environment/rescource.py +++ b/abm/environment/rescource.py @@ -45,6 +45,7 @@ def __init__(self, id, radius, position, env_size, color, window_pad, resc_units self.resc_left_color = colors.DARK_GREY self.unit_per_timestep = quality # saved self.is_clicked = False + self.show_stats = False # Environment related parameters self.WIDTH = env_size[0] # env width @@ -99,7 +100,7 @@ def update(self): self.rect.centerx = self.center[0] self.rect.centery = self.center[1] self.mask = pygame.mask.from_surface(self.image) - if self.is_clicked: + if self.is_clicked or self.show_stats: font = pygame.font.Font(None, 18) text = font.render(f"{self.resc_left:.2f}, Q{self.unit_per_timestep:.2f}", True, colors.BLACK) self.image.blit(text, (0, 0)) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index a77245ed..2da82e32 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -43,6 +43,7 @@ def __init__(self): self.save_video = False # trigger to save video from screenshots self.video_save_path = os.path.join(root_abm_dir, pgt.VIDEO_SAVE_DIR) # path to save video self.image_save_path = os.path.join(self.video_save_path, "tmp") # path from collect screenshots + self.show_all_stats = False # enabling paths if os.path.isdir(self.image_save_path): shutil.rmtree(self.image_save_path) @@ -109,6 +110,14 @@ def __init__(self): inactiveColour=colors.GREEN, borderThickness=1, onClick=lambda: self.fix_SUM_res()) self.function_buttons.append(self.fix_SUM_res_button) + function_button_start_x += self.function_button_width + self.function_button_pad + self.show_all_stats_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.function_button_width, + self.function_button_height, text='Show All', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREY, borderThickness=1, + onClick=lambda: self.show_hide_all_stats()) + self.function_buttons.append(self.show_all_stats_button) self.global_stats_start += self.function_button_height + self.window_pad ## First Slider column @@ -266,6 +275,22 @@ def __init__(self): self.sliders.append(self.SUMR_slider) self.slider_texts.append(self.SUMR_textbox) + def show_hide_all_stats(self): + """Show or hide all information""" + self.show_all_stats = not self.show_all_stats + if self.show_all_stats: + self.show_all_stats_button.inactiveColour = colors.GREEN + for ag in self.agents: + ag.show_stats = True + for res in self.rescources: + res.show_stats = True + else: + self.show_all_stats_button.inactiveColour = colors.GREY + for ag in self.agents: + ag.show_stats = False + for res in self.rescources: + res.show_stats = False + def fix_SUM_res(self): """Fixing total amount of possible resources so it will not change with changing the number of patches""" self.SUM_res_fixed = not self.SUM_res_fixed @@ -529,6 +554,9 @@ def act_on_N_mismatch(self): for i, ag in enumerate(self.agents): if i == len(self.agents) - 1: ag.kill() + if self.show_all_stats: + for ag in self.agents: + ag.show_stats = True self.stats, self.stats_pos = self.create_vis_field_graph() def act_on_NRES_mismatch(self): @@ -554,6 +582,9 @@ def act_on_NRES_mismatch(self): self.update_SUMR() else: self.distribute_sumR() + if self.show_all_stats: + for res in self.rescources: + res.show_stats = True def draw_visual_fields(self): """Visualizing the range of vision for agents as opaque circles around the agents""" diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 6cac619d..7c3c2488 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -266,7 +266,7 @@ def draw_agent_stats(self, font_size=15, spacing=0): # if self.is_paused: font = pygame.font.Font(None, font_size) for agent in self.agents: - if agent.is_moved_with_cursor: + if agent.is_moved_with_cursor or agent.show_stats: status = [ f"ID: {agent.id}", f"res.: {agent.collected_r:.2f}", @@ -281,7 +281,11 @@ def draw_agent_stats(self, font_size=15, spacing=0): def kill_resource(self, resource): """Killing (and regenerating) a given resource patch""" if self.regenerate_resources: - self.add_new_resource_patch() + rid = self.add_new_resource_patch() + if resource.show_stats: + for res in self.rescources: + if res.id == rid: + res.show_stats = True resource.kill() def add_new_resource_patch(self): @@ -302,6 +306,7 @@ def add_new_resource_patch(self): quality) resource_proven = self.proove_sprite(resource) self.rescources.add(resource) + return resource.id def agent_agent_collision(self, agent1, agent2): """collision protocol called on any agent that has been collided with another one From da4b664c2cb46708fe98768b8ecbad67c1b3b0be Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:12:46 +0100 Subject: [PATCH 41/58] add software elements --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7747c790..20ca1a05 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,8 @@ The package includes the following submodules: * `loader`: including all classes and methods to dynamically load data that was generated with the package. These methods are for example cvs and json readers and initializers that initialize input classes for Replay and DataAnalysis tools. * `metarunner`: including all classes and methods to run multiple simulations one after the other with programatically changed initialization parameters. The main classes are `Tunables` that define a criterion range with requested number of datapoints (e.g.: simulate with environment width from 300 to 600 via 4 datapoints). The `Constant` class that defines a fixed criterion throughout the simulations (e.g.: keep the simulation time `T` fixed at 1000). And `MetaProtocol` class that defines a batch of simulations with all combinations of the defined criteria according to the added `Tunable`s and `Constant`s. During running metaprotocols the corresponding initializations (as `.env` files) will be saved under the `data/metaprotocol/temp` folder. Only those `.env` files will be removed from here for which the simulations have been carried out, therefore the metaprotocol can be interrupted and finished later. * `monitoring`: including all methods to interface with InfluxDB, Grafana and to save the stored data from the database at the end of the simulation. The data will be saved into the `data/simualtion_data/` of the root abm folder. The data will consist of 2 relevant csv files (agent_data, resource_data) containing time series of the agent and resource patch status and a json file containing all parameters of the simulation for reproducibility. -* `simulation`: including the main `Simulation` class that defines how the environment is visualized, what interactions the user can have with the pygame environment (e.g.: via cursor or buttons), and how the environment enforces some restrictions on agents, and how resources are regenerated. +* `simulation`: including the main `Simulation` class that defines how the environment is visualized, what interactions the user can have with the pygame environment (e.g.: via cursor or buttons), and how the environment enforces some restrictions on agents, and how resources are regenerated. Furthermore a `PlaygroundSimulation` class is dedicated to provide an interactive playground where the user can explore different parameter combinations with the help of sliders and buttons. This class inherits all of it's simulation functionality from the main `Simulation` class but might change the visualization and adds additional interactive optionalities. When the framework is started as a playground, the parameters in the `.env` file don't matter anymore, but a `.env` file is still needed in the main ABM folder so that the supercalss can be initiated. +* `replay`: to explore large batches of simulated experimental data, a replay class has been implemented. To initialize the class one needs to pass the absolute path of an experiment folder generated by the metaruneer tool. Upon initialization, in case the experiment is not yet summarized into numpy arrays this step is carried out. The arrays are then read back to the memory at once. The different batches and parameter combinations can be explored with interactive GUI elements. In case the amount of data is too large, one can use undersampling of data to only include every n-th timestep in the summary arrays. ### Functionality and Behavior Here you can read about how the framework works in large scale behavior and what restrictions and assumptions we used throughout the simulation. From 9816c0e486be89a752295fcfbb91edcd62f670bc Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:28:05 +0100 Subject: [PATCH 42/58] Added playground tool --- README.md | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 20ca1a05..68e3df06 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,13 @@ This repository hold the code base for the agent based model framework implement ### Requirements To run the simulations you will need python 3.8 or 3.9 and pip correspondingly. It is worth to set up a virtualenvironment using pipenv or venv for the project so that your global workspace is not polluted. -### Test -To test the code: +### Test Requirements +To test if all the requirements are ready to use: 1. Clone the repo 2. Activate your virtual environment (pipenv, venv) if you are using one 3. Move into the cloned repo where `setup.py` is located and run `pip install -e .` with that you installed the simulation package - 4. run the start entrypoint of the simulation package by running `abm-start` + 4. run the start entrypoint of the simulation package by running `playground-start` or `abm-start` + 5. If you also would like to save data you will need an InfluxDB instance. To setup one, please follow the instructions below. ## Install Grafana and InfluxDB To monitor individual agents real time and save simulation data (i.e. write simulation data real time and save upon request at the end) we use InfluxDB and a grafana server for visualization. For this purpose you will need to install influx and grafana. If you don't do these steps you are still going to be able to run simulations, but you won't be able to save the resulting data or visualize the agent's parameters. This installation guide is only tested on Ubuntu. If you decide to use another op.system or you don't want to monitor and save simulation data, set `USE_IFDB_LOGGING` and `SAVE_CSV_FILES` parameters in the `.env` file to `0`. @@ -231,3 +232,22 @@ EXPERIMENT_NAME=exp4 python home/ABM/abm/data/metarunner/experiments/exp4.py where we assume you have a `exp4.env` file in the root project folder (`home/ABM`) and you store an experiment file `exp4.py` under a dedicated path, in the example, this path is `home/ABM/abm/data/metarunner/experiments/`. Note that the env variable `EXPERIMENT_NAME` is used to show the given `MetaProtocol` instance which `.env` file it needs to use (and replace during runs). Therefore it must have a scope ONLY for the given command. If you set this varaible globally on your OS then all `MetaProtocol` instances will try to use and replace the same `.env` file and therefore during parallel runs unwanted behavior and corrupted data states can occur. The given commands are only to be used on Linux. + +### Interactive Exploration Tool +To allow users quick-and-dirty experimentation with the model framework, an interactive playground tool has been implemented. This can be started with `playground-start` after preparing the environment as described above. + +Once the playground tool has been started a window will pop up with a simulation arena on the upper right part with a given number of agnets and resources. Parameters are initialized according to the `contrib` package. These parameters can be tuned with interactive sliders on the right side of the window. To get some insights of these parameters see the env variable descriptions above or click and hold the `?` buttons next to the sliders. + +#### Resource number and radius +When changing the number of resource patches and their radii, the tool automatically adjusts these to each other so that the total covered area in the arena will not exceed 30% of the arena surface. This is necessary as resources are initialized in a way that no overlap is present. + +#### Fixed Overall Resource Units +When starting the tool the overall amount of resource units (summed over all patches of the arena) is fixed and can be controlled with the `SUM_R` slider. Changing this value will redistribute the amount of units between the patches in a way that the ratio of units in between tha patches will not change, and the depletion level of the patches also stays the same. In case this feature is turned off with the corresponding action button below the simulation arena, increasing the number of resource patches will increase the overall number of resources in the environment. + +#### Detailed Information +To get more detailed information about resource patches and agents, click and hold them with the left mouse button. Note that this alos moves the agents. Other interactions such as rotating agents, pausing the simulation, etc. are the same as in the original simulation class. In case you would like to get an insight about all agents and resources use the corresponding action button under the simulation area. Note that this can slow down the simulation significantly due to the amount of text to be rendered on the screen. + +#### Video Recording +To show the effect of parameter combinations and make experiments reproducable, you can also record a short video of particularly interesting phenomena. To do so, use the `Record Video` action button under the simulation arena. When the recording is started, the button turns red as well as a red "Rec" dot will pop up in the upper left corner. When you stop the recording with the same action button, the tool will save and compress the resulting video and save in the data folder of the package. Please note that this might take a few minutes for longer videos. + +### Replay Tool From 70d83c8514b6b09af2d534582e4da12f591a0eea Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Wed, 23 Feb 2022 09:47:06 +0100 Subject: [PATCH 43/58] add replay tool --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 68e3df06..9c18007c 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,13 @@ To get more detailed information about resource patches and agents, click and ho To show the effect of parameter combinations and make experiments reproducable, you can also record a short video of particularly interesting phenomena. To do so, use the `Record Video` action button under the simulation arena. When the recording is started, the button turns red as well as a red "Rec" dot will pop up in the upper left corner. When you stop the recording with the same action button, the tool will save and compress the resulting video and save in the data folder of the package. Please note that this might take a few minutes for longer videos. ### Replay Tool +To visualize large batches of data generated as experiment folders with the metarunner tool, one can use the replay tool. A demonstrative script has been provided in the repo to show how one can start such a replay of experiment. + +#### Behavior +Upon start if the experiment was not summarized before into numpy arrays this will be done. Then these arrays are read back to the memory to initialize the GUI of the tool. On the left side an arena shows the agnets and resources. Below, global statistics are shown in case it is requested with the `Show Stats` action button. The path of the agents as well as their visual field can be visualized with the corresponding buttons. Note that interactions from the simulation or the playground tool won't work here as this visualization will be a pure replay (as in a replayed video) of the recorded simulation itself. One can replay the recorded data in time with the `Start/Stop` button or by moving the time slider. + +#### Parameter Combinations +Possible parameter combinations are read automatically from the data and the corresponding sliders will be initialized in the action area accordingly. By that, one can go through the simulated parameter combinations and different batches by moving the sliders on the right. + +#### Plotting +To plot some global statistics of the data corresponding action buttons have been implemented on the right. Note that it only works with 1, 2 or 3 changed parameters. In case 3 parameters were tuned throughout the experiment one can either plot multiple 2 dimensional figures or "collapse" the plot along an axis using some method, such as minimum or maximum collision. This means that along that axis instead of taking all values into consideration one will onmly take the max or min of the values. This is especially useful when 2 parameters were tuned together in a way that their product should remain the same (That can be done adding so called Tuned Pairs to the criterion of the metarunner tool). In these cases only specified parameter combinations have informative values and not the whole parameter space provided with the parameter ranges. From 4c94ba065d598183826a8685b8597d58b461437e Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 11:03:40 +0100 Subject: [PATCH 44/58] Visual occlusion and ghost mode can be changed --- abm/simulation/isims.py | 51 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 2da82e32..e1efd931 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -86,16 +86,17 @@ def __init__(self): self.textbox_start_x = self.slider_start_x + self.slider_width + 15 self.help_start_x = self.textbox_start_x + self.textbox_width + 15 - ## Function Button Row + ## Function Button 1st Row function_button_start_x = self.window_pad - self.start_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + function_button_start_y = self.vis_area_end_height + self.start_button = Button(self.screen, function_button_start_x, function_button_start_y, self.function_button_width, self.function_button_height, text='Start/Stop', fontSize=self.function_button_height - 2, inactiveColour=colors.GREEN, borderThickness=1, onClick=lambda: self.start_stop()) self.function_buttons.append(self.start_button) function_button_start_x += self.function_button_width + self.function_button_pad - self.record_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.record_button = Button(self.screen, function_button_start_x, function_button_start_y, self.function_button_width, self.function_button_height, text='Record Video', fontSize=self.function_button_height - 2, @@ -103,7 +104,7 @@ def __init__(self): onClick=lambda: self.start_stop_record()) self.function_buttons.append(self.record_button) function_button_start_x += self.function_button_width + self.function_button_pad - self.fix_SUM_res_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.fix_SUM_res_button = Button(self.screen, function_button_start_x, function_button_start_y, self.function_button_width, self.function_button_height, text='Fix Total Units', fontSize=self.function_button_height - 2, @@ -111,14 +112,34 @@ def __init__(self): onClick=lambda: self.fix_SUM_res()) self.function_buttons.append(self.fix_SUM_res_button) function_button_start_x += self.function_button_width + self.function_button_pad - self.show_all_stats_button = Button(self.screen, function_button_start_x, self.vis_area_end_height, + self.show_all_stats_button = Button(self.screen, function_button_start_x, function_button_start_y, self.function_button_width, self.function_button_height, text='Show All', fontSize=self.function_button_height - 2, inactiveColour=colors.GREY, borderThickness=1, onClick=lambda: self.show_hide_all_stats()) self.function_buttons.append(self.show_all_stats_button) - self.global_stats_start += self.function_button_height + self.window_pad + + ## Function Button Second Row + function_button_start_x = self.window_pad + function_button_start_y = self.vis_area_end_height + self.function_button_height + self.function_button_pad + self.visual_exclusion_button = Button(self.screen, function_button_start_x, function_button_start_y, + self.function_button_width, + self.function_button_height, text='Visual Occl.', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREEN, borderThickness=1, + onClick=lambda: self.change_visual_occlusion()) + self.function_buttons.append(self.visual_exclusion_button) + function_button_start_x += self.function_button_width + self.function_button_pad + self.ghost_mode_button = Button(self.screen, function_button_start_x, function_button_start_y, + self.function_button_width, + self.function_button_height, text='Ghost Mode', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREEN, borderThickness=1, + onClick=lambda: self.change_ghost_mode()) + self.function_buttons.append(self.ghost_mode_button) + + self.global_stats_start += 2 * self.function_button_height + self.function_button_pad + self.window_pad ## First Slider column slider_i = 1 @@ -275,6 +296,24 @@ def __init__(self): self.sliders.append(self.SUMR_slider) self.slider_texts.append(self.SUMR_textbox) + def change_ghost_mode(self): + """Changing ghost mdoe during exploutation""" + self.ghost_mode = not self.ghost_mode + if self.ghost_mode: + self.ghost_mode_button.inactiveColour = colors.GREEN + else: + self.ghost_mode_button.inactiveColour = colors.GREY + + def change_visual_occlusion(self): + """Changing visual occlusion parameter""" + self.visual_exclusion = not self.visual_exclusion + for ag in self.agents: + ag.visual_exclusion = self.visual_exclusion + if self.visual_exclusion: + self.visual_exclusion_button.inactiveColour = colors.GREEN + else: + self.visual_exclusion_button.inactiveColour = colors.GREY + def show_hide_all_stats(self): """Show or hide all information""" self.show_all_stats = not self.show_all_stats From e48819fcd3601e081e8ea4d924e50ed341226c32 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 13:11:39 +0100 Subject: [PATCH 45/58] batch size can be explicitly set --- abm/monitoring/ifdb.py | 12 ++++++++---- abm/simulation/sims.py | 9 +++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/abm/monitoring/ifdb.py b/abm/monitoring/ifdb.py index 9a20ce86..4cc4a575 100644 --- a/abm/monitoring/ifdb.py +++ b/abm/monitoring/ifdb.py @@ -46,7 +46,7 @@ def pad_to_n_digits(number, n=3): return str(number) -def save_agent_data(ifclient, agents, exp_hash=""): +def save_agent_data(ifclient, agents, exp_hash="", batch_size=None): """Saving relevant agent data into InfluxDB intance if multiple simulations are running in parallel a uuid hash must be passed as experiment hash to find the unique measurement in the database @@ -82,7 +82,9 @@ def save_agent_data(ifclient, agents, exp_hash=""): # write the measurement in batches batch_bodies_agents.append(body) - if len(batch_bodies_agents) == ifdbp.write_batch_size: + if batch_size is None: + batch_size = ifdbp.write_batch_size + if len(batch_bodies_agents) == batch_size: ifclient.write_points(batch_bodies_agents) batch_bodies_agents = [] @@ -99,7 +101,7 @@ def mode_to_int(mode): return int(3) -def save_resource_data(ifclient, resources, exp_hash=""): +def save_resource_data(ifclient, resources, exp_hash="", batch_size=None): """Saving relevant resource patch data into InfluxDB instance if multiple simulations are running in parallel a uuid hash must be passed as experiment hash to find the unique measurement in the database""" @@ -129,7 +131,9 @@ def save_resource_data(ifclient, resources, exp_hash=""): batch_bodies_resources.append(body) # write the measurement in batches - if len(batch_bodies_resources) == ifdbp.write_batch_size: + if batch_size is None: + batch_size = ifdbp.write_batch_size + if len(batch_bodies_resources) == batch_size: ifclient.write_points(batch_bodies_resources) batch_bodies_resources = [] diff --git a/abm/simulation/sims.py b/abm/simulation/sims.py index 7c3c2488..6186cacb 100644 --- a/abm/simulation/sims.py +++ b/abm/simulation/sims.py @@ -171,6 +171,7 @@ def __init__(self, N, T, v_field_res=800, width=600, height=480, self.clock = pygame.time.Clock() # Monitoring + self.write_batch_size = None self.parallel = parallel if self.parallel: self.ifdb_hash = uuid.uuid4().hex @@ -184,6 +185,8 @@ def __init__(self, N, T, v_field_res=800, width=600, height=480, self.ifdb_client.drop_database(ifdb_params.INFLUX_DB_NAME) self.ifdb_client.create_database(ifdb_params.INFLUX_DB_NAME) ifdb.save_simulation_params(self.ifdb_client, self, exp_hash=self.ifdb_hash) + else: + self.ifdb_client = None # by default we parametrize with the .env file in root folder root_abm_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) @@ -674,8 +677,10 @@ def start(self): # Monitoring with IFDB if self.save_in_ifd: - ifdb.save_agent_data(self.ifdb_client, self.agents, exp_hash=self.ifdb_hash) - ifdb.save_resource_data(self.ifdb_client, self.rescources, exp_hash=self.ifdb_hash) + ifdb.save_agent_data(self.ifdb_client, self.agents, exp_hash=self.ifdb_hash, + batch_size=self.write_batch_size) + ifdb.save_resource_data(self.ifdb_client, self.rescources, exp_hash=self.ifdb_hash, + batch_size=self.write_batch_size) # Moving time forward self.clock.tick(self.framerate) From ad5ced4c4554d43200946cb8ec7469ed80daa089 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 13:11:54 +0100 Subject: [PATCH 46/58] labeled interactive params --- abm/contrib/playgroundtool.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index 8d20fa9f..a47ea500 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -3,12 +3,12 @@ VIDEO_SAVE_DIR = "abm/data/videos" default_params = { - "N": 5, - "T": 100000, + "N": 5, # interactive + "T": 100000, # interactive "v_field_res": 1200, "width": 500, "height": 500, - "framerate": 30, + "framerate": 30, # interactive "window_pad": 30, "with_visualization": True, "show_vis_field": False, @@ -16,22 +16,22 @@ "pooling_time": 0, "pooling_prob":0, "agent_radius": 10, - "N_resc": 3, + "N_resc": 3, # interactive "min_resc_perpatch": 200, "max_resc_perpatch": 201, "min_resc_quality": 0.25, "max_resc_quality": 0.25, - "patch_radius": 30, + "patch_radius": 30, # interactive "regenerate_patches": True, "agent_consumption": 1, "teleport_exploit": False, "vision_range": 5000, - "agent_fov": 0.5, - "visual_exclusion": True, + "agent_fov": 0.5, # interactive + "visual_exclusion": True, # interactive "show_vision_range": True, - "use_ifdb_logging": True, + "use_ifdb_logging": False, # interactive "save_csv_files": False, - "ghost_mode": True, + "ghost_mode": True, # interactive "patchwise_exclusion": True, "parallel": False } From 1038fd398ec2de054fdfb1be964a6d1520da3f66 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 13:12:23 +0100 Subject: [PATCH 47/58] IFDB logging interactive --- abm/simulation/isims.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index e1efd931..26e8c982 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -7,6 +7,7 @@ from abm.contrib import colors, ifdb_params from abm.contrib import playgroundtool as pgt +from abm.monitoring import ifdb from abm.simulation.sims import Simulation from pygame_widgets.slider import Slider from pygame_widgets.button import Button @@ -138,6 +139,14 @@ def __init__(self): inactiveColour=colors.GREEN, borderThickness=1, onClick=lambda: self.change_ghost_mode()) self.function_buttons.append(self.ghost_mode_button) + function_button_start_x += self.function_button_width + self.function_button_pad + self.IFDB_button = Button(self.screen, function_button_start_x, function_button_start_y, + self.function_button_width, + self.function_button_height, text='IFDB Log', + fontSize=self.function_button_height - 2, + inactiveColour=colors.GREY, borderThickness=1, + onClick=lambda: self.start_stop_IFDB_logging()) + self.function_buttons.append(self.IFDB_button) self.global_stats_start += 2 * self.function_button_height + self.function_button_pad + self.window_pad @@ -296,6 +305,20 @@ def __init__(self): self.sliders.append(self.SUMR_slider) self.slider_texts.append(self.SUMR_textbox) + def start_stop_IFDB_logging(self): + """Start or stop IFDB logging in case of grafana interface is used""" + print(self.save_in_ifd) + self.save_in_ifd = not self.save_in_ifd + if self.save_in_ifd: + if self.ifdb_client is None: + self.ifdb_client = ifdb.create_ifclient() + self.ifdb_client.create_database(ifdb_params.INFLUX_DB_NAME) + self.write_batch_size = 2 + self.IFDB_button.inactiveColour = colors.GREEN + else: + self.write_batch_size = None + self.IFDB_button.inactiveColour = colors.GREY + def change_ghost_mode(self): """Changing ghost mdoe during exploutation""" self.ghost_mode = not self.ghost_mode From 860a760c9d4c6445c8b441bc72d2734aabda06b8 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 13:12:46 +0100 Subject: [PATCH 48/58] cleanup --- abm/simulation/isims.py | 1 - 1 file changed, 1 deletion(-) diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index 26e8c982..b4033c70 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -307,7 +307,6 @@ def __init__(self): def start_stop_IFDB_logging(self): """Start or stop IFDB logging in case of grafana interface is used""" - print(self.save_in_ifd) self.save_in_ifd = not self.save_in_ifd if self.save_in_ifd: if self.ifdb_client is None: From ac11c463aece2d867ed50695e47bd419eeba0832 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 14:28:47 +0100 Subject: [PATCH 49/58] resource patch can be moved with cursor --- abm/environment/rescource.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/abm/environment/rescource.py b/abm/environment/rescource.py index 5e1c213b..8328a414 100644 --- a/abm/environment/rescource.py +++ b/abm/environment/rescource.py @@ -79,6 +79,9 @@ def update_clicked_status(self, mouse): """Checking if the resource patch was clicked on a mouse event""" if self.rect.collidepoint(mouse): self.is_clicked = True + self.position[0] = mouse[0] - self.radius + self.position[1] = mouse[1] - self.radius + self.center = (self.position[0] + self.radius, self.position[1] + self.radius) self.update() else: self.is_clicked = False From 9477416a7e0552052eb2e4cea472514dd9ad7a09 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Thu, 24 Feb 2022 15:23:00 +0100 Subject: [PATCH 50/58] more possible agents and fix help message --- abm/contrib/playgroundtool.py | 16 ++++++++++------ abm/simulation/isims.py | 2 +- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/abm/contrib/playgroundtool.py b/abm/contrib/playgroundtool.py index a47ea500..742d5293 100644 --- a/abm/contrib/playgroundtool.py +++ b/abm/contrib/playgroundtool.py @@ -119,9 +119,9 @@ W to U Cross-inhibition strength, S_wu [a.U.]: - The parameter controls how much socially guided behvaior and + The parameter controls how much socially guided behavior and integration of social cues inhibit individual exploitation - behavior. Note, that thsi inhibition is only present if the + behavior. Note, that this inhibition is only present if the social integrator w is above it's decision threshold. ''', @@ -129,18 +129,22 @@ U to W Cross-inhibition strength, S_uw [a.U.]: - The parameter controls how much individual exploitation behvaior and + The parameter controls how much individual exploitation behavior and integration of individual information inhibits social - behavior. Note, that thsi inhibition is only present if the + behavior. Note, that this inhibition is only present if the individual integrator u is above it's decision threshold. ''', 'SUMR': ''' - Total number of resource units, SUM_r [pcs]: + Total number of resource units, SUM_r [a.U.]: The parameter controls how many resource units should be overall - distributed in the environment. + distributed in the environment. This number can be fixed with the + "Fix Total Units" action button. In this case changing the number + of patches will redistribute the units between the patches so + that although the ratio between patches stays the same, the + number of units per patch will change. ''' } \ No newline at end of file diff --git a/abm/simulation/isims.py b/abm/simulation/isims.py index b4033c70..6852ebdf 100644 --- a/abm/simulation/isims.py +++ b/abm/simulation/isims.py @@ -169,7 +169,7 @@ def __init__(self): slider_i = 2 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.N_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=1, max=25, step=1, initial=self.N) + self.slider_height, min=1, max=35, step=1, initial=self.N) self.N_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.textbox_height, fontSize=self.textbox_height - 2, borderThickness=1) self.N_help = Button(self.screen, self.help_start_x, slider_start_y, self.help_width, self.help_height, From b93676865412a8333672fcd631bf0b229b612db7 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 25 Feb 2022 10:14:29 +0100 Subject: [PATCH 51/58] possibility to start sim headless --- abm/app.py | 81 +++++++++++++++++++++++++++++++----------------------- setup.py | 1 + 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/abm/app.py b/abm/app.py index cc75f629..f7ea9933 100644 --- a/abm/app.py +++ b/abm/app.py @@ -1,47 +1,58 @@ +from contextlib import ExitStack + from abm.simulation.sims import Simulation from abm.simulation.isims import PlaygroundSimulation +from xvfbwrapper import Xvfb + import os # loading env variables from dotenv file from dotenv import dotenv_values + EXP_NAME = os.getenv("EXPERIMENT_NAME", "") -def start(parallel=False): - root_abm_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - envconf = dotenv_values(os.path.join(root_abm_dir, f"{EXP_NAME}.env")) - sim = Simulation(N=int(float(envconf["N"])), - T=int(float(envconf["T"])), - v_field_res=int(envconf["VISUAL_FIELD_RESOLUTION"]), - agent_fov=float(envconf['AGENT_FOV']), - framerate=int(float(envconf["INIT_FRAMERATE"])), - with_visualization=bool(int(float(envconf["WITH_VISUALIZATION"]))), - width=int(float(envconf["ENV_WIDTH"])), - height=int(float(envconf["ENV_HEIGHT"])), - show_vis_field=bool(int(float(envconf["SHOW_VISUAL_FIELDS"]))), - show_vis_field_return=bool(int(envconf['SHOW_VISUAL_FIELDS_RETURN'])), - pooling_time=int(float(envconf["POOLING_TIME"])), - pooling_prob=float(envconf["POOLING_PROBABILITY"]), - agent_radius=int(float(envconf["RADIUS_AGENT"])), - N_resc=int(float(envconf["N_RESOURCES"])), - min_resc_perpatch=int(float(envconf["MIN_RESOURCE_PER_PATCH"])), - max_resc_perpatch=int(float(envconf["MAX_RESOURCE_PER_PATCH"])), - min_resc_quality=float(envconf["MIN_RESOURCE_QUALITY"]), - max_resc_quality=float(envconf["MAX_RESOURCE_QUALITY"]), - patch_radius=int(float(envconf["RADIUS_RESOURCE"])), - regenerate_patches=bool(int(float(envconf["REGENERATE_PATCHES"]))), - agent_consumption=int(float(envconf["AGENT_CONSUMPTION"])), - ghost_mode=bool(int(float(envconf["GHOST_WHILE_EXPLOIT"]))), - patchwise_exclusion=bool(int(float(envconf["PATCHWISE_SOCIAL_EXCLUSION"]))), - teleport_exploit=bool(int(float(envconf["TELEPORT_TO_MIDDLE"]))), - vision_range=int(float(envconf["VISION_RANGE"])), - visual_exclusion=bool(int(float(envconf["VISUAL_EXCLUSION"]))), - show_vision_range=bool(int(float(envconf["SHOW_VISION_RANGE"]))), - use_ifdb_logging=bool(int(float(envconf["USE_IFDB_LOGGING"]))), - save_csv_files=bool(int(float(envconf["SAVE_CSV_FILES"]))), - parallel=parallel - ) - sim.start() +def start(parallel=False, headless=False): + with ExitStack() if not headless else Xvfb(width=1280, height=740) as xvfb: + root_abm_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + envconf = dotenv_values(os.path.join(root_abm_dir, f"{EXP_NAME}.env")) + sim = Simulation(N=int(float(envconf["N"])), + T=int(float(envconf["T"])), + v_field_res=int(envconf["VISUAL_FIELD_RESOLUTION"]), + agent_fov=float(envconf['AGENT_FOV']), + framerate=int(float(envconf["INIT_FRAMERATE"])), + with_visualization=bool(int(float(envconf["WITH_VISUALIZATION"]))), + width=int(float(envconf["ENV_WIDTH"])), + height=int(float(envconf["ENV_HEIGHT"])), + show_vis_field=bool(int(float(envconf["SHOW_VISUAL_FIELDS"]))), + show_vis_field_return=bool(int(envconf['SHOW_VISUAL_FIELDS_RETURN'])), + pooling_time=int(float(envconf["POOLING_TIME"])), + pooling_prob=float(envconf["POOLING_PROBABILITY"]), + agent_radius=int(float(envconf["RADIUS_AGENT"])), + N_resc=int(float(envconf["N_RESOURCES"])), + min_resc_perpatch=int(float(envconf["MIN_RESOURCE_PER_PATCH"])), + max_resc_perpatch=int(float(envconf["MAX_RESOURCE_PER_PATCH"])), + min_resc_quality=float(envconf["MIN_RESOURCE_QUALITY"]), + max_resc_quality=float(envconf["MAX_RESOURCE_QUALITY"]), + patch_radius=int(float(envconf["RADIUS_RESOURCE"])), + regenerate_patches=bool(int(float(envconf["REGENERATE_PATCHES"]))), + agent_consumption=int(float(envconf["AGENT_CONSUMPTION"])), + ghost_mode=bool(int(float(envconf["GHOST_WHILE_EXPLOIT"]))), + patchwise_exclusion=bool(int(float(envconf["PATCHWISE_SOCIAL_EXCLUSION"]))), + teleport_exploit=bool(int(float(envconf["TELEPORT_TO_MIDDLE"]))), + vision_range=int(float(envconf["VISION_RANGE"])), + visual_exclusion=bool(int(float(envconf["VISUAL_EXCLUSION"]))), + show_vision_range=bool(int(float(envconf["SHOW_VISION_RANGE"]))), + use_ifdb_logging=bool(int(float(envconf["USE_IFDB_LOGGING"]))), + save_csv_files=bool(int(float(envconf["SAVE_CSV_FILES"]))), + parallel=parallel + ) + sim.write_batch_size = 100 + sim.start() + + +def start_headless(): + start(headless=True) def start_playground(): diff --git a/setup.py b/setup.py index 9b8f7905..a1811b74 100644 --- a/setup.py +++ b/setup.py @@ -34,6 +34,7 @@ entry_points={ 'console_scripts': [ 'abm-start=abm.app:start', + 'headless-abm-start=abm.app:start_headless', 'playground-start=abm.app:start_playground' ] }, From 03e820f827b31a5e288a8583e257331cd0c90f48 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 25 Feb 2022 10:15:06 +0100 Subject: [PATCH 52/58] fix slider bug for now --- abm/replay/replay.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/abm/replay/replay.py b/abm/replay/replay.py index 44e2ba2b..0e42688a 100644 --- a/abm/replay/replay.py +++ b/abm/replay/replay.py @@ -86,9 +86,12 @@ def __init__(self, data_folder_path, undersample=1, collapse=None): self.time_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) slider_i = 3 + slider_max_val = self.num_batches - 1 + if slider_max_val <= 0: + slider_max_val = 1 slider_start_y = slider_i * (self.slider_height + self.action_area_pad) self.batch_slider = Slider(self.screen, self.slider_start_x, slider_start_y, self.slider_width, - self.slider_height, min=0, max=self.num_batches - 1, step=1, initial=0) + self.slider_height, min=0, max=slider_max_val, step=1, initial=0) self.batch_textbox = TextBox(self.screen, self.textbox_start_x, slider_start_y, self.textbox_width, self.slider_height, fontSize=self.slider_height - 2, borderThickness=1) From 3f19a4634bdb884b9ea7611c663e3b2a132aab68 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 25 Feb 2022 10:19:47 +0100 Subject: [PATCH 53/58] add requirement, bump version --- setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index a1811b74..98142896 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name='P34 ABM', description='Agent based model framework to simulate collectively foraging agents relying on their private and social' 'visual cues. Written in pygame and python 3.7+', - version='1.1.1', + version='1.1.2', url='https://github.com/scioip34/ABM', maintainer='David Mezey and Dominik Deffner @ SCIoI', packages=find_packages(exclude=['tests']), @@ -21,7 +21,8 @@ 'python-dotenv', 'pandas', 'influxdb', - 'opencv-python' + 'opencv-python', + 'xvfbwrapper' ], extras_require={ 'test': [ From a4fd905d1793dd8f73c3abcac622a1082b685d5b Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:22:20 +0100 Subject: [PATCH 54/58] Add headless mode requirement instructions --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9c18007c..827b07ba 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ To test if all the requirements are ready to use: 3. Move into the cloned repo where `setup.py` is located and run `pip install -e .` with that you installed the simulation package 4. run the start entrypoint of the simulation package by running `playground-start` or `abm-start` 5. If you also would like to save data you will need an InfluxDB instance. To setup one, please follow the instructions below. + 6. If you would like to run simulations in headless mode (without graphics) you will need to install xvfb first (only tested on Ubuntu) with `sudo apt-get install xvfb`. ## Install Grafana and InfluxDB To monitor individual agents real time and save simulation data (i.e. write simulation data real time and save upon request at the end) we use InfluxDB and a grafana server for visualization. For this purpose you will need to install influx and grafana. If you don't do these steps you are still going to be able to run simulations, but you won't be able to save the resulting data or visualize the agent's parameters. This installation guide is only tested on Ubuntu. If you decide to use another op.system or you don't want to monitor and save simulation data, set `USE_IFDB_LOGGING` and `SAVE_CSV_FILES` parameters in the `.env` file to `0`. From 78e9da8473eebf9224765d8b478330bb36a6f179 Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:23:35 +0100 Subject: [PATCH 55/58] headless instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 827b07ba..8cade8e7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ To test if all the requirements are ready to use: 3. Move into the cloned repo where `setup.py` is located and run `pip install -e .` with that you installed the simulation package 4. run the start entrypoint of the simulation package by running `playground-start` or `abm-start` 5. If you also would like to save data you will need an InfluxDB instance. To setup one, please follow the instructions below. - 6. If you would like to run simulations in headless mode (without graphics) you will need to install xvfb first (only tested on Ubuntu) with `sudo apt-get install xvfb`. + 6. If you would like to run simulations in headless mode (without graphics) you will need to install xvfb first (only tested on Ubuntu) with `sudo apt-get install xvfb`. After this, you can start the simulation in headless mode by calling the `headless-abm-start` entrypoint instead of the normal `abm-start` entrypoint. ## Install Grafana and InfluxDB To monitor individual agents real time and save simulation data (i.e. write simulation data real time and save upon request at the end) we use InfluxDB and a grafana server for visualization. For this purpose you will need to install influx and grafana. If you don't do these steps you are still going to be able to run simulations, but you won't be able to save the resulting data or visualize the agent's parameters. This installation guide is only tested on Ubuntu. If you decide to use another op.system or you don't want to monitor and save simulation data, set `USE_IFDB_LOGGING` and `SAVE_CSV_FILES` parameters in the `.env` file to `0`. From dfb04aaccf8529744245ca99e33a16f5afcf058e Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 25 Feb 2022 10:29:29 +0100 Subject: [PATCH 56/58] dynamic virtual screen size --- abm/app.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/abm/app.py b/abm/app.py index f7ea9933..1d03d619 100644 --- a/abm/app.py +++ b/abm/app.py @@ -13,9 +13,11 @@ def start(parallel=False, headless=False): - with ExitStack() if not headless else Xvfb(width=1280, height=740) as xvfb: - root_abm_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) - envconf = dotenv_values(os.path.join(root_abm_dir, f"{EXP_NAME}.env")) + root_abm_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + envconf = dotenv_values(os.path.join(root_abm_dir, f"{EXP_NAME}.env")) + vscreen_width = int(float(envconf["ENV_WIDTH"])) + 100 + vscreen_height = int(float(envconf["ENV_HEIGHT"])) + 100 + with ExitStack() if not headless else Xvfb(width=vscreen_width, height=vscreen_height) as xvfb: sim = Simulation(N=int(float(envconf["N"])), T=int(float(envconf["T"])), v_field_res=int(envconf["VISUAL_FIELD_RESOLUTION"]), From e56509726ecee9a63e22dd8fd0a98ca85c63dcb4 Mon Sep 17 00:00:00 2001 From: mezdahun Date: Fri, 25 Feb 2022 10:32:39 +0100 Subject: [PATCH 57/58] bump version before merge to develop --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 98142896..a067a44b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ name='P34 ABM', description='Agent based model framework to simulate collectively foraging agents relying on their private and social' 'visual cues. Written in pygame and python 3.7+', - version='1.1.2', + version='1.2.0', url='https://github.com/scioip34/ABM', maintainer='David Mezey and Dominik Deffner @ SCIoI', packages=find_packages(exclude=['tests']), From e424be11097e0e95c590fe47780049c987b16622 Mon Sep 17 00:00:00 2001 From: David Mezey <38374698+mezdahun@users.noreply.github.com> Date: Fri, 25 Feb 2022 10:37:02 +0100 Subject: [PATCH 58/58] finish playground readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 8cade8e7..a2e9a127 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,12 @@ To get more detailed information about resource patches and agents, click and ho #### Video Recording To show the effect of parameter combinations and make experiments reproducable, you can also record a short video of particularly interesting phenomena. To do so, use the `Record Video` action button under the simulation arena. When the recording is started, the button turns red as well as a red "Rec" dot will pop up in the upper left corner. When you stop the recording with the same action button, the tool will save and compress the resulting video and save in the data folder of the package. Please note that this might take a few minutes for longer videos. +#### Other Function Buttons +Some boolean parameters can be turned on and off with the help of additional function buttons (below the visualization area). These are + * Turn on Ghost Mode: overalpping on the patches are allowed + * Turn on IFDB logging: in case a visualization through the grafana interface is required one can start IFDB logging with this button. By default it is turned off so that we can avoid a database writing overhead and the tool can be aslo started without IFDB installed on the system. + * Turn on Visual Occlusion: in case it is turned on, agents can occlude visual cues from farther away agants. + ### Replay Tool To visualize large batches of data generated as experiment folders with the metarunner tool, one can use the replay tool. A demonstrative script has been provided in the repo to show how one can start such a replay of experiment.