From 2f6e1536c5ab6aef10f28c649e397efb34686604 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Thu, 19 Dec 2024 12:23:48 +0100 Subject: [PATCH 01/26] =?UTF-8?q?Bisschen=20Cleanup=20im=20Code=20f=C3=BCr?= =?UTF-8?q?=20bessere=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Cell.py | 60 +++++++++++++--------------------- Application/Grid.py | 8 ++--- Application/test_components.py | 1 + 3 files changed, 28 insertions(+), 41 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 6f6ebf6..99a0d88 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -60,6 +60,14 @@ def get_neighbors(self, grid, radius=2): neighbors[r] = layer_neighbors # Store the current layer of neighbors return neighbors + + def valid_neighbors(self, neighbors): + valid_neighbors = [ + cell for layer in neighbors.values() + for cell in layer + if cell.is_passable() + ] + return valid_neighbors @log_decorator def euclidean_distance_to(self, other): #Euklidische Distanz zwischen Zwei Zellen @@ -118,13 +126,7 @@ def __init__(self, row, col, cell_size): def spawn_agents(self, grid, max_agents): - neighbors = self.get_neighbors(grid, radius=1) - valid_neighbors = [ - cell for layer in neighbors.values() - for cell in layer - if not grid.is_cell_occupied(cell.row, cell.col) and cell.is_passable() - ] - + valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) random.shuffle(valid_neighbors) chance = random.randrange(0,1) agents_to_spawn = min(max_agents, len(valid_neighbors)) @@ -161,23 +163,7 @@ def __init__(self, row, col, cell_size): self.id = self.__hash__() self.route = [] self.movement_range = self.velocity - #Momentan nicht in Verwendung da wir Ziele als Liste führen und nicht immer als Grid Search finden müssen - # def find_nearest_target(self, grid): - # """Find the nearest TargetCell on the grid to this agent's current position.""" - # min_distance = float('inf') - # nearest_target = None - # - # for target_row in range(grid.rows): - # for target_col in range(grid.cols): - # cell = grid.grid[target_row][target_col] - # if isinstance(cell, TargetCell): - # # Calculate Euclidean distance using self.row and self.col - # distance = math.sqrt((target_row - self.row) ** 2 + (target_col - self.col) ** 2) - # if distance < min_distance: - # min_distance = distance - # nearest_target = (target_row, target_col) - # - # return nearest_target # Returns (target_row, target_col) or None if no TargetCell is found + def log_state(self, timestep, log_file="logs/agent_states.log"): """Log the agent's state to a file.""" @@ -298,12 +284,12 @@ def movement_towards_target(self, grid): distance_map = grid.dijkstra_distance_maps.get(target_key) # Find the best move - neighbors = self.get_neighbors(grid, radius=1) - valid_neighbors = [ - cell for layer in neighbors.values() - for cell in layer - if not isinstance(cell, ObstacleCell) and not grid.is_cell_occupied(cell.row, cell.col) and not isinstance(cell, TargetCell) and cell.is_passable() - ] + valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) + # valid_neighbors = [ + # cell for layer in neighbors.values() + # for cell in layer + # if not isinstance(cell, ObstacleCell) and not grid.is_cell_occupied(cell.row, cell.col) and not isinstance(cell, TargetCell) and cell.is_passable() + # ] # Include the agent's current position as an option valid_neighbors.append(self) @@ -325,7 +311,7 @@ def movement_towards_target(self, grid): self.row, self.col = best_move.row, best_move.col # Mark as arrived if adjacent to the target - if smallest_distance == grid.cell_size: + if smallest_distance == 0: self.arrived = True grid.agents.remove(self) grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) @@ -333,12 +319,12 @@ def movement_towards_target(self, grid): elif grid.movement_method=="floodfill": distance_map = grid.flood_fill_distance_maps.get(target_key) #print(distance_map) - neighbors = self.get_neighbors(grid, radius=1) - valid_neighbors = [ - cell for layer in neighbors.values() - for cell in layer - if not isinstance(cell, ObstacleCell) and not isinstance(cell, TargetCell) and not grid.is_cell_occupied(cell.row, cell.col) - ] + valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) + # valid_neighbors = [ + # cell for layer in neighbors.values() + # for cell in layer + # if grid.grid[cell.row][cell.col].is_passable() + # ] # Include the agent's current position as an option valid_neighbors.append(self) diff --git a/Application/Grid.py b/Application/Grid.py index 81b8538..13d54c7 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -69,10 +69,9 @@ def select_area_by_coordinates(self, start_x, start_y, end_x, end_y): List[Cell]: A list of Cell objects within the selected area. """ # Convert coordinates to grid indices using clamp - start_row = clamp(int(start_y / self.cell_size), 0, self.rows - 1) - start_col = clamp(int(start_x / self.cell_size), 0, self.cols - 1) - end_row = clamp(int(end_y / self.cell_size), 0, self.rows - 1) - end_col = clamp(int(end_x / self.cell_size), 0, self.cols - 1) + start_row, start_col = self.meter_to_rowcol(start_x, start_y) + end_row, end_col = self.meter_to_rowcol(end_x, end_y) + # Ensure start indices are less than or equal to end indices if start_row > end_row or start_col > end_col: @@ -261,6 +260,7 @@ def update(self, target_list, timestep): for agent in self.agents: if agent.arrived == True: self.agents.remove(agent) + #print(agent) if self.movement_method == "floodfill": agent.movement_towards_target(self) diff --git a/Application/test_components.py b/Application/test_components.py index 0d2a013..b7380b4 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -57,6 +57,7 @@ def test_agent_removal_on_arrival(self): # Update bewirkt das er sich Ziel Nähert for timestep in range(10): self.grid.update(self.grid.target_cells, timestep=timestep) + #print(self.grid.agents) # Verifiziere Löschung self.assertNotIn(agent, self.grid.agents, "Agent was not removed after arrival.") From 9b28d5849866df3749c169a42b23264348d633cb Mon Sep 17 00:00:00 2001 From: elFleppo Date: Thu, 19 Dec 2024 15:06:31 +0100 Subject: [PATCH 02/26] Massenbewegung angefangen --- Application/Cell.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Application/Cell.py b/Application/Cell.py index 99a0d88..0706600 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -254,6 +254,16 @@ def social_force(self, grid): print(f"Social force penalty for {self.__hash__()} is {penalty}") return penalty + def repulsive_force(width, height): + repulsive_force = -height + math.exp(1 / (2/width)**2 -1) + return repulsive_force + def social_penalty(self, grid): + neighbors = self.get_neighbors(grid, radius=2) + for distance, cells in neighbors.items(): + for cell in cells: + if isinstance(cell, Agent): + + @log_decorator def increase_movement_range(self): From 5cc69f5388a2a176a7e00816187843359bef5549 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Thu, 19 Dec 2024 22:58:06 +0100 Subject: [PATCH 03/26] =?UTF-8?q?Erste=20Version=20von=20Abstossender=20Wi?= =?UTF-8?q?rkung=20anderer=20Agenten=20entwickelt,=20unit=20test=20der=20f?= =?UTF-8?q?unktionalit=C3=A4t=20pr=C3=BCft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Cell.py | 21 +++++++++++++++++---- Application/Grid.py | 24 +++++++++++++++++++++--- Application/test_components.py | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 0706600..03400ca 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -73,6 +73,12 @@ def euclidean_distance_to(self, other): #Euklidische Distanz zwischen Zwei Zellen return math.sqrt((self.row - other.row) ** 2 + (self.col - other.col) ** 2) *self.cell_size + def manhattan_difference_to(self, other_cell): + """Calculate the height and length difference between this cell and another cell.""" + height_diff = abs(self.row - other_cell.row) + length_diff = abs(self.col - other_cell.col) + return height_diff, length_diff + #Jedes Feld hat einen Potentialwert zu der naheliegendsten Target Zelle @@ -218,7 +224,7 @@ def bresenham_line(self, x1, y1, x2, y2): return cells - @log_decorator + # @log_decorator def social_force(self, grid): print("entering social force") neighbors = self.get_neighbors(grid, radius=2) # Get neighbors within the radius @@ -253,19 +259,26 @@ def social_force(self, grid): penalty += penalty_contribution print(f"Social force penalty for {self.__hash__()} is {penalty}") return penalty + @log_decorator - def repulsive_force(width, height): + def repulsive_force(self, width, height): repulsive_force = -height + math.exp(1 / (2/width)**2 -1) return repulsive_force + @log_decorator def social_penalty(self, grid): + total_penalty = 0 neighbors = self.get_neighbors(grid, radius=2) for distance, cells in neighbors.items(): for cell in cells: if isinstance(cell, Agent): + height, width = self.manhattan_difference_to(cell) + print(height, width) + penalty_term = self.repulsive_force(width, height) + total_penalty = total_penalty+penalty_term + return total_penalty - - @log_decorator + # @log_decorator def increase_movement_range(self): #Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> self.movement_range = self.velocity + self.movement_range diff --git a/Application/Grid.py b/Application/Grid.py index 13d54c7..8b1e4cb 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -305,14 +305,32 @@ def plot_distance_map(self, distance_map, title="Distance Map"): distance_map (List[List[float]]): The 2D distance map to plot. title (str): Title for the heatmap. """ + import numpy as np + import matplotlib.pyplot as plt + import seaborn as sns + # Convert distance map to a numpy array for easier handling distance_array = np.array(distance_map) + # Determine color scale limits for better visualization + finite_values = distance_array[np.isfinite(distance_array)] # Exclude inf values + vmin = finite_values.min() if len(finite_values) > 0 else 0 + vmax = finite_values.max() if len(finite_values) > 0 else 1 + # Create a heatmap using seaborn - plt.figure(figsize=(8, 6)) - sns.heatmap(distance_array, annot=False, cmap="YlGnBu", cbar=True, square=True, linewidths=0.5) + plt.figure(figsize=(10, 8)) + sns.heatmap( + distance_array, + annot=False, # No annotations inside the cells + cmap="coolwarm", # Color map for gradient + cbar=True, # Display color bar + square=True, # Ensure square cells + linewidths=0, # Remove lines between cells + vmin=vmin, # Set minimum value for color scale + vmax=vmax # Set maximum value for color scale + ) - # Add a title and labels + # Add title and labels plt.title(title) plt.xlabel("Columns") plt.ylabel("Rows") diff --git a/Application/test_components.py b/Application/test_components.py index b7380b4..f4b7f74 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -254,5 +254,31 @@ def test_flood_fill_distance_map(self): grid.plot_distance_map(distance_map) +class TestAgentSocialPenalty(unittest.TestCase): + def setUp(self): + self.grid = Grid( + length=5, + height=5, + cell_size=1, + spawn_cells=[], + target_cells=[], + obstacle_cells=[] + ) + self.agent = Agent(2, 2, cell_size=1) + self.grid.grid[2][2] = self.agent + + def test_social_penalty(self): + # Place other agents around the main agent + self.grid.grid[1][1] = Agent(1, 1, cell_size=1) + self.grid.grid[1][3] = Agent(1, 3, cell_size=1) + self.grid.grid[3][1] = Agent(3, 1, cell_size=1) + self.grid.grid[3][3] = Agent(3, 3, cell_size=1) + + # Calculate the social penalty + penalty = self.agent.social_penalty(self.grid) + + # Verify the penalty is calculated correctly + self.assertLess(penalty, 0, "Penalty should be smaller than 0 when there are neighboring agents.") + if __name__ == '__main__': unittest.main() From 9dcff071042abe607dd930cff5cb71a8b3b15af5 Mon Sep 17 00:00:00 2001 From: Silvano <118846332+Silivanili@users.noreply.github.com> Date: Tue, 24 Dec 2024 10:01:28 +0100 Subject: [PATCH 04/26] =?UTF-8?q?mittlere=20Geschwindigkeit=20+=20Fundamen?= =?UTF-8?q?taldiagram.=20Break=20Condition=20f=C3=BCr=20Simulation.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/run.py | 60 +++++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/Application/run.py b/Application/run.py index f704c0e..097beb6 100644 --- a/Application/run.py +++ b/Application/run.py @@ -4,55 +4,83 @@ import numpy as np from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4 -import tests -grid = RiMEA9(1, "floodfill") + +grid = RiMEA9() visualization = Visualization(grid) agent_count_list = [] -average_distance = [] +average_distance_list = [] +average_speed_list = [] density_list = [] -timesteps = 10 +timesteps = 10000 for i in range(timesteps): grid.update(target_list=grid.target_cells, timestep=i) - #visualization.plot_grid_state(i) - grid.plot_grid_state(i) - plt.pause(0.2) + visualization.plot_grid_state(i) + #grid.plot_grid_state(i) + plt.pause(0.01) agent_count = len(grid.agents) agent_count_list.append(agent_count) + # berechne dichte + total_area = grid.length * grid.height + density = agent_count / total_area + density_list.append(density) + + if agent_count == 0: + print("All agents have reached their targets. Stopping simulation.") + break + + # berechne mittlere Geschwindigkeit + total_speed = sum(agent.velocity for agent in grid.agents) + if agent_count > 0: + average_speed = total_speed / agent_count + else: + average_speed = 0 # Avoid division by zero + average_speed_list.append(average_speed) + # mittlere distanz von Agenten zu Ziel total_distance_to_target = 0 if len(grid.agents) > 0: - total_istance_to_target = 0 for agent in grid.agents: target = agent.find_target(grid.target_cells) total_distance_to_target += agent.euclidean_distance_to(grid.grid[target[0]][target[1]]) - average_distance.append(total_distance_to_target / len(grid.agents)) + average_distance_list.append(total_distance_to_target / len(grid.agents)) else: - average_distance.append(np.nan) - - + average_distance_list.append(np.nan) plt.figure(figsize=(10,5)) -plt.subplot(1, 3, 1) +plt.subplot(1, 4, 1) plt.plot(agent_count_list) plt.title('Anzahl Agenten über Zeit') plt.xlabel('Zeitschritt') plt.ylabel('Anzahl Agenten') -plt.subplot(1, 3, 2) -plt.plot(average_distance) +plt.subplot(1, 4, 2) +plt.plot(average_distance_list) plt.title('Mittlere Distanz Agenten zum Ziel') plt.xlabel('Zeitschritt') plt.ylabel('Distanz') +plt.subplot(1, 4, 3) +plt.plot(average_speed_list) +plt.title('Mittlere Geschwindigkeit der Agenten') +plt.xlabel('Zeitschritt') +plt.ylabel('Geschwindigkeit') +plt.subplot(1, 4, 4) +if len(density_list) > len(average_speed_list): + plt.plot(density_list[:-1], average_speed_list) +else: + plt.plot(density_list, average_speed_list[:len(density_list)]) +plt.title('Fundamental Diagram of Traffic Flow') +plt.xlabel('Dichte') +plt.ylabel('Mittlere Geschwindigkeit') plt.tight_layout() plt.show() -visualization.animate_grid_states(timesteps) \ No newline at end of file +# visualization.animate_grid_states(timesteps) \ No newline at end of file From 8c39ea2a81d561af058d30c756a64c6bcec71f30 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Fri, 27 Dec 2024 09:39:27 +0100 Subject: [PATCH 05/26] Erste Version Social Penalty --- Application/Cell.py | 193 ++++++++++++++++++--------------- Application/Grid.py | 43 +++++++- Application/run.py | 8 +- Application/test_components.py | 169 ++++++++++++++++++++++++++++- 4 files changed, 318 insertions(+), 95 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 03400ca..d87c98f 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -169,7 +169,7 @@ def __init__(self, row, col, cell_size): self.id = self.__hash__() self.route = [] self.movement_range = self.velocity - + self.target = None def log_state(self, timestep, log_file="logs/agent_states.log"): """Log the agent's state to a file.""" @@ -259,12 +259,14 @@ def social_force(self, grid): penalty += penalty_contribution print(f"Social force penalty for {self.__hash__()} is {penalty}") return penalty - @log_decorator + # @log_decorator def repulsive_force(self, width, height): + if width == 0: # Ensure no division by zero for width + width = 1e-6 # Substitute with a very small number repulsive_force = -height + math.exp(1 / (2/width)**2 -1) return repulsive_force - @log_decorator + # @log_decorator def social_penalty(self, grid): total_penalty = 0 neighbors = self.get_neighbors(grid, radius=2) @@ -272,9 +274,10 @@ def social_penalty(self, grid): for cell in cells: if isinstance(cell, Agent): height, width = self.manhattan_difference_to(cell) - print(height, width) + #print(height, width) penalty_term = self.repulsive_force(width, height) total_penalty = total_penalty+penalty_term + #print(total_penalty) return total_penalty @@ -284,102 +287,120 @@ def increase_movement_range(self): self.movement_range = self.velocity + self.movement_range return self.movement_range + def movement_decision(self, grid): + """ + Calculate the next move for the agent without modifying the grid. + Returns the new position (row, col). + """ + if self.arrived: + return None + if self.target is None: + + target = self.find_target(grid.target_cells) + + if not target: + return None + + # Calculate the best move (similar logic as before) + target_key = (target[0], target[1]) + distance_map = grid.dijkstra_distance_maps.get( + target_key) if grid.movement_method == "dijkstra" else grid.flood_fill_distance_maps.get(target_key) + + if not distance_map: + return None + valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) + valid_neighbors.append(self) # Include current position + + best_move = self + smallest_cost = float('inf') + for neighbor in valid_neighbors: + distance_to_target = distance_map[neighbor.row][neighbor.col] + social_penalty = self.social_penalty(grid) + staying_penalty = 2 if neighbor == self else 0 + total_cost = distance_to_target + social_penalty + staying_penalty + + if total_cost < smallest_cost: + smallest_cost = total_cost + best_move = neighbor + + return (best_move.row, best_move.col) if best_move != self else None #Bewegungslogik # sure this method does make sense here from a architectural point of view? - def movement_towards_target(self, grid): - + def movement_towards_target(self, grid): + """ + Decide movement based on target proximity and social penalties. """ - Use precomputed distance maps to move toward the target. - Supports flood-fill or Dijkstra-based maps. - """ if self.arrived: return - # Determine the target and select the appropriate distance map + # Determine the target and select the distance map target = self.find_target(grid.target_cells) if not target: return - target_key = (target[0], target[1]) # Coordinates of the target - - if grid.movement_method=="dijkstra": + target_key = (target[0], target[1]) + if grid.movement_method == "dijkstra": distance_map = grid.dijkstra_distance_maps.get(target_key) - - # Find the best move - valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) - # valid_neighbors = [ - # cell for layer in neighbors.values() - # for cell in layer - # if not isinstance(cell, ObstacleCell) and not grid.is_cell_occupied(cell.row, cell.col) and not isinstance(cell, TargetCell) and cell.is_passable() - # ] - - # Include the agent's current position as an option - valid_neighbors.append(self) - - # Determine the neighbor with the smallest distance to the target - best_move = self - smallest_distance = distance_map[self.row][self.col] - - for neighbor in valid_neighbors: - distance = distance_map[neighbor.row][neighbor.col] - if distance < smallest_distance: - smallest_distance = distance - best_move = neighbor - - # Move to the best neighbor - if best_move != self: - grid.grid[self.row][self.col] = Cell(self.row, self.col,cell_size=self.cell_size) # Clear current position - grid.grid[best_move.row][best_move.col] = self # Update agent position - self.row, self.col = best_move.row, best_move.col - - # Mark as arrived if adjacent to the target - if smallest_distance == 0: - self.arrived = True - grid.agents.remove(self) - grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - print(f"Agent at ({self.row}, {self.col}) has arrived at the target.") - elif grid.movement_method=="floodfill": + elif grid.movement_method == "floodfill": distance_map = grid.flood_fill_distance_maps.get(target_key) - #print(distance_map) - valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) - # valid_neighbors = [ - # cell for layer in neighbors.values() - # for cell in layer - # if grid.grid[cell.row][cell.col].is_passable() - # ] - - # Include the agent's current position as an option - valid_neighbors.append(self) - - # Determine the neighbor with the smallest distance to the target - best_move = self - smallest_distance = distance_map[self.row][self.col] - - - - for neighbor in valid_neighbors: - neighbor_distance = distance_map[neighbor.row][neighbor.col] - if neighbor_distance < smallest_distance: - smallest_distance = neighbor_distance - best_move = neighbor - - # Move to the best neighbor - if best_move != self: - # Clear current position - grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - # Update agent position - grid.grid[best_move.row][best_move.col] = self - self.row, self.col = best_move.row, best_move.col - - # Check if the agent has reached the target - if smallest_distance == 0: # Reached the target - self.arrived = True - grid.agents.remove(self) - grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - print(f"Agent at ({self.row}, {self.col}) has arrived at the target.") + if not distance_map: - return # No distance map available + return # Ensure the distance map is available + + # Get valid neighbors + valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) + valid_neighbors.append(self) # Include the current position as a fallback + social_penalties = { + (neighbor.row, neighbor.col): self.social_penalty(grid) + for neighbor in valid_neighbors + } + + # Determine the best move + best_move = self + smallest_cost = float('inf') + + for neighbor in valid_neighbors: + # Cache distance to target for efficiency + distance_to_target = distance_map[neighbor.row][neighbor.col] + + # Fetch precomputed social penalty + penalty = social_penalties[(neighbor.row, neighbor.col)] + + # Add penalty for staying in place + staying_penalty = 1.0 if neighbor == self else 0 + + total_cost = distance_to_target + penalty + staying_penalty + + if total_cost < smallest_cost: + smallest_cost = total_cost + best_move = neighbor + + # Check if the best move is onto the target + if (best_move.row, best_move.col) == target: + self.arrived = True + grid.agents.remove(self) + # Leave the target cell unchanged + grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + return + + # Move to the best neighbor + if best_move != self: + grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + grid.grid[best_move.row][best_move.col] = self + self.row, self.col = best_move.row, best_move.col + + if smallest_cost == 0: # If reached the target + self.arrived = True + grid.agents.remove(self) + grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + + # Mark as arrived if at the target + # if smallest_cost == 0: + # self.arrived = True + # grid.agents.remove(self) + # #grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + # print(f"Agent at ({self.row}, {self.col}) has arrived at the target.") diff --git a/Application/Grid.py b/Application/Grid.py index 8b1e4cb..b9aff39 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -10,6 +10,7 @@ import matplotlib.colors as mcolors import math import numpy as np +from concurrent.futures import ThreadPoolExecutor #Helper function for convert def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) @@ -253,7 +254,7 @@ def display(self): #Update funktion: Wir müssen nur die Agenten bewegen und die Spawns für den nächsten Zeitschritt durchführen def update(self, target_list, timestep): - if timestep%4 == 0: + if timestep == 0: self.update_distance_maps() #Bewege Agenten @@ -277,6 +278,46 @@ def update(self, target_list, timestep): cell.spawn_agents(self, max_agents) self.log_grid_state(timestep) + def calculate_movement(self,agent): + if not agent.arrived: + # Store the agent's movement decision (current and next position) + return (agent, agent.movement_decision(self)) # New method to compute move + return None + + def update_p(self, target_list, timestep): + """ + Parallelized update for agent movements with resolved self references. + """ + + # Helper function to calculate an agent's movement + def calculate_movement(agent, grid): + if not agent.arrived: + return (agent, agent.movement_decision(grid)) + return None + + # Step 1: Compute movements in parallel + move_decisions = [] + with ThreadPoolExecutor() as executor: + move_decisions = list( + filter( + None, + executor.map(lambda agent: calculate_movement(agent, self), self.agents), + ) + ) + + # Step 2: Apply movements sequentially to update the grid + for agent, new_position in move_decisions: + if new_position: + # Clear the agent's current position + self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, cell_size=self.cell_size) + # Move the agent to the new position + self.grid[new_position[0]][new_position[1]] = agent + agent.row, agent.col = new_position + + # Remove agents that arrived at their targets + self.agents = [agent for agent in self.agents if not agent.arrived] + + print(f"Timestep {timestep} complete. Active agents: {len(self.agents)}") def update_distance_maps(self): """ diff --git a/Application/run.py b/Application/run.py index 097beb6..bc7d1f7 100644 --- a/Application/run.py +++ b/Application/run.py @@ -5,7 +5,7 @@ from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4 -grid = RiMEA9() +grid = RiMEA9(2, "dijkstra") visualization = Visualization(grid) agent_count_list = [] @@ -13,11 +13,11 @@ average_speed_list = [] density_list = [] timesteps = 10000 - +grid.update_distance_maps() for i in range(timesteps): grid.update(target_list=grid.target_cells, timestep=i) - visualization.plot_grid_state(i) - #grid.plot_grid_state(i) + #visualization.plot_grid_state(i) + grid.plot_grid_state(i) plt.pause(0.01) agent_count = len(grid.agents) diff --git a/Application/test_components.py b/Application/test_components.py index f4b7f74..5d48dd2 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -1,7 +1,7 @@ import unittest, math from Grid import Grid from Cell import Cell, SpawnCell, TargetCell, Agent, ObstacleCell - +from concurrent.futures import ThreadPoolExecutor #Werden wir später noch in einen dedizierten Testordner verschieben class TestAgentBehavior(unittest.TestCase): @@ -14,9 +14,10 @@ def setUp(self): cell_size=1, spawn_cells=[(1, 1)], target_cells=[(3, 3)], + movement_method="dijkstra", obstacle_cells=[]#Momentan noch keine Kolisionsvermeidung ) - + self.grid.update_distance_maps() def test_spawn_agents_restrict_to_neighbors(self): spawn_cell = self.grid.grid[1][1] # Define a spawn cell self.assertIsInstance(spawn_cell, SpawnCell, "Expected a SpawnCell at (1, 1)") @@ -34,7 +35,7 @@ def test_spawn_agents(self): self.assertEqual(len(self.grid.agents), 0) # Update erzeugt neue Agenten - self.grid.update(self.grid.target_cells, timestep=1) + #self.grid.update(self.grid.target_cells, timestep=1) # Verify all agents are in valid spawn neighbor cells @@ -57,7 +58,8 @@ def test_agent_removal_on_arrival(self): # Update bewirkt das er sich Ziel Nähert for timestep in range(10): self.grid.update(self.grid.target_cells, timestep=timestep) - #print(self.grid.agents) + #self.grid.plot_distance_map(self.grid.dijkstra_distance_maps([(3,3)])) + self.grid.plot_grid_state(timestep) # Verifiziere Löschung self.assertNotIn(agent, self.grid.agents, "Agent was not removed after arrival.") @@ -280,5 +282,164 @@ def test_social_penalty(self): # Verify the penalty is calculated correctly self.assertLess(penalty, 0, "Penalty should be smaller than 0 when there are neighboring agents.") +class TestRepulsiveForceAndSocialPenalty(unittest.TestCase): + def setUp(self): + # Create a 5x5 grid with agents and targets + self.grid = Grid( + length=5, + height=5, + spawn_cells=[], + target_cells=[(4, 4)], + obstacle_cells=[], + cell_size=1.0, + movement_method="dijkstra" + ) + self.grid.update_distance_maps() + + # Place agents + self.agent1 = Agent(2, 2, cell_size=1.0) + self.agent2 = Agent(2, 3, cell_size=1.0) + self.grid.grid[2][2] = self.agent1 + self.grid.grid[2][3] = self.agent2 + self.grid.agents.extend([self.agent1, self.agent2]) + + def test_social_penalty_computation(self): + penalty = self.agent1.social_penalty(self.grid) + self.assertGreater(penalty, 0, "Social penalty should be positive with nearby agents.") + + def test_repulsive_force_computation(self): + height, width = self.agent1.manhattan_difference_to(self.agent2) + repulsive_force = self.agent1.repulsive_force(width, height) + self.assertGreater(repulsive_force, 0, "Repulsive force should be positive for neighboring agents.") + + def test_agent_avoids_high_penalty(self): + # Add additional agents to simulate crowding + additional_agents = [ + Agent(1, 1, cell_size=1.0), + Agent(1, 2, cell_size=1.0), + Agent(1, 3, cell_size=1.0) + ] + for agent in additional_agents: + self.grid.grid[agent.row][agent.col] = agent + self.grid.agents.append(agent) + + # Update and ensure agent1 moves to minimize penalty + self.grid.update(self.grid.target_cells, timestep=1) + self.assertNotEqual((self.agent1.row, self.agent1.col), (2, 2), "Agent should move away from high-penalty area.") + + def test_agent_movement_with_penalties(self): + original_position = (self.agent1.row, self.agent1.col) + self.grid.update(self.grid.target_cells, timestep=1) + new_position = (self.agent1.row, self.agent1.col) + self.assertNotEqual(original_position, new_position, "Agent should move considering both distance and penalties.") + +class TestParallelizedAgentMovement(unittest.TestCase): + def setUp(self): + """ + Set up a small grid with multiple agents for testing parallelization. + """ + self.grid = Grid( + length=5, + height=5, + spawn_cells=[], + target_cells=[(4, 4)], + obstacle_cells=[], + cell_size=1.0, + movement_method="dijkstra" + ) + self.grid.update_distance_maps() + + # Add agents + self.agent1 = Agent(0, 0, cell_size=1.0) + self.agent2 = Agent(0, 1, cell_size=1.0) + self.agent3 = Agent(1, 0, cell_size=1.0) + self.grid.grid[0][0] = self.agent1 + self.grid.grid[0][1] = self.agent2 + self.grid.grid[1][0] = self.agent3 + self.grid.agents.extend([self.agent1, self.agent2, self.agent3]) + + def test_unique_agent_positions_after_update(self): + """ + Ensure no two agents occupy the same cell after an update. + """ + def move_agent(agent): + if not agent.arrived: + agent.movement_towards_target(self.grid) + + # Parallelize agent movement + with ThreadPoolExecutor() as executor: + executor.map(move_agent, self.grid.agents) + + # Collect agent positions + positions = [(agent.row, agent.col) for agent in self.grid.agents] + unique_positions = set(positions) + + # Ensure all positions are unique + self.assertEqual(len(positions), len(unique_positions), "Agents occupy the same cell after update.") + + def test_correct_movement_logic(self): + """ + Verify each agent independently follows the correct movement logic. + """ + initial_positions = [(agent.row, agent.col) for agent in self.grid.agents] + + def move_agent(agent): + if not agent.arrived: + agent.movement_towards_target(self.grid) + + with ThreadPoolExecutor() as executor: + executor.map(move_agent, self.grid.agents) + + new_positions = [(agent.row, agent.col) for agent in self.grid.agents] + + # Ensure agents have moved and are closer to the target + for i, (old_pos, new_pos) in enumerate(zip(initial_positions, new_positions)): + self.assertNotEqual(old_pos, new_pos, f"Agent {i} did not move.") + self.assertLess( + self.grid.dijkstra_distance_maps[(4, 4)][new_pos[0]][new_pos[1]], + self.grid.dijkstra_distance_maps[(4, 4)][old_pos[0]][old_pos[1]], + f"Agent {i} did not move closer to the target." + ) + + def test_grid_consistency_after_update(self): + """ + Ensure grid consistency after parallel updates. + """ + def move_agent(agent): + if not agent.arrived: + agent.movement_towards_target(self.grid) + + with ThreadPoolExecutor() as executor: + executor.map(move_agent, self.grid.agents) + + # Verify grid reflects agent positions correctly + for agent in self.grid.agents: + cell = self.grid.grid[agent.row][agent.col] + self.assertIs(agent, cell, f"Grid inconsistency: Agent not correctly placed at ({agent.row}, {agent.col}).") + + def test_arrived_agents_removed_correctly(self): + """ + Verify agents that arrive at the target are removed from the grid and agent list. + """ + # Place one agent directly on the target + target_agent = Agent(4, 4, cell_size=1.0) + self.grid.grid[4][4] = target_agent + self.grid.agents.append(target_agent) + + def move_agent(agent): + if not agent.arrived: + agent.movement_towards_target(self.grid) + + with ThreadPoolExecutor() as executor: + executor.map(move_agent, self.grid.agents) + + # Ensure the agent is removed + self.assertNotIn(target_agent, self.grid.agents, "Arrived agent was not removed.") + self.assertNotIsInstance(self.grid.grid[4][4], Agent, "Arrived agent is still present in the target cell.") + +if __name__ == "__main__": + unittest.main() + + if __name__ == '__main__': unittest.main() From cf2b1feef212ea91e43f78a3bd8ea94478ca7386 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Fri, 27 Dec 2024 10:31:24 +0100 Subject: [PATCH 06/26] Erste Version Social Penalty --- Application/Grid.py | 103 +++++++++++++++++++++++++++++++++++++++++++- Application/run.py | 32 ++++++++++++-- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index b9aff39..5c0a1db 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -31,6 +31,9 @@ def __init__(self, length, height, spawn_cells, target_cells, obstacle_cells, ce self.obstacle_cells = obstacle_cells self.agents = [] # Liste mit allen Agenten die sich auf dem Feld befinden self.movement_method = movement_method + self.density_data = [] + self.speed_data = [] + self.flow_data = [] # Aufbau von Spawn, Zielen und Hindernissen for row, col in spawn_cells: @@ -86,6 +89,19 @@ def select_area_by_coordinates(self, start_x, start_y, end_x, end_y): ] return selected_cells + + def get_agents_in_rectangular_roi(self, start_x, start_y, end_x, end_y): + """ + Get all agents within a rectangular ROI defined by real-world coordinates. + Parameters: + start_x, start_y (float): Bottom-left corner of the rectangle in meters. + end_x, end_y (float): Top-right corner of the rectangle in meters. + Returns: + List[Agent]: List of agents within the ROI. + """ + selected_cells = self.select_area_by_coordinates(start_x, start_y, end_x, end_y) + agents_in_roi = [cell for cell in selected_cells if isinstance(cell, Agent)] + return agents_in_roi #Plaziere Wand um Feld def place_border(self): """Place a border around the grid""" @@ -269,7 +285,10 @@ def update(self, target_list, timestep): agent.movement_towards_target(self) # Pass the grid instance agent.log_state(timestep) - + densities, speeds, flows = self.calculate_density_speed_flow() + self.density_data.append(densities) + self.speed_data.append(speeds) + self.flow_data.append(flows) #Spawne Agenten ( for row, col in self.spawn_cells: cell = self.grid[row][col] @@ -377,6 +396,35 @@ def plot_distance_map(self, distance_map, title="Distance Map"): plt.ylabel("Rows") plt.show() + def calculate_density_speed_flow(self): + """ + Calculate density, speed, and flow for each timestep. + Density: Agents per unit area. + Speed: Average speed of agents. + Flow: Number of agents crossing a reference line. + """ + densities = [] + speeds = [] + flows = [] + + # Define a grid area to calculate density and a reference line for flow + area = self.rows * self.cols + reference_line = self.rows // 2 # Example: Middle row of the grid + + # Calculate density (number of agents / area) + density = len(self.agents) / area + densities.append(density) + + # Calculate speed (average agent velocity) + avg_speed = np.mean([agent.velocity for agent in self.agents]) + speeds.append(avg_speed) + + # Calculate flow (agents crossing the reference line) + flow = sum(1 for agent in self.agents if agent.row == reference_line) + flows.append(flow) + + return densities, speeds, flows + def plot_grid_state(grid, timestep): #Plot Ausgabe für klarere Visualisierung, momentan noch über States für Farbwahl: Evtl besser mit cell.color? # Convert grid to a DataFrame for easy visualization @@ -413,6 +461,59 @@ def plot_grid_state(grid, timestep): plt.ylabel("Rows") plt.show() + def calculate_density_speed_flow_in_rectangular_roi(self, start_x, start_y, end_x, end_y): + """ + Calculate density, speed, and flow in a rectangular ROI. + Parameters: + start_x, start_y (float): Bottom-left corner of the rectangle in meters. + end_x, end_y (float): Top-right corner of the rectangle in meters. + Returns: + Tuple[float, float, int]: Density (agents/m²), average speed (m/s), flow (agents/s). + """ + agents_in_roi = self.get_agents_in_rectangular_roi(start_x, start_y, end_x, end_y) + + # Calculate the area of the rectangular ROI in m² + width = abs(end_x - start_x) + height = abs(end_y - start_y) + area_m2 = width * height + + # Density: Number of agents per unit area + density = len(agents_in_roi) / area_m2 + + # Speed: Average velocity of agents in the ROI + avg_speed = np.mean([agent.velocity for agent in agents_in_roi]) if agents_in_roi else 0 + + # Flow: Count agents exiting the rectangle + # Example: If the bottom edge is the exit boundary + flow = sum(1 for agent in agents_in_roi if agent.row == self.rows - 1) + + return density, avg_speed, flow + + def plot_fundamental_diagram(self): + """ + Plot the fundamental diagram with density vs speed and flow. + """ + import matplotlib.pyplot as plt + + densities = self.density_data + speeds = self.speed_data + flows = self.flow_data + + fig, ax1 = plt.subplots() + + ax1.set_xlabel("Density (agents/unit area)") + ax1.set_ylabel("Speed (units/time)", color="blue") + ax1.plot(densities, speeds, label="Speed", color="blue") + ax1.tick_params(axis="y", labelcolor="blue") + + ax2 = ax1.twinx() # instantiate a second y-axis that shares the same x-axis + ax2.set_ylabel("Flow (agents/unit time)", color="red") + ax2.plot(densities, flows, label="Flow", color="red") + ax2.tick_params(axis="y", labelcolor="red") + + fig.tight_layout() # ensure everything fits without overlap + plt.title("Fundamental Diagram") + plt.show() def create_logfile(self): path = f"gridlog-{self.__hash__()}.txt" if(os.path.isfile(path)): diff --git a/Application/run.py b/Application/run.py index bc7d1f7..6e894ba 100644 --- a/Application/run.py +++ b/Application/run.py @@ -4,7 +4,27 @@ import numpy as np from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4 +def plot_fundamental_diagram(densities, speeds, flows): + """ + Plot density vs. speed and flow for ROI-based analysis. + """ + import matplotlib.pyplot as plt + fig, ax1 = plt.subplots() + + ax1.set_xlabel("Density (agents/m²)") + ax1.set_ylabel("Speed (m/s)", color="blue") + ax1.plot(densities, speeds, label="Speed", color="blue") + ax1.tick_params(axis="y", labelcolor="blue") + + ax2 = ax1.twinx() # instantiate a second y-axis that shares the same x-axis + ax2.set_ylabel("Flow (agents/s)", color="red") + ax2.plot(densities, flows, label="Flow", color="red") + ax2.tick_params(axis="y", labelcolor="red") + + fig.tight_layout() # ensure everything fits without overlap + plt.title("Fundamental Diagram at Exit") + plt.show() grid = RiMEA9(2, "dijkstra") visualization = Visualization(grid) @@ -12,14 +32,20 @@ average_distance_list = [] average_speed_list = [] density_list = [] +densities = [] +speeds = [] +flows = [] timesteps = 10000 grid.update_distance_maps() -for i in range(timesteps): +for i in range(20): grid.update(target_list=grid.target_cells, timestep=i) #visualization.plot_grid_state(i) grid.plot_grid_state(i) plt.pause(0.01) - + density, speed, flow = grid.calculate_density_speed_flow_in_rectangular_roi(10, 8, 12, 8) + densities.append(density) + speeds.append(speed) + flows.append(flow) agent_count = len(grid.agents) agent_count_list.append(agent_count) @@ -50,7 +76,7 @@ average_distance_list.append(total_distance_to_target / len(grid.agents)) else: average_distance_list.append(np.nan) - +plot_fundamental_diagram(densities, speeds, flows) plt.figure(figsize=(10,5)) plt.subplot(1, 4, 1) From 8edddc91b399a690b489cd16fc283b5788d9da70 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Fri, 27 Dec 2024 14:51:55 +0100 Subject: [PATCH 07/26] =?UTF-8?q?Erste=20Version=20Social=20Penalty,=20noc?= =?UTF-8?q?h=20weiter=20angepasst.=20Gibt=20immernoch=20Probleme=20(Stragg?= =?UTF-8?q?ler=20bzw.=20Tie-braker=20die=20abgefangen=20werden=20m=C3=BCss?= =?UTF-8?q?en)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Cell.py | 105 ++++++++++++++++++++++------ Application/Grid.py | 164 ++++++++++++++++++++++++++++++++++++++------ Application/run.py | 10 +-- 3 files changed, 234 insertions(+), 45 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index d87c98f..23e302d 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -1,7 +1,7 @@ import random import math - +import numpy as np from decorator import log_decorator @@ -268,16 +268,32 @@ def repulsive_force(self, width, height): return repulsive_force # @log_decorator def social_penalty(self, grid): + """ + Calculate the social penalty based on agent proximity and movement preference. + Returns a penalty score to discourage crowding. + """ total_penalty = 0 - neighbors = self.get_neighbors(grid, radius=2) + cutoff_distance = 3.0 # Maximum distance in meters to consider for social penalty + penalty_decay_factor = 0.5 # Control the steepness of the Gaussian decay + stay_penalty = 0.5 # Additional penalty for remaining stationary + + # Get neighbors within a cutoff radius + neighbors = self.get_neighbors(grid, radius=int(cutoff_distance / grid.cell_size)) + for distance, cells in neighbors.items(): for cell in cells: - if isinstance(cell, Agent): - height, width = self.manhattan_difference_to(cell) - #print(height, width) - penalty_term = self.repulsive_force(width, height) - total_penalty = total_penalty+penalty_term - #print(total_penalty) + if isinstance(cell, Agent) and not cell.arrived: # Only consider other agents + # Compute Euclidean distance in meters + euclidean_distance = self.euclidean_distance_to(cell) + + if euclidean_distance <= cutoff_distance: + # Apply Gaussian decay penalty + penalty_contribution = math.exp(-(euclidean_distance ** 2) / (2 * penalty_decay_factor ** 2)) + total_penalty += penalty_contribution + + # Add penalty for staying in place + total_penalty += stay_penalty + return total_penalty @@ -287,43 +303,92 @@ def increase_movement_range(self): self.movement_range = self.velocity + self.movement_range return self.movement_range - def movement_decision(self, grid): + # def movement_decision(self, grid): + # """ + # Calculate the next move for the agent without modifying the grid. + # Returns the new position (row, col). + # """ + # if self.arrived: + # return None +# + # if self.target is None: + # self.target = self.find_target(grid.target_cells) +# + # if not self.target: + # return None +# + # # Select the appropriate distance map + # target_key = (self.target[0], self.target[1]) + # distance_map = grid.dijkstra_distance_maps.get(target_key) or grid.flood_fill_distance_maps.get(target_key) +# + # if not distance_map: + # return None +# + # # Get valid neighbors and include current position + # valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) + # valid_neighbors.append(self) # Allow staying in place if necessary +# + # best_move = self + # smallest_cost = float('inf') +# + # for neighbor in valid_neighbors: + # distance_to_target = distance_map[neighbor.row][neighbor.col] + # social_penalty = neighbor.social_penalty(grid) # Calculate penalty for the neighbor + # staying_penalty = 2 if neighbor == self else 0 # Encourage moving over staying + # total_cost = distance_to_target + social_penalty + staying_penalty +# + # if total_cost < smallest_cost: + # smallest_cost = total_cost + # best_move = neighbor +# + # return (best_move.row, best_move.col) if best_move != self else None + + def movement_decision(self, grid, precomputed_penalties, agent_index): """ - Calculate the next move for the agent without modifying the grid. - Returns the new position (row, col). + Decide the next move for the agent. Mark as arrived if reaching the target. + Returns the new position (row, col) or None if no movement. """ if self.arrived: return None - if self.target is None: - target = self.find_target(grid.target_cells) + if self.target is None: + self.target = self.find_target(grid.target_cells) - if not target: + if not self.target: return None - # Calculate the best move (similar logic as before) - target_key = (target[0], target[1]) - distance_map = grid.dijkstra_distance_maps.get( - target_key) if grid.movement_method == "dijkstra" else grid.flood_fill_distance_maps.get(target_key) + target_key = (self.target[0], self.target[1]) + distance_map = grid.dijkstra_distance_maps.get(target_key) or grid.flood_fill_distance_maps.get(target_key) if not distance_map: return None valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) - valid_neighbors.append(self) # Include current position + valid_neighbors.append(self) # Include staying in place as an option best_move = self smallest_cost = float('inf') + for neighbor in valid_neighbors: distance_to_target = distance_map[neighbor.row][neighbor.col] - social_penalty = self.social_penalty(grid) + social_penalty = precomputed_penalties[agent_index] staying_penalty = 2 if neighbor == self else 0 + + # Reduce weight of social penalties near the target + if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): + social_penalty *= 0.2 # Halve the effect of social penalties near the target + total_cost = distance_to_target + social_penalty + staying_penalty if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor + # Mark as arrived if moving onto the target + if isinstance(grid.grid[best_move.row][best_move.col], TargetCell): + self.arrived = True + return None + return (best_move.row, best_move.col) if best_move != self else None #Bewegungslogik # sure this method does make sense here from a architectural point of view? diff --git a/Application/Grid.py b/Application/Grid.py index 5c0a1db..4e34577 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -267,36 +267,136 @@ def display(self): print(" ".join(str(cell) for cell in row)) print() + def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5): + """ + Compute social penalties for all agents using a vectorized approach. + Parameters: + grid (Grid): The simulation grid. + cutoff_distance (float): Maximum distance in meters to consider for social penalties. + penalty_decay_factor (float): Controls the steepness of the Gaussian decay. + Returns: + np.ndarray: Social penalties for all agents. + """ + positions = grid.get_agent_positions() # Shape: (num_agents, 2) + # If positions is 1D, reshape it + if positions.ndim == 1: + positions = positions.reshape(-1, 2) + #print(f"Position : None :{positions[:, None, :]}") + #print(f"Position None ::{positions[None, :, :]}") + arrived = grid.get_agent_arrival_status() # Shape: (num_agents,) + num_agents = positions.shape[0] + #print(num_agents) + # Compute pairwise Euclidean distances (broadcasting) + deltas = positions[:, None, :] - positions[None, :, :] # Shape: (num_agents, num_agents, 2) + distances = np.linalg.norm(deltas, axis=2) # Shape: (num_agents, num_agents) + + # Apply cutoff distance (set penalties to 0 beyond this distance) + mask = (distances <= cutoff_distance) & ~np.eye(num_agents, dtype=bool) # Ignore self-distances + distances[~mask] = np.inf + + # Apply Gaussian decay penalty + penalties = np.exp(-(distances ** 2) / (2 * penalty_decay_factor ** 2)) # Shape: (num_agents, num_agents) + penalties[~mask] = 0 # Ensure penalties are 0 for distances > cutoff + + # Sum penalties for each agent + total_penalties = penalties.sum(axis=1) # Shape: (num_agents,) + + # Add a small penalty for staying in place + stay_penalty = 0.5 + total_penalties += stay_penalty + + return total_penalties + + + + #Update funktion: Wir müssen nur die Agenten bewegen und die Spawns für den nächsten Zeitschritt durchführen + # def update(self, target_list, timestep): + # + # if timestep == 0: + # self.update_distance_maps() + # + # #Bewege Agenten + # for agent in self.agents: + # if agent.arrived == True: + # self.agents.remove(agent) + # + # #print(agent) + # if self.movement_method == "floodfill": + # agent.movement_towards_target(self) + # elif self.movement_method == "dijkstra": + # agent.movement_towards_target(self) # Pass the grid instance + # + # agent.log_state(timestep) + # densities, speeds, flows = self.calculate_density_speed_flow() + # self.density_data.append(densities) + # self.speed_data.append(speeds) + # self.flow_data.append(flows) + # #Spawne Agenten ( + # for row, col in self.spawn_cells: + # cell = self.grid[row][col] + # if isinstance(cell, SpawnCell): # Check if the cell at (row, col) is a SpawnCell + # max_agents = 1 # Adjust the number of agents to spawn as needed + # cell.spawn_agents(self, max_agents) + # + # self.log_grid_state(timestep) def update(self, target_list, timestep): - + """ + Update the grid by moving agents and handling arrivals. + """ if timestep == 0: self.update_distance_maps() - - #Bewege Agenten - for agent in self.agents: - if agent.arrived == True: - self.agents.remove(agent) - #print(agent) - if self.movement_method == "floodfill": - agent.movement_towards_target(self) - elif self.movement_method == "dijkstra": - agent.movement_towards_target(self) # Pass the grid instance - - agent.log_state(timestep) - densities, speeds, flows = self.calculate_density_speed_flow() - self.density_data.append(densities) - self.speed_data.append(speeds) - self.flow_data.append(flows) - #Spawne Agenten ( + # Compute social penalties + precomputed_penalties = self.compute_social_penalties() + + # List to track agents to remove + agents_to_remove = [] + + for i, agent in enumerate(self.agents[:]): # Iterate over a copy of the agents list + if agent.arrived: + agents_to_remove.append(agent) + continue + + new_position = agent.movement_decision(self, precomputed_penalties, i) + if new_position: + current_row, current_col = agent.row, agent.col + new_row, new_col = new_position + + # Update grid: Move the agent + if not isinstance(self.grid[new_row][new_col], TargetCell): + self.grid[new_row][new_col] = agent + + # Restore the current cell + self.grid[current_row][current_col] = ( + TargetCell(current_row, current_col, self.cell_size) + if (current_row, current_col) in target_list + else Cell(current_row, current_col, self.cell_size) + ) + + # Update agent position + agent.row, agent.col = new_row, new_col + + # Remove agents that have arrived + for agent in agents_to_remove: + print(f"Removing agent {agent} from ({agent.row}, {agent.col})") + self.agents.remove(agent) + + # Restore target cell explicitly + if (agent.row, agent.col) in target_list: + self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) + else: + self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) + + # Spawn new agents for row, col in self.spawn_cells: cell = self.grid[row][col] - if isinstance(cell, SpawnCell): # Check if the cell at (row, col) is a SpawnCell - max_agents = 1 # Adjust the number of agents to spawn as needed + if isinstance(cell, SpawnCell): + max_agents = 1 cell.spawn_agents(self, max_agents) self.log_grid_state(timestep) + def calculate_movement(self,agent): if not agent.arrived: # Store the agent's movement decision (current and next position) @@ -489,6 +589,30 @@ def calculate_density_speed_flow_in_rectangular_roi(self, start_x, start_y, end_ return density, avg_speed, flow + def get_agent_positions(self): + """ + Extract the positions of all agents as a NumPy array. + Returns: + np.ndarray: Array of shape (num_agents, 2) with rows [row, col]. + """ + return np.array([[agent.row, agent.col] for agent in self.agents]) + + def get_agent_arrival_status(self): + """ + Extract whether each agent has arrived. + Returns: + np.ndarray: Boolean array of shape (num_agents,) indicating arrival status. + """ + return np.array([agent.arrived for agent in self.agents]) + + def get_agent_velocities(self): + """ + Extract the velocities of all agents. + Returns: + np.ndarray: Array of shape (num_agents,) with agent velocities. + """ + return np.array([agent.velocity for agent in self.agents]) + def plot_fundamental_diagram(self): """ Plot the fundamental diagram with density vs speed and flow. diff --git a/Application/run.py b/Application/run.py index 6e894ba..f414746 100644 --- a/Application/run.py +++ b/Application/run.py @@ -37,15 +37,15 @@ def plot_fundamental_diagram(densities, speeds, flows): flows = [] timesteps = 10000 grid.update_distance_maps() -for i in range(20): +for i in range(800): grid.update(target_list=grid.target_cells, timestep=i) #visualization.plot_grid_state(i) grid.plot_grid_state(i) plt.pause(0.01) - density, speed, flow = grid.calculate_density_speed_flow_in_rectangular_roi(10, 8, 12, 8) - densities.append(density) - speeds.append(speed) - flows.append(flow) + # density, speed, flow = grid.calculate_density_speed_flow_in_rectangular_roi(10, 8, 12, 8) + #densities.append(density) + #speeds.append(speed) + #flows.append(flow) agent_count = len(grid.agents) agent_count_list.append(agent_count) From c4d60f9c5c2fb53112f1ca692fd28920fa480733 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sat, 28 Dec 2024 21:07:40 +0100 Subject: [PATCH 08/26] WIP am Fundamentaldiagram --- Application/Cell.py | 53 +---------- Application/Grid.py | 217 ++++++++++++++++++++++++------------------- Application/run.py | 108 +++++++++++---------- Application/tests.py | 69 +++++++------- 4 files changed, 212 insertions(+), 235 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 23e302d..92d02ef 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -303,45 +303,7 @@ def increase_movement_range(self): self.movement_range = self.velocity + self.movement_range return self.movement_range - # def movement_decision(self, grid): - # """ - # Calculate the next move for the agent without modifying the grid. - # Returns the new position (row, col). - # """ - # if self.arrived: - # return None -# - # if self.target is None: - # self.target = self.find_target(grid.target_cells) -# - # if not self.target: - # return None -# - # # Select the appropriate distance map - # target_key = (self.target[0], self.target[1]) - # distance_map = grid.dijkstra_distance_maps.get(target_key) or grid.flood_fill_distance_maps.get(target_key) -# - # if not distance_map: - # return None -# - # # Get valid neighbors and include current position - # valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) - # valid_neighbors.append(self) # Allow staying in place if necessary -# - # best_move = self - # smallest_cost = float('inf') -# - # for neighbor in valid_neighbors: - # distance_to_target = distance_map[neighbor.row][neighbor.col] - # social_penalty = neighbor.social_penalty(grid) # Calculate penalty for the neighbor - # staying_penalty = 2 if neighbor == self else 0 # Encourage moving over staying - # total_cost = distance_to_target + social_penalty + staying_penalty -# - # if total_cost < smallest_cost: - # smallest_cost = total_cost - # best_move = neighbor -# - # return (best_move.row, best_move.col) if best_move != self else None + def movement_decision(self, grid, precomputed_penalties, agent_index): """ @@ -376,9 +338,9 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): # Reduce weight of social penalties near the target if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): - social_penalty *= 0.2 # Halve the effect of social penalties near the target - - total_cost = distance_to_target + social_penalty + staying_penalty + social_penalty *= 0.5 # Halve the effect of social penalties near the target + random_bias = random.uniform(-0.5, 0.5) + total_cost = distance_to_target + random_bias +social_penalty + staying_penalty if total_cost < smallest_cost: smallest_cost = total_cost @@ -460,12 +422,7 @@ def movement_towards_target(self, grid): grid.agents.remove(self) grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - # Mark as arrived if at the target - # if smallest_cost == 0: - # self.arrived = True - # grid.agents.remove(self) - # #grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - # print(f"Agent at ({self.row}, {self.col}) has arrived at the target.") + diff --git a/Application/Grid.py b/Application/Grid.py index 4e34577..9a1526d 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -11,6 +11,9 @@ import math import numpy as np from concurrent.futures import ThreadPoolExecutor +import numpy as np +import matplotlib.pyplot as plt +import seaborn as sns #Helper function for convert def clamp(value, min_value, max_value): return max(min(value, max_value), min_value) @@ -146,14 +149,13 @@ def is_cell_occupied(self,row,col): #Helper Methode für Djkstra Algorithmus + def compute_distance_map(self, target): """ Compute the shortest path distances from the target to all cells using Dijkstra's algorithm. - This version calculates direct Euclidean distance for each cell to avoid cumulative errors. - + This version allows setting distances on Agent cells but prohibits Obstacle cells. Parameters: target (tuple): Coordinates of the target cell as (row, col). - Returns: List[List[float]]: A 2D distance map where each cell contains the shortest distance to the target. """ @@ -177,24 +179,25 @@ def compute_distance_map(self, target): if current_distance > distance_map[current_row][current_col]: continue - # Get the current cell and its neighbors - current_cell = self.grid[current_row][current_col] - neighbors = current_cell.get_neighbors(self, radius=1) + # Get neighbors of the current cell + neighbors = self.grid[current_row][current_col].get_neighbors(self, radius=1) for layer in neighbors.values(): for neighbor in layer: neighbor_row, neighbor_col = neighbor.row, neighbor.col - # Skip impassable cells - if not neighbor.is_passable(): + # Skip obstacle cells + if isinstance(self.grid[neighbor_row][neighbor_col], ObstacleCell): continue - # Calculate the direct Euclidean distance from the target + # Allow distances on Agent cells + if isinstance(self.grid[neighbor_row][neighbor_col], Agent): + pass # Agents are treated as passable for distance purposes + + # Calculate the direct Euclidean distance direct_distance = math.sqrt((target_row - neighbor_row) ** 2 + (target_col - neighbor_col) ** 2) - #direct_distance_2 = self.grid[target_row][target_col].euclidean_distance(neighbor) - #print (direct_distance_2, direct_distance) - # If the calculated distance is shorter, update and enqueue the neighbor + # Update the neighbor's distance if this path is shorter if direct_distance < distance_map[neighbor_row][neighbor_col]: distance_map[neighbor_row][neighbor_col] = direct_distance heapq.heappush(queue, (direct_distance, neighbor_row, neighbor_col)) @@ -202,17 +205,16 @@ def compute_distance_map(self, target): return distance_map #Helper Methode für Flood Fill, returned Distance map + def flood_fill(self, target_row, target_col, target_state): """ Perform reverse flood-fill starting from the target (target_row, target_col). Computes a distance map where cells closer to the target have smaller values. Skips impassable cells (e.g., obstacles, borders). - Parameters: target_row (int): Row index of the target. target_col (int): Column index of the target. target_state (int): State of the target cell. - Returns: List[List[float]]: A 2D distance map with Manhattan distances to the target. """ @@ -240,23 +242,27 @@ def flood_fill(self, target_row, target_col, target_state): current_row, current_col = queue.pop(0) current_distance = distance_map[current_row][current_col] - # Get neighbors of the current cell (4 directions: up, down, left, right) for dr, dc in directions: neighbor_row, neighbor_col = current_row + dr, current_col + dc - # Skip if out of bounds + # Skip out-of-bounds cells if not (0 <= neighbor_row < self.rows and 0 <= neighbor_col < self.cols): continue neighbor_cell = self.grid[neighbor_row][neighbor_col] - # Skip impassable cells or already visited cells - if not neighbor_cell.is_passable() or distance_map[neighbor_row][neighbor_col] != float('inf'): + # Skip obstacle cells + if isinstance(neighbor_cell, ObstacleCell): continue - # Update the distance for the neighbor - distance_map[neighbor_row][neighbor_col] = current_distance + 1 - queue.append((neighbor_row, neighbor_col)) + # Allow distances on Agent cells + if isinstance(neighbor_cell, Agent): + pass # Agents are treated as passable for flood-fill purposes + + # Update the distance for the neighbor if not visited + if distance_map[neighbor_row][neighbor_col] == float('inf'): + distance_map[neighbor_row][neighbor_col] = current_distance + 1 + queue.append((neighbor_row, neighbor_col)) return distance_map @@ -465,9 +471,7 @@ def plot_distance_map(self, distance_map, title="Distance Map"): distance_map (List[List[float]]): The 2D distance map to plot. title (str): Title for the heatmap. """ - import numpy as np - import matplotlib.pyplot as plt - import seaborn as sns + # Convert distance map to a numpy array for easier handling distance_array = np.array(distance_map) @@ -496,34 +500,7 @@ def plot_distance_map(self, distance_map, title="Distance Map"): plt.ylabel("Rows") plt.show() - def calculate_density_speed_flow(self): - """ - Calculate density, speed, and flow for each timestep. - Density: Agents per unit area. - Speed: Average speed of agents. - Flow: Number of agents crossing a reference line. - """ - densities = [] - speeds = [] - flows = [] - - # Define a grid area to calculate density and a reference line for flow - area = self.rows * self.cols - reference_line = self.rows // 2 # Example: Middle row of the grid - - # Calculate density (number of agents / area) - density = len(self.agents) / area - densities.append(density) - - # Calculate speed (average agent velocity) - avg_speed = np.mean([agent.velocity for agent in self.agents]) - speeds.append(avg_speed) - - # Calculate flow (agents crossing the reference line) - flow = sum(1 for agent in self.agents if agent.row == reference_line) - flows.append(flow) - return densities, speeds, flows def plot_grid_state(grid, timestep): #Plot Ausgabe für klarere Visualisierung, momentan noch über States für Farbwahl: Evtl besser mit cell.color? @@ -561,33 +538,7 @@ def plot_grid_state(grid, timestep): plt.ylabel("Rows") plt.show() - def calculate_density_speed_flow_in_rectangular_roi(self, start_x, start_y, end_x, end_y): - """ - Calculate density, speed, and flow in a rectangular ROI. - Parameters: - start_x, start_y (float): Bottom-left corner of the rectangle in meters. - end_x, end_y (float): Top-right corner of the rectangle in meters. - Returns: - Tuple[float, float, int]: Density (agents/m²), average speed (m/s), flow (agents/s). - """ - agents_in_roi = self.get_agents_in_rectangular_roi(start_x, start_y, end_x, end_y) - # Calculate the area of the rectangular ROI in m² - width = abs(end_x - start_x) - height = abs(end_y - start_y) - area_m2 = width * height - - # Density: Number of agents per unit area - density = len(agents_in_roi) / area_m2 - - # Speed: Average velocity of agents in the ROI - avg_speed = np.mean([agent.velocity for agent in agents_in_roi]) if agents_in_roi else 0 - - # Flow: Count agents exiting the rectangle - # Example: If the bottom edge is the exit boundary - flow = sum(1 for agent in agents_in_roi if agent.row == self.rows - 1) - - return density, avg_speed, flow def get_agent_positions(self): """ @@ -613,31 +564,111 @@ def get_agent_velocities(self): """ return np.array([agent.velocity for agent in self.agents]) - def plot_fundamental_diagram(self): + # def plot_fundamental_diagram(self): + # """ + # Plot the fundamental diagram with density vs speed and flow. + # """ + # + # + # densities = self.density_data + # speeds = self.speed_data + # flows = self.flow_data + # + # fig, ax1 = plt.subplots() + # + # ax1.set_xlabel("Density (agents/unit area)") + # ax1.set_ylabel("Speed (units/time)", color="blue") + # ax1.plot(densities, speeds, label="Speed", color="blue") + # ax1.tick_params(axis="y", labelcolor="blue") + # + # ax2 = ax1.twinx() # instantiate a second y-axis that shares the same x-axis + # ax2.set_ylabel("Flow (agents/unit time)", color="red") + # ax2.plot(densities, flows, label="Flow", color="red") + # ax2.tick_params(axis="y", labelcolor="red") + # + # fig.tight_layout() # ensure everything fits without overlap + # plt.title("Fundamental Diagram") + # plt.show() + + def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, region_coordinates): """ - Plot the fundamental diagram with density vs speed and flow. + Calculate and plot the Fundamental Diagram. + + Parameters: + grid (Grid): The simulation grid. + boundary (list): A list of (row, col) tuples representing the boundary. + timestep (int): The current simulation timestep. + agents_crossed (dict): A dictionary tracking agents that crossed the boundary. + region_coordinates (tuple): Coordinates defining the region in front of the boundary as (x1, y1, x2, y2). + + Returns: + dict: Contains density, flow, and average speed for the timestep. """ - import matplotlib.pyplot as plt + # Step 1: Calculate Flow (Agents crossing the boundary in this timestep) + current_crossed_agents = [] + for row, col in boundary: + cell = grid.grid[row][col] + if isinstance(cell, Agent) and cell.id not in agents_crossed: + current_crossed_agents.append(cell) + agents_crossed[cell.id] = timestep # Track the agent's crossing + + flow = len(current_crossed_agents) # Number of agents crossing the boundary + + # Step 2: Calculate Density (Agents per square meter in the region in front of the boundary) + region_in_front = grid.select_area_by_coordinates(*region_coordinates) + agent_count = sum( + 1 for cell in region_in_front if isinstance(cell, Agent) + ) + region_area = len(region_in_front) * (grid.cell_size ** 2) # Area of region in m^2 + density = agent_count / region_area if region_area > 0 else 0 - densities = self.density_data - speeds = self.speed_data - flows = self.flow_data + # Step 3: Calculate Average Speed (of agents crossing the boundary) + avg_speed = ( + sum(agent.velocity for agent in current_crossed_agents) / len(current_crossed_agents) + if current_crossed_agents else 0 + ) + + # Step 4: Return and log the results + results = { + "density": density, + "flow": flow, + "avg_speed": avg_speed, + } + + print(f"Timestep {timestep}: Density={density:.2f}, Flow={flow:.2f}, AvgSpeed={avg_speed:.2f}") + return results + + def plot_fundamental_diagram(data): + """ + Plot the Fundamental Diagram based on collected data. + + Parameters: + data (list): A list of dictionaries containing density, flow, and average speed values. + """ + densities = [entry["density"] for entry in data] + flows = [entry["flow"] for entry in data] + avg_speeds = [entry["avg_speed"] for entry in data] fig, ax1 = plt.subplots() - ax1.set_xlabel("Density (agents/unit area)") - ax1.set_ylabel("Speed (units/time)", color="blue") - ax1.plot(densities, speeds, label="Speed", color="blue") - ax1.tick_params(axis="y", labelcolor="blue") + # Plot Flow vs. Density + ax1.plot(densities, flows, 'b-o', label="Flow") + ax1.set_xlabel("Density (agents/m^2)") + ax1.set_ylabel("Flow (agents/s)", color="blue") + ax1.tick_params(axis='y', labelcolor="blue") - ax2 = ax1.twinx() # instantiate a second y-axis that shares the same x-axis - ax2.set_ylabel("Flow (agents/unit time)", color="red") - ax2.plot(densities, flows, label="Flow", color="red") - ax2.tick_params(axis="y", labelcolor="red") + # Plot Avg Speed vs. Density + ax2 = ax1.twinx() + ax2.plot(densities, avg_speeds, 'r-s', label="Average Speed") + ax2.set_ylabel("Average Speed (m/s)", color="red") + ax2.tick_params(axis='y', labelcolor="red") - fig.tight_layout() # ensure everything fits without overlap + fig.tight_layout() plt.title("Fundamental Diagram") plt.show() + + + def create_logfile(self): path = f"gridlog-{self.__hash__()}.txt" if(os.path.isfile(path)): @@ -670,7 +701,7 @@ def plot_grid_state(self, timestep): self.ax.clear() self.ax.imshow(data, cmap=cmap, norm=norm) - + self.ax.set_title(f"Grid State at Timestep {timestep}") self.ax.set_xlabel("Columns") diff --git a/Application/run.py b/Application/run.py index f414746..a9f73de 100644 --- a/Application/run.py +++ b/Application/run.py @@ -4,30 +4,20 @@ import numpy as np from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4 -def plot_fundamental_diagram(densities, speeds, flows): - """ - Plot density vs. speed and flow for ROI-based analysis. - """ - import matplotlib.pyplot as plt - fig, ax1 = plt.subplots() - - ax1.set_xlabel("Density (agents/m²)") - ax1.set_ylabel("Speed (m/s)", color="blue") - ax1.plot(densities, speeds, label="Speed", color="blue") - ax1.tick_params(axis="y", labelcolor="blue") - - ax2 = ax1.twinx() # instantiate a second y-axis that shares the same x-axis - ax2.set_ylabel("Flow (agents/s)", color="red") - ax2.plot(densities, flows, label="Flow", color="red") - ax2.tick_params(axis="y", labelcolor="red") - - fig.tight_layout() # ensure everything fits without overlap - plt.title("Fundamental Diagram at Exit") - plt.show() -grid = RiMEA9(2, "dijkstra") +room_height = 20 +room_length = 30 +frameSize = 5 +height = room_height + 2 * frameSize +length = room_length + 2 * frameSize +grid, door_cells = RiMEA9(1, "dijkstra") visualization = Visualization(grid) - +region = [] +for cell in door_cells: + row,col = cell.row, cell.col + print(row, col) + region.append([row, col]) +#region = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) agent_count_list = [] average_distance_list = [] average_speed_list = [] @@ -37,15 +27,21 @@ def plot_fundamental_diagram(densities, speeds, flows): flows = [] timesteps = 10000 grid.update_distance_maps() -for i in range(800): +agents_crossed = {} # Dictionary to track agents crossing the boundary +for i in range(20): grid.update(target_list=grid.target_cells, timestep=i) + #visualization.plot_grid_state(i) grid.plot_grid_state(i) - plt.pause(0.01) - # density, speed, flow = grid.calculate_density_speed_flow_in_rectangular_roi(10, 8, 12, 8) - #densities.append(density) - #speeds.append(speed) - #flows.append(flow) + plt.pause(1) + + + # Calculate density, speed, and flow for the door region + fd_results = grid.calculate_fundamental_diagram(grid, door_cells, i, agents_crossed, region) + # Store results for plotting + + # Update previous positions for the next timestep + agent_count = len(grid.agents) agent_count_list.append(agent_count) @@ -76,37 +72,37 @@ def plot_fundamental_diagram(densities, speeds, flows): average_distance_list.append(total_distance_to_target / len(grid.agents)) else: average_distance_list.append(np.nan) -plot_fundamental_diagram(densities, speeds, flows) -plt.figure(figsize=(10,5)) - -plt.subplot(1, 4, 1) -plt.plot(agent_count_list) -plt.title('Anzahl Agenten über Zeit') -plt.xlabel('Zeitschritt') -plt.ylabel('Anzahl Agenten') +#plot_fundamental_diagram_with_dual_axis(densities, speeds, flows) +#plt.figure(figsize=(10,5)) -plt.subplot(1, 4, 2) -plt.plot(average_distance_list) -plt.title('Mittlere Distanz Agenten zum Ziel') -plt.xlabel('Zeitschritt') -plt.ylabel('Distanz') +#plt.subplot(1, 4, 1) +#plt.plot(agent_count_list) +#plt.title('Anzahl Agenten über Zeit') +#plt.xlabel('Zeitschritt') +#plt.ylabel('Anzahl Agenten') -plt.subplot(1, 4, 3) -plt.plot(average_speed_list) -plt.title('Mittlere Geschwindigkeit der Agenten') -plt.xlabel('Zeitschritt') -plt.ylabel('Geschwindigkeit') +#plt.subplot(1, 4, 2) +#plt.plot(average_distance_list) +#plt.title('Mittlere Distanz Agenten zum Ziel') +#plt.xlabel('Zeitschritt') +#plt.ylabel('Distanz') -plt.subplot(1, 4, 4) -if len(density_list) > len(average_speed_list): - plt.plot(density_list[:-1], average_speed_list) -else: - plt.plot(density_list, average_speed_list[:len(density_list)]) -plt.title('Fundamental Diagram of Traffic Flow') -plt.xlabel('Dichte') -plt.ylabel('Mittlere Geschwindigkeit') +#plt.subplot(1, 4, 3) +#plt.plot(average_speed_list) +#plt.title('Mittlere Geschwindigkeit der Agenten') +#plt.xlabel('Zeitschritt') +#plt.ylabel('Geschwindigkeit') -plt.tight_layout() -plt.show() +#plt.subplot(1, 4, 4) +#if len(density_list) > len(average_speed_list): +# plt.plot(density_list[:-1], average_speed_list) +#else: +# plt.plot(density_list, average_speed_list[:len(density_list)]) +#plt.title('Fundamental Diagram of Traffic Flow') +#plt.xlabel('Dichte') +#plt.ylabel('Mittlere Geschwindigkeit') +# +#plt.tight_layout() +#plt.show() # visualization.animate_grid_states(timesteps) \ No newline at end of file diff --git a/Application/tests.py b/Application/tests.py index a848dab..5419653 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -49,66 +49,59 @@ def RiMEA9(Doors, movement_method): room_height = 20 room_length = 30 frameSize = 5 - height = room_height+2*frameSize - length = room_length+2*frameSize + height = room_height + 2 * frameSize + length = room_length + 2 * frameSize grid = Grid(height=height, length=length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5, movement_method=movement_method) - # place obstacles and targets - vertical_placement = grid.select_area_by_coordinates(frameSize, frameSize, frameSize, height-frameSize) # Obstacle-Zellen links - vertical_placement += grid.select_area_by_coordinates(length-frameSize, frameSize, length-frameSize, height-frameSize) # Obstacle-Zellen rechts - horizontal_placement = grid.select_area_by_coordinates(frameSize, frameSize, length-frameSize, frameSize) # Obstacle-Zellen oben - horizontal_placement += grid.select_area_by_coordinates(frameSize, height-frameSize, length-frameSize, height-frameSize) # Obstacle-Zellen unten - - obstacles = vertical_placement+horizontal_placement + # Place obstacles and targets + vertical_placement = grid.select_area_by_coordinates(frameSize, frameSize, frameSize, height - frameSize) + vertical_placement += grid.select_area_by_coordinates(length - frameSize, frameSize, length - frameSize, height - frameSize) + horizontal_placement = grid.select_area_by_coordinates(frameSize, frameSize, length - frameSize, frameSize) + horizontal_placement += grid.select_area_by_coordinates(frameSize, height - frameSize, length - frameSize, height - frameSize) + obstacles = vertical_placement + horizontal_placement for cell in obstacles: grid.place_obstacle(cell.row, cell.col) if Doors == 1: - door_cells = grid.select_area_by_coordinates(frameSize+4, frameSize, frameSize+5, frameSize) - print(len(door_cells)) - target_placement = grid.select_area_by_coordinates(frameSize+4, 0, frameSize+5, 0) + door_cells = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) + target_placement = grid.select_area_by_coordinates(frameSize + 4, 0, frameSize + 5, 0) elif Doors == 2: - door_cells = grid.select_area_by_coordinates(frameSize+4, frameSize, frameSize+5, frameSize) - door_cells += grid.select_area_by_coordinates(length-frameSize-5, height-frameSize, length-frameSize-4, height-frameSize) - target_placement = grid.select_area_by_coordinates(frameSize+4, 0, frameSize+5, 0) - target_placement += grid.select_area_by_coordinates(length-frameSize-5, height, length-frameSize-4, height) + door_cells = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) + door_cells += grid.select_area_by_coordinates(length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) + target_placement = grid.select_area_by_coordinates(frameSize + 4, 0, frameSize + 5, 0) + target_placement += grid.select_area_by_coordinates(length - frameSize - 5, height, length - frameSize - 4, height) elif Doors == 3: - door_cells = grid.select_area_by_coordinates(frameSize+4, frameSize, frameSize+5, frameSize) - door_cells += grid.select_area_by_coordinates(length-frameSize-5, height-frameSize, length-frameSize-4, height-frameSize) - door_cells += grid.select_area_by_coordinates(length-frameSize-5, frameSize, length-frameSize-4, frameSize) - target_placement = grid.select_area_by_coordinates(frameSize+4, 0, frameSize+5, 0) - target_placement += grid.select_area_by_coordinates(length-frameSize-5, height, length-frameSize-4, height) - target_placement += grid.select_area_by_coordinates(length-frameSize-5, 0, length-frameSize-4, 0) + door_cells = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) + door_cells += grid.select_area_by_coordinates(length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) + door_cells += grid.select_area_by_coordinates(length - frameSize - 5, frameSize, length - frameSize - 4, frameSize) + target_placement = grid.select_area_by_coordinates(frameSize + 4, 0, frameSize + 5, 0) + target_placement += grid.select_area_by_coordinates(length - frameSize - 5, height, length - frameSize - 4, height) + target_placement += grid.select_area_by_coordinates(length - frameSize - 5, 0, length - frameSize - 4, 0) else: - # hiermit wird abgefangen, falls Zahlen ausserhalb der Menge 1-3 eingegeben werden - dann default 4 - door_cells = grid.select_area_by_coordinates(frameSize+4, frameSize, frameSize+5, frameSize) - door_cells += grid.select_area_by_coordinates(length-frameSize-5, height-frameSize, length-frameSize-4, height-frameSize) - door_cells += grid.select_area_by_coordinates(length-frameSize-5, frameSize, length-frameSize-4, frameSize) - door_cells += grid.select_area_by_coordinates(frameSize+4, height-frameSize, frameSize+5, height-frameSize) - target_placement = grid.select_area_by_coordinates(frameSize+4, 0, frameSize+5, 0) - target_placement += grid.select_area_by_coordinates(length-frameSize-5, height, length-frameSize-4, height) - target_placement += grid.select_area_by_coordinates(length-frameSize-5, 0, length-frameSize-4, 0) - target_placement += grid.select_area_by_coordinates(frameSize+4, height, frameSize+5, height) + door_cells = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) + door_cells += grid.select_area_by_coordinates(length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) + door_cells += grid.select_area_by_coordinates(length - frameSize - 5, frameSize, length - frameSize - 4, frameSize) + door_cells += grid.select_area_by_coordinates(frameSize + 4, height - frameSize, frameSize + 5, height - frameSize) + target_placement = grid.select_area_by_coordinates(frameSize + 4, 0, frameSize + 5, 0) + target_placement += grid.select_area_by_coordinates(length - frameSize - 5, height, length - frameSize - 4, height) + target_placement += grid.select_area_by_coordinates(length - frameSize - 5, 0, length - frameSize - 4, 0) + target_placement += grid.select_area_by_coordinates(frameSize + 4, height, frameSize + 5, height) for cell in target_placement: grid.place_target(cell.row, cell.col) - # place doors for cell in door_cells: grid.place_empty_cell(cell.row, cell.col) - # place agents - potential_agent_cells = grid.select_area_by_coordinates(frameSize+2, frameSize+2, length-frameSize-2, height-frameSize-2) - + potential_agent_cells = grid.select_area_by_coordinates(frameSize + 2, frameSize + 2, length - frameSize - 2, height - frameSize - 2) for cell in potential_agent_cells: chance = random.randint(0, 10) - if chance <= 5 and len(grid.agents)<=1000: + if chance <= 5 and len(grid.agents) <= 1000: grid.place_agent(cell.row, cell.col) - - return grid + return grid, door_cells # Anpassungen aus den anderen Maps müssen für RiMEA4 noch übernommen werden From 66d1e979c00a79c1af08a7504a19efff4402c052 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sat, 28 Dec 2024 22:00:39 +0100 Subject: [PATCH 09/26] WIP am Fundamentaldiagram --- Application/Grid.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index 9a1526d..8789fe6 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -606,20 +606,26 @@ def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, regi """ # Step 1: Calculate Flow (Agents crossing the boundary in this timestep) current_crossed_agents = [] - for row, col in boundary: - cell = grid.grid[row][col] - if isinstance(cell, Agent) and cell.id not in agents_crossed: - current_crossed_agents.append(cell) - agents_crossed[cell.id] = timestep # Track the agent's crossing + for cell in boundary: + b_cell = self.grid[cell.row][cell.col] + if isinstance(b_cell, Agent) and b_cell.id not in agents_crossed: + current_crossed_agents.append(b_cell) + agents_crossed[b_cell.id] = timestep # Track the agent's crossing flow = len(current_crossed_agents) # Number of agents crossing the boundary # Step 2: Calculate Density (Agents per square meter in the region in front of the boundary) - region_in_front = grid.select_area_by_coordinates(*region_coordinates) - agent_count = sum( - 1 for cell in region_in_front if isinstance(cell, Agent) - ) - region_area = len(region_in_front) * (grid.cell_size ** 2) # Area of region in m^2 + # region_in_front = self.select_area_by_coordinates(*region_coordinates) + agent_count = 0 + # agent_count = sum( + # 1 for cell in region_coordinates if isinstance(cell, Agent) + # ) + for cell in region_coordinates: + if isinstance(self.grid[cell.row][cell.col], Agent): + agent_count += 1 + print(agent_count) + region_area = len(region_coordinates) * (self.cell_size ** 2) # Area of region in m^2 + print(region_area) density = agent_count / region_area if region_area > 0 else 0 # Step 3: Calculate Average Speed (of agents crossing the boundary) @@ -638,7 +644,7 @@ def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, regi print(f"Timestep {timestep}: Density={density:.2f}, Flow={flow:.2f}, AvgSpeed={avg_speed:.2f}") return results - def plot_fundamental_diagram(data): + def plot_fundamental_diagram(self, data): """ Plot the Fundamental Diagram based on collected data. From c5ff376257f1eb480832c5349dc6aa34efad79b2 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sat, 28 Dec 2024 22:00:46 +0100 Subject: [PATCH 10/26] WIP am Fundamentaldiagram --- Application/run.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Application/run.py b/Application/run.py index a9f73de..99116ac 100644 --- a/Application/run.py +++ b/Application/run.py @@ -10,13 +10,11 @@ frameSize = 5 height = room_height + 2 * frameSize length = room_length + 2 * frameSize -grid, door_cells = RiMEA9(1, "dijkstra") +grid, door_cells = RiMEA9(2, "dijkstra") visualization = Visualization(grid) -region = [] -for cell in door_cells: - row,col = cell.row, cell.col - print(row, col) - region.append([row, col]) +#region_cord = (frameSize + 4, frameSize+1, frameSize + 5, frameSize+1) +#region_2_cord = (length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) + #region = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) agent_count_list = [] average_distance_list = [] @@ -25,6 +23,8 @@ densities = [] speeds = [] flows = [] +fundamental_data = [] +fundamental_data2 = [] timesteps = 10000 grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary @@ -37,7 +37,9 @@ # Calculate density, speed, and flow for the door region - fd_results = grid.calculate_fundamental_diagram(grid, door_cells, i, agents_crossed, region) + fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, door_cells) + + fundamental_data.append(fd_results) # Store results for plotting # Update previous positions for the next timestep @@ -71,7 +73,9 @@ average_distance_list.append(total_distance_to_target / len(grid.agents)) else: - average_distance_list.append(np.nan) + average_distance_list.append(np.nan) + +grid.plot_fundamental_diagram(fundamental_data) #plot_fundamental_diagram_with_dual_axis(densities, speeds, flows) #plt.figure(figsize=(10,5)) From b8f1be8a9c664ee220a8626c93ff213ce4350363 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 14:15:03 +0100 Subject: [PATCH 11/26] Fundamentaldiagram fast fertig --- Application/Grid.py | 38 ++++----- Application/run.py | 138 +++++++++++++++++++++++++-------- Application/test_components.py | 62 +++++++++++++++ 3 files changed, 188 insertions(+), 50 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index 8789fe6..2fd16b3 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -590,7 +590,7 @@ def get_agent_velocities(self): # plt.title("Fundamental Diagram") # plt.show() - def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, region_coordinates): + def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, area): """ Calculate and plot the Fundamental Diagram. @@ -599,35 +599,36 @@ def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, regi boundary (list): A list of (row, col) tuples representing the boundary. timestep (int): The current simulation timestep. agents_crossed (dict): A dictionary tracking agents that crossed the boundary. - region_coordinates (tuple): Coordinates defining the region in front of the boundary as (x1, y1, x2, y2). + Returns: dict: Contains density, flow, and average speed for the timestep. """ # Step 1: Calculate Flow (Agents crossing the boundary in this timestep) current_crossed_agents = [] - for cell in boundary: - b_cell = self.grid[cell.row][cell.col] - if isinstance(b_cell, Agent) and b_cell.id not in agents_crossed: - current_crossed_agents.append(b_cell) - agents_crossed[b_cell.id] = timestep # Track the agent's crossing + for coordinates in boundary: + cell = self.grid[coordinates.row][coordinates.col] + if isinstance(cell, Agent) and cell.id not in agents_crossed: + current_crossed_agents.append(cell) + agents_crossed[cell.id] = timestep # Track the agent's crossing flow = len(current_crossed_agents) # Number of agents crossing the boundary - # Step 2: Calculate Density (Agents per square meter in the region in front of the boundary) + # Step 2: Calculate Density (Agents per square meter in the Room or Hallway) # region_in_front = self.select_area_by_coordinates(*region_coordinates) agent_count = 0 - # agent_count = sum( - # 1 for cell in region_coordinates if isinstance(cell, Agent) - # ) - for cell in region_coordinates: - if isinstance(self.grid[cell.row][cell.col], Agent): - agent_count += 1 - print(agent_count) - region_area = len(region_coordinates) * (self.cell_size ** 2) # Area of region in m^2 - print(region_area) - density = agent_count / region_area if region_area > 0 else 0 + agent_count = sum( + 1 for cell in area if isinstance(cell, Agent) + ) + print(f"agent count{agent_count}") + area_squared = sum( + self.cell_size**2 for cell in area if(cell.cell_size>0) + ) + # print(area_squared) + # Area of region in m^2 + density = agent_count / area_squared if area_squared > 0 else 0 + print(f"density:{density}") # Step 3: Calculate Average Speed (of agents crossing the boundary) avg_speed = ( sum(agent.velocity for agent in current_crossed_agents) / len(current_crossed_agents) @@ -639,6 +640,7 @@ def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, regi "density": density, "flow": flow, "avg_speed": avg_speed, + "timestep": timestep, } print(f"Timestep {timestep}: Density={density:.2f}, Flow={flow:.2f}, AvgSpeed={avg_speed:.2f}") diff --git a/Application/run.py b/Application/run.py index 99116ac..564214b 100644 --- a/Application/run.py +++ b/Application/run.py @@ -10,7 +10,9 @@ frameSize = 5 height = room_height + 2 * frameSize length = room_length + 2 * frameSize -grid, door_cells = RiMEA9(2, "dijkstra") +grid, door_cells = RiMEA9(1, "dijkstra") + + visualization = Visualization(grid) #region_cord = (frameSize + 4, frameSize+1, frameSize + 5, frameSize+1) #region_2_cord = (length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) @@ -28,54 +30,126 @@ timesteps = 10000 grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary -for i in range(20): +for i in range(40): grid.update(target_list=grid.target_cells, timestep=i) + #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) + area = grid.select_area_by_coordinates(frameSize, frameSize, length - frameSize, height - frameSize) + if i == 0: + initial_count = len(grid.agents) + print(f"init{initial_count}") #visualization.plot_grid_state(i) grid.plot_grid_state(i) plt.pause(1) - - - # Calculate density, speed, and flow for the door region - fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, door_cells) - - fundamental_data.append(fd_results) + print(f"len_agentscrossed:{len(agents_crossed)}") + + # Calculate density, speed, and flow: Set Boundarys to be doorcells (as defined in Rimea), pass current timestep, already_crossed agents and the area for density calculation + #print(agents_crossed) + if len(agents_crossed) <= initial_count: + fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) + print(type(fd_results)) + fundamental_data.append(fd_results) + #print(fundamental_data) # Store results for plotting + print(type(fundamental_data)) + # Update previous positions for the next timestep agent_count = len(grid.agents) agent_count_list.append(agent_count) - # berechne dichte - total_area = grid.length * grid.height - density = agent_count / total_area - density_list.append(density) + + if agent_count == 0: print("All agents have reached their targets. Stopping simulation.") break - # berechne mittlere Geschwindigkeit - total_speed = sum(agent.velocity for agent in grid.agents) - if agent_count > 0: - average_speed = total_speed / agent_count - else: - average_speed = 0 # Avoid division by zero - average_speed_list.append(average_speed) - - # mittlere distanz von Agenten zu Ziel - total_distance_to_target = 0 - if len(grid.agents) > 0: - for agent in grid.agents: - target = agent.find_target(grid.target_cells) - total_distance_to_target += agent.euclidean_distance_to(grid.grid[target[0]][target[1]]) - - average_distance_list.append(total_distance_to_target / len(grid.agents)) - else: - average_distance_list.append(np.nan) - -grid.plot_fundamental_diagram(fundamental_data) + + + +#Dataprep before plotting + + +#grid.plot_fundamental_diagram(fundamental_data) + def plot_fundamental_diagram(data): + """Generate two separate plots from fundamental diagram data.""" + + # Extract data + timesteps = [entry["timestep"] for entry in data if entry["flow"]>0] + densities = [entry["density"] for entry in data if entry["flow"]>0] + avg_speeds = [entry["avg_speed"] for entry in data if entry["flow"]>0] + + # Plot 1: Density over time + plt.figure(figsize=(10, 6)) + plt.plot(timesteps, densities, marker="o", label="Density over time") + plt.title("Density Over Time") + plt.xlabel("Timestep") + plt.ylabel("Density (agents/m^2)") + plt.grid(True) + plt.legend() + plt.savefig("Density_over_time") + plt.show() + + # Plot 2: Average speed relative to density + plt.figure(figsize=(10, 6)) + plt.plot(densities, avg_speeds, marker="s", label="Avg Speed vs. Density", color="red") + plt.title("Average Speed Relative to Density") + plt.xlabel("Density (agents/m^2)") + plt.ylabel("Average Speed (m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Speed_relative_to_density") + plt.show() + +plot = plot_fundamental_diagram(fundamental_data) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + #plot_fundamental_diagram_with_dual_axis(densities, speeds, flows) #plt.figure(figsize=(10,5)) diff --git a/Application/test_components.py b/Application/test_components.py index 5d48dd2..372a907 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -2,6 +2,7 @@ from Grid import Grid from Cell import Cell, SpawnCell, TargetCell, Agent, ObstacleCell from concurrent.futures import ThreadPoolExecutor +import numpy as np #Werden wir später noch in einen dedizierten Testordner verschieben class TestAgentBehavior(unittest.TestCase): @@ -254,7 +255,68 @@ def test_flood_fill_distance_map(self): msg=f"Mismatch in Flood Fill distance map for target ({target_row}, {target_col}) at ({row}, {col})" ) grid.plot_distance_map(distance_map) +class TestPenalties(unittest.TestCase): + def setUp(self): + """Set up a grid for testing.""" + # Create a 5x5 grid with cell size 1.0 + self.grid = Grid( + length=5.0, height=5.0, + spawn_cells=[], + target_cells=[(4, 4)], + obstacle_cells=[], + cell_size=1.0 + ) + # Place a target at (4, 4) + self.grid.place_target(4, 4) + # Place an agent at (0, 0) + self.grid.place_agent(0, 0) + self.agent = self.grid.agents[0] + + def test_precomputed_penalties(self): + """Test that precomputed social penalties are calculated correctly.""" + # Place multiple agents in the grid + self.grid.place_agent(1, 1) + self.grid.place_agent(2, 2) + + # Compute social penalties using the precomputed method + penalties = self.grid.compute_social_penalties() + + # Ensure penalties are calculated for each agent + self.assertEqual(len(penalties), len(self.grid.agents)) + + # Verify that penalties are non-zero and consistent with agent positions + positions = np.array([[agent.row, agent.col] for agent in self.grid.agents]) + for i, penalty in enumerate(penalties): + self.assertGreater(penalty, 0) + + # Check penalty consistency with distances + deltas = positions - positions[i] + distances = np.linalg.norm(deltas, axis=1) + within_cutoff = distances <= 2.0 + + # Penalty should be influenced by nearby agents within cutoff distance + expected_penalty = sum( + np.exp(-(distances[j] ** 2) / (2 * 0.5 ** 2)) + for j in range(len(distances)) + if within_cutoff[j] and i != j + ) + 0.5 # Include stay penalty + + self.assertAlmostEqual(penalty, expected_penalty, places=2) + + def test_update_with_precomputed_penalties(self): + """Test that the update method uses precomputed penalties correctly.""" + # Place a second agent near the first agent + self.grid.place_agent(0, 1) + self.grid.update_distance_maps() + # Run the update method + self.grid.update(self.grid.target_cells, timestep=1) + + # Verify penalties were used in movement decisions + precomputed_penalties = self.grid.compute_social_penalties() + for i, agent in enumerate(self.grid.agents): + penalty = precomputed_penalties[i] + self.assertGreater(penalty, 0) class TestAgentSocialPenalty(unittest.TestCase): def setUp(self): From 9c8c6fa1a91a56173ccd2c1d0cd49fdf584477b4 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 15:09:49 +0100 Subject: [PATCH 12/26] Rimea4 Map von #Lima und Gruppenvisualisierung von #Sil --- Application/Cell.py | 2 ++ Application/Grid.py | 41 ++++++++++++++++++++++++++++--------- Application/run.py | 48 ++++++++++++++++++++++++++++---------------- Application/tests.py | 28 +++++++++++++++++--------- 4 files changed, 83 insertions(+), 36 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 92d02ef..2eec6ac 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -141,6 +141,7 @@ def spawn_agents(self, grid, max_agents): cell = valid_neighbors.pop(0) row, col = cell.row, cell.col agent = Agent(row, col, cell_size=self.cell_size) + agent.group = random.choice([0, 1]) # NEUE ZEILE grid.grid[row][col] = agent grid.agents.append(agent) @@ -170,6 +171,7 @@ def __init__(self, row, col, cell_size): self.route = [] self.movement_range = self.velocity self.target = None + self.group = None def log_state(self, timestep, log_file="logs/agent_states.log"): """Log the agent's state to a file.""" diff --git a/Application/Grid.py b/Application/Grid.py index 2fd16b3..99dd9a1 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -695,26 +695,49 @@ def plot_grid_state(self, timestep): data = [[cell.state for cell in row] for row in self.grid.grid] custom_colors = { - 0: 'white', # Empty cells - 1: 'yellow', # Border cells - 2: 'green', # Spawn cells - 3: 'red', # Target cells - 4: 'gray', # Obstacles - 47: 'blue' # Agents + 0: 'white', + 1: 'yellow', + 2: 'green', + 3: 'red', + 4: 'gray' } + agent_color_map = {0: 'blue', 1: 'red'} # Map agent groups to colors + cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) - bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] + bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] norm = mcolors.BoundaryNorm(bounds, cmap.N) - self.ax.clear() self.ax.imshow(data, cmap=cmap, norm=norm) - + # Create a new array to hold the agent colors + agent_data = np.zeros((self.grid.rows, self.grid.cols), dtype=int) + for row in range(self.grid.rows): + for col in range(self.grid.cols): + cell = self.grid.grid[row][col] + if isinstance(cell, Agent): + # Map the agent's group to a unique value + agent_data[row, col] = 47 + cell.group + + # Update the custom_colors dictionary with the new values for agents + custom_colors[47] = 'black' # Default color for agents (not used here) + custom_colors[48] = agent_color_map[0] # Color for group 0 agents + custom_colors[49] = agent_color_map[1] # Color for group 1 agents + + # Update the bounds and norm to include the new values + cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) + bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] + norm = mcolors.BoundaryNorm(bounds, cmap.N) + + # Plot the agent data using the updated colormap + self.ax.imshow(agent_data, cmap=cmap, norm=norm, alpha=0.8) # Alpha for layering + self.ax.set_title(f"Grid State at Timestep {timestep}") self.ax.set_xlabel("Columns") self.ax.set_ylabel("Rows") + plt.pause(0.1) + def animate_grid_states(self, timesteps): def update(frame): self.grid.update(target_list=self.grid.target_cells, timestep=frame) diff --git a/Application/run.py b/Application/run.py index 564214b..fbb97fe 100644 --- a/Application/run.py +++ b/Application/run.py @@ -5,12 +5,9 @@ from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4 -room_height = 20 -room_length = 30 -frameSize = 5 -height = room_height + 2 * frameSize -length = room_length + 2 * frameSize -grid, door_cells = RiMEA9(1, "dijkstra") + +grid, door_cells, roi = RiMEA4( "dijkstra") +print(roi) visualization = Visualization(grid) @@ -19,27 +16,22 @@ #region = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) agent_count_list = [] -average_distance_list = [] -average_speed_list = [] -density_list = [] -densities = [] -speeds = [] -flows = [] + fundamental_data = [] -fundamental_data2 = [] + timesteps = 10000 grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary for i in range(40): grid.update(target_list=grid.target_cells, timestep=i) #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) - area = grid.select_area_by_coordinates(frameSize, frameSize, length - frameSize, height - frameSize) + area = grid.select_area_by_coordinates(roi[0], roi[1], roi[2], roi[3]) if i == 0: initial_count = len(grid.agents) print(f"init{initial_count}") - #visualization.plot_grid_state(i) - grid.plot_grid_state(i) + visualization.plot_grid_state(i) + #grid.plot_grid_state(i) plt.pause(1) print(f"len_agentscrossed:{len(agents_crossed)}") @@ -76,10 +68,11 @@ def plot_fundamental_diagram(data): """Generate two separate plots from fundamental diagram data.""" - # Extract data + # Extract data, remove entries where flow was 0 (no agents crossing yet) timesteps = [entry["timestep"] for entry in data if entry["flow"]>0] densities = [entry["density"] for entry in data if entry["flow"]>0] avg_speeds = [entry["avg_speed"] for entry in data if entry["flow"]>0] + flows = [entry["flow"] for entry in data if entry["flow"]>0] # Plot 1: Density over time plt.figure(figsize=(10, 6)) @@ -92,6 +85,27 @@ def plot_fundamental_diagram(data): plt.savefig("Density_over_time") plt.show() + # Plot 2: Flow over time + plt.figure(figsize=(10, 6)) + plt.plot(timesteps, flows, marker="o", label="Density over time") + plt.title("Flow Over Time") + plt.xlabel("Timestep") + plt.ylabel("Flow (agents/m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Flow_over_time") + plt.show() + # Plot 3: Flow over time + plt.figure(figsize=(10, 6)) + plt.plot(densities, flows, marker="o", label="Density over time") + plt.title("Flows over Densities") + plt.xlabel("Densities") + plt.ylabel("Flow (agents/m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Flow_over_densities") + plt.show() + # Plot 2: Average speed relative to density plt.figure(figsize=(10, 6)) plt.plot(densities, avg_speeds, marker="s", label="Avg Speed vs. Density", color="red") diff --git a/Application/tests.py b/Application/tests.py index 5419653..19bbf1e 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -96,28 +96,36 @@ def RiMEA9(Doors, movement_method): grid.place_empty_cell(cell.row, cell.col) potential_agent_cells = grid.select_area_by_coordinates(frameSize + 2, frameSize + 2, length - frameSize - 2, height - frameSize - 2) + roi = (frameSize, frameSize, length - frameSize, height - frameSize) for cell in potential_agent_cells: chance = random.randint(0, 10) if chance <= 5 and len(grid.agents) <= 1000: grid.place_agent(cell.row, cell.col) - return grid, door_cells + return grid, door_cells, roi -# Anpassungen aus den anderen Maps müssen für RiMEA4 noch übernommen werden -def RiMEA4(): - height = 10 - length = 150 - obstacle_cells = [(4, 24), (5, 24), (4, 25), (5, 25), (4, 74), (5, 74), (6, 74), (7, 74), (4, 75), (5, 75), (6, 75), (7, 75)] +def RiMEA4(movement_method): + height = 10 + length = 100 + warm_up = 25 + corridor_length = warm_up + length + grid = Grid(height=height, length=corridor_length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=1, movement_method=movement_method) - grid = Grid(height=height, length=length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5) + # Door_cells sind in diesem Fall eine Zelle vor Ende des Ganges + door_cells = grid.select_area_by_coordinates(corridor_length-1, 0, corridor_length-1, height) + obstacle_placement = grid.select_area_by_coordinates(24, 4, 25, 5) + obstacle_placement += grid.select_area_by_coordinates(74, 4, 75, 7) for i in range(0, grid.rows-1): grid.place_spawn_cell(i, 0) grid.place_target(i, grid.cols-1) - for j in range(0, len(obstacle_cells)-1): - grid.place_obstacle(obstacle_cells[j][0], obstacle_cells[j][1]) + for cell in obstacle_placement: + grid.place_obstacle(cell.row, cell.col) - return grid \ No newline at end of file + for cell in door_cells: + grid.place_empty_cell(cell.row, cell.col) + roi = (0,0,corridor_length,height) + return grid, door_cells, roi \ No newline at end of file From 3e81c50c366b56c519b76cfb5455a8b72189d8e0 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 16:29:06 +0100 Subject: [PATCH 13/26] Rimea4 Map von #Lima und Gruppenvisualisierung von #Sil --- Application/Grid.py | 1 + Application/run.py | 28 +++++++++++----------------- Application/test_components.py | 3 +-- Application/tests.py | 31 ++++++++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index 99dd9a1..f3f5fe6 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -134,6 +134,7 @@ def place_agent(self, row,col): """Place an agent at a specific position on the grid""" #row, col = self.meter_to_rowcol(x, y) agent = Agent(row, col, cell_size=self.cell_size) + agent.group = random.choice([0, 1]) if isinstance(self.grid[row][col], Cell) and not isinstance(self.grid[row][col], TargetCell): self.grid[row][col] = agent self.agents.append(agent) diff --git a/Application/run.py b/Application/run.py index fbb97fe..5156a22 100644 --- a/Application/run.py +++ b/Application/run.py @@ -3,13 +3,14 @@ import matplotlib.pyplot as plt import numpy as np from Grid import Grid, Visualization -from tests import room_square, ChickenTest, RiMEA9, RiMEA4 - - -grid, door_cells, roi = RiMEA4( "dijkstra") +from tests import room_square, ChickenTest, RiMEA9, RiMEA4, Experiment +import matplotlib +matplotlib.use("Qt5Agg") +grid, door_cells, roi = RiMEA4("dijkstra") print(roi) - +#exp_grid, roi = Experiment("dijkstra") +#exp_grid.plot_grid_state(timestep=0) visualization = Visualization(grid) #region_cord = (frameSize + 4, frameSize+1, frameSize + 5, frameSize+1) #region_2_cord = (length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) @@ -22,7 +23,7 @@ timesteps = 10000 grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary -for i in range(40): +for i in range(800): grid.update(target_list=grid.target_cells, timestep=i) #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) area = grid.select_area_by_coordinates(roi[0], roi[1], roi[2], roi[3]) @@ -37,23 +38,16 @@ # Calculate density, speed, and flow: Set Boundarys to be doorcells (as defined in Rimea), pass current timestep, already_crossed agents and the area for density calculation #print(agents_crossed) - if len(agents_crossed) <= initial_count: - fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) - print(type(fd_results)) - fundamental_data.append(fd_results) + + fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) + print(type(fd_results)) + fundamental_data.append(fd_results) #print(fundamental_data) # Store results for plotting print(type(fundamental_data)) - - # Update previous positions for the next timestep - agent_count = len(grid.agents) agent_count_list.append(agent_count) - - - - if agent_count == 0: print("All agents have reached their targets. Stopping simulation.") break diff --git a/Application/test_components.py b/Application/test_components.py index 372a907..42c281e 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -503,5 +503,4 @@ def move_agent(agent): unittest.main() -if __name__ == '__main__': - unittest.main() + diff --git a/Application/tests.py b/Application/tests.py index 19bbf1e..4316163 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -128,4 +128,33 @@ def RiMEA4(movement_method): for cell in door_cells: grid.place_empty_cell(cell.row, cell.col) roi = (0,0,corridor_length,height) - return grid, door_cells, roi \ No newline at end of file + return grid, door_cells, roi + + +def Experiment(movement_method): + length = 8 + height = 16 + grid = Grid(height=height, length=length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5) + target_cells = grid.select_area_by_coordinates(3.5, 0, 4.5,1) + #Tische beim Einweiser + obstacle_cells = grid.select_area_by_coordinates(0, 16, 3, 16) + obstacle_cells += grid.select_area_by_coordinates(length-3, 16, length, 16) + #Säulen rechts und links + obstacle_cells += grid.select_area_by_coordinates(0, height/2, 0, height/2) + obstacle_cells += grid.select_area_by_coordinates(length, height/2, length, height/2) + spawn_cells = grid.select_area_by_coordinates(3.5, height,4.5 , height) + agent_pos_1 = grid.meter_to_rowcol(3.5, 8) + agent_pos_2 = grid.meter_to_rowcol(4.5, 8) + grid.place_agent(agent_pos_1[0], agent_pos_1[1]) + grid.place_agent(agent_pos_2[0], agent_pos_2[1]) + roi = (0,0,length,height) + for target in target_cells: + grid.place_agent(target.row, target.col) + for cell in obstacle_cells: + grid.place_obstacle(cell.row, cell.col) + for cell in spawn_cells: + grid.place_spawn_cell(cell.row, cell.col) + for agent in grid.agents: + #Set velocity to 0 so these Agents dont move + agent.velocity = 0 + return grid, roi \ No newline at end of file From e565626940d62de157e82ab63b42059f568ed665 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 18:52:23 +0100 Subject: [PATCH 14/26] Movement range und geschwindigkeitsreduktion sollten jetzt passen, Update() methode angepasst damit agent.movement_towards_target verwendet wird um agenten zu bewegen --- Application/Cell.py | 163 +++++++++++++++++++++++++--------- Application/Grid.py | 212 ++++++++++++++++++++++++++++---------------- Application/run.py | 5 +- 3 files changed, 256 insertions(+), 124 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 2eec6ac..b272e51 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -166,12 +166,16 @@ def __init__(self, row, col, cell_size): super().__init__(state=47, row=row, col=col, cell_size=cell_size) # Set the agent state as before self.arrived = False #velocity wird später verwendet um die Gehgeschwindigkeit der einzelnen Agenten zu verändern - self.velocity = random.uniform(0.75, 1.5) + self.velocity = random.uniform(0.5, 1.5) self.id = self.__hash__() self.route = [] self.movement_range = self.velocity self.target = None self.group = None + self.original_velocity = self.velocity + self.original_movement_range = self.movement_range + self.idle = None + def log_state(self, timestep, log_file="logs/agent_states.log"): """Log the agent's state to a file.""" @@ -300,9 +304,16 @@ def social_penalty(self, grid): # @log_decorator - def increase_movement_range(self): + def adjust_movement_range(self): #Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> - self.movement_range = self.velocity + self.movement_range + if self.idle: + self.movement_range = self.velocity + self.movement_range + print("MOVEMENT INCREASE") + elif not self.idle: + #We enter this part when the agent was able to move after being idle(for example stuck in crowd) --> We reset velocity and movement range to original states + self.movement_range = self.original_movement_range + print("MOVEMENT RESET") + self.velocity = self.original_velocity return self.movement_range @@ -347,6 +358,16 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor + #Set lowest possible velocity before reducing it any further to 0.40, graceful penalty only + if self.velocity >= 0.40: + if social_penalty > 0: + self.velocity = self.velocity - social_penalty / 3 + if self.velocity <= 0: + #we dont want negative velocities + self.velocity = abs(self.velocity) + self.movement_range = self.velocity + #print(f"new velocity for {agent_index} is {self.velocity}") + # Mark as arrived if moving onto the target if isinstance(grid.grid[best_move.row][best_move.col], TargetCell): @@ -356,73 +377,127 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): return (best_move.row, best_move.col) if best_move != self else None #Bewegungslogik # sure this method does make sense here from a architectural point of view? +# def movement_towards_target(self, grid): +# """ +# Decide movement based on target proximity and social penalties. +# """ +# if self.arrived: +# return +# +# # Determine the target and select the distance map +# target = self.find_target(grid.target_cells) +# if not target: +# return +# +# target_key = (target[0], target[1]) +# if grid.movement_method == "dijkstra": +# distance_map = grid.dijkstra_distance_maps.get(target_key) +# elif grid.movement_method == "floodfill": +# distance_map = grid.flood_fill_distance_maps.get(target_key) +# +# if not distance_map: +# return # Ensure the distance map is available +# +# # Get valid neighbors +# valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) +# valid_neighbors.append(self) # Include the current position as a fallback +# social_penalties = { +# (neighbor.row, neighbor.col): self.social_penalty(grid) +# for neighbor in valid_neighbors +# } +# +# # Determine the best move +# best_move = self +# smallest_cost = float('inf') +# +# for neighbor in valid_neighbors: +# # Cache distance to target for efficiency +# distance_to_target = distance_map[neighbor.row][neighbor.col] +# +# # Fetch precomputed social penalty +# penalty = social_penalties[(neighbor.row, neighbor.col)] +# +# # Add penalty for staying in place +# staying_penalty = 1.0 if neighbor == self else 0 +# +# total_cost = distance_to_target + penalty + staying_penalty +# +# if total_cost < smallest_cost: +# smallest_cost = total_cost +# best_move = neighbor +# +# # Check if the best move is onto the target +# if (best_move.row, best_move.col) == target: +# self.arrived = True +# grid.agents.remove(self) +# # Leave the target cell unchanged +# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) +# return +# +# # Move to the best neighbor +# #print(f"agent id{self.id} has {self.euclidean_distance_to(best_move)} distance to best_move and {self.movement_range} movement_range") +# if best_move != self and self.euclidean_distance_to(best_move)<=self.movement_range: +# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) +# grid.grid[best_move.row][best_move.col] = self +# self.row, self.col = best_move.row, best_move.col +# self.idle = False +# self.adjust_movement_range() +# elif best_move != self and self.euclidean_distance_to(best_move)>self.movement_range: +# self.idle = True +# print(f"agent{self.id} is idle: {self.idle}") +# self.adjust_movement_range() +# print(f"increase of movement range to {self.movement_range}") +# if smallest_cost == 0: # If reached the target +# self.arrived = True +# grid.agents.remove(self) +# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) def movement_towards_target(self, grid): - """ - Decide movement based on target proximity and social penalties. - """ if self.arrived: return - # Determine the target and select the distance map target = self.find_target(grid.target_cells) - if not target: - return - - target_key = (target[0], target[1]) - if grid.movement_method == "dijkstra": - distance_map = grid.dijkstra_distance_maps.get(target_key) - elif grid.movement_method == "floodfill": - distance_map = grid.flood_fill_distance_maps.get(target_key) + #print(f"Agent {self.id} moving towards target {target}") - if not distance_map: - return # Ensure the distance map is available - - # Get valid neighbors valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) - valid_neighbors.append(self) # Include the current position as a fallback - social_penalties = { - (neighbor.row, neighbor.col): self.social_penalty(grid) - for neighbor in valid_neighbors - } + if not valid_neighbors: + #print(f"Agent {self.id} has no valid neighbors and is stuck.") + return - # Determine the best move best_move = self smallest_cost = float('inf') for neighbor in valid_neighbors: - # Cache distance to target for efficiency - distance_to_target = distance_map[neighbor.row][neighbor.col] - - # Fetch precomputed social penalty - penalty = social_penalties[(neighbor.row, neighbor.col)] - - # Add penalty for staying in place - staying_penalty = 1.0 if neighbor == self else 0 - - total_cost = distance_to_target + penalty + staying_penalty + if grid.movement_method =="dijkstra": + distance_to_target = grid.dijkstra_distance_maps[target][neighbor.row][neighbor.col] + elif grid.movement_method == "floodfill": + distance_to_target = grid.flood_fill_distance_maps[target][neighbor.row][neighbor.col] + total_cost = distance_to_target # Add penalties if needed if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor - - # Check if the best move is onto the target - if (best_move.row, best_move.col) == target: + if isinstance(grid.grid[best_move.row][best_move.col], TargetCell): self.arrived = True grid.agents.remove(self) # Leave the target cell unchanged grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) return - # Move to the best neighbor - if best_move != self: + if best_move != self and self.euclidean_distance_to(best_move) <= self.movement_range: grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) grid.grid[best_move.row][best_move.col] = self self.row, self.col = best_move.row, best_move.col + print(f"Agent {self.id} moved to ({self.row}, {self.col})") + self.idle = False + self.adjust_movement_range() + + elif best_move != self: + self.idle = True + self.adjust_movement_range() + + + - if smallest_cost == 0: # If reached the target - self.arrived = True - grid.agents.remove(self) - grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) diff --git a/Application/Grid.py b/Application/Grid.py index f3f5fe6..a4eee77 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -147,6 +147,29 @@ def is_cell_occupied(self,row,col): # row, col = self.meter_to_rowcol(x, y) cell = self.grid[row][col] return isinstance(cell, Agent) + def get_agent_positions(self): + """ + Extract the positions of all agents as a NumPy array. + Returns: + np.ndarray: Array of shape (num_agents, 2) with rows [row, col]. + """ + return np.array([[agent.row, agent.col] for agent in self.agents]) + + def get_agent_arrival_status(self): + """ + Extract whether each agent has arrived. + Returns: + np.ndarray: Boolean array of shape (num_agents,) indicating arrival status. + """ + return np.array([agent.arrived for agent in self.agents]) + + def get_agent_velocities(self): + """ + Extract the velocities of all agents. + Returns: + np.ndarray: Array of shape (num_agents,) with agent velocities. + """ + return np.array([agent.velocity for agent in self.agents]) #Helper Methode für Djkstra Algorithmus @@ -365,24 +388,12 @@ def update(self, target_list, timestep): agents_to_remove.append(agent) continue - new_position = agent.movement_decision(self, precomputed_penalties, i) - if new_position: - current_row, current_col = agent.row, agent.col - new_row, new_col = new_position - - # Update grid: Move the agent - if not isinstance(self.grid[new_row][new_col], TargetCell): - self.grid[new_row][new_col] = agent + # Call the agent's movement logic + print(f"Agent {agent.id} moving from ({agent.row}, {agent.col})") + agent.movement_towards_target(self) - # Restore the current cell - self.grid[current_row][current_col] = ( - TargetCell(current_row, current_col, self.cell_size) - if (current_row, current_col) in target_list - else Cell(current_row, current_col, self.cell_size) - ) - - # Update agent position - agent.row, agent.col = new_row, new_col + # Debugging + print(f"Agent {agent.id} now at ({agent.row}, {agent.col})") # Remove agents that have arrived for agent in agents_to_remove: @@ -404,46 +415,115 @@ def update(self, target_list, timestep): self.log_grid_state(timestep) +# def update(self, target_list, timestep): +# """ +# Update the grid by moving agents and handling arrivals. +# """ +# if timestep == 0: +# self.update_distance_maps() +# # Compute social penalties +# precomputed_penalties = self.compute_social_penalties() +# # List to track agents to remove +# agents_to_remove = [] +# for i, agent in enumerate(self.agents[:]): # Iterate over a copy of the agents list +# if agent.arrived: +# agents_to_remove.append(agent) +# continue +# new_position = agent.movement_decision(self, precomputed_penalties, i) +# if new_position: +# current_row, current_col = agent.row, agent.col +# new_row, new_col = new_position +# # Update grid: Move the agent +# if not isinstance(self.grid[new_row][new_col], TargetCell): +# self.grid[new_row][new_col] = agent +# # Restore the current cell +# self.grid[current_row][current_col] = ( +# TargetCell(current_row, current_col, self.cell_size) +# if (current_row, current_col) in target_list +# else Cell(current_row, current_col, self.cell_size) +# ) +# # Update agent position +# agent.row, agent.col = new_row, new_col +# # Remove agents that have arrived +# for agent in agents_to_remove: +# print(f"Removing agent {agent} from ({agent.row}, {agent.col})") +# self.agents.remove(agent) +# # Restore target cell explicitly +# if (agent.row, agent.col) in target_list: +# self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) +# else: +# self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) +# # Spawn new agents +# for row, col in self.spawn_cells: +# cell = self.grid[row][col] +# if isinstance(cell, SpawnCell): +# max_agents = 1 +# cell.spawn_agents(self, max_agents) +# self.log_grid_state(timestep) + #def update(self, target_list, timestep): +# """ +# Update the grid by moving agents and handling arrivals. +# """ +# if timestep == 0: +# self.update_distance_maps() + +# # Precompute penalties for agents +# precomputed_penalties = self.compute_social_penalties() + +# # List to track agents to remove +# agents_to_remove = [] + +# for agent in self.agents[:]: # Iterate over a copy of the agents list +# if agent.arrived: +# agents_to_remove.append(agent) +# continue + +# # Use the agent's own movement logic +# print(f"moving agent {agent.id}") +# agent.movement_towards_target(self) + +# # Remove agents that have arrived +# for agent in agents_to_remove: +# print(f"Removing agent {agent} from ({agent.row}, {agent.col})") +# self.agents.remove(agent) + +# # Restore target cell explicitly +# if (agent.row, agent.col) in target_list: +# self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) +# else: +# self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) + +# # Spawn new agents +# for row, col in self.spawn_cells: +# cell = self.grid[row][col] +# if isinstance(cell, SpawnCell): +# max_agents = 1 +# cell.spawn_agents(self, max_agents) + +# self.log_grid_state(timestep) + + + + + + + + + + + + + + + + def calculate_movement(self,agent): if not agent.arrived: # Store the agent's movement decision (current and next position) return (agent, agent.movement_decision(self)) # New method to compute move return None - def update_p(self, target_list, timestep): - """ - Parallelized update for agent movements with resolved self references. - """ - - # Helper function to calculate an agent's movement - def calculate_movement(agent, grid): - if not agent.arrived: - return (agent, agent.movement_decision(grid)) - return None - - # Step 1: Compute movements in parallel - move_decisions = [] - with ThreadPoolExecutor() as executor: - move_decisions = list( - filter( - None, - executor.map(lambda agent: calculate_movement(agent, self), self.agents), - ) - ) - - # Step 2: Apply movements sequentially to update the grid - for agent, new_position in move_decisions: - if new_position: - # Clear the agent's current position - self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, cell_size=self.cell_size) - # Move the agent to the new position - self.grid[new_position[0]][new_position[1]] = agent - agent.row, agent.col = new_position - - # Remove agents that arrived at their targets - self.agents = [agent for agent in self.agents if not agent.arrived] - - print(f"Timestep {timestep} complete. Active agents: {len(self.agents)}") + def update_distance_maps(self): """ @@ -501,12 +581,10 @@ def plot_distance_map(self, distance_map, title="Distance Map"): plt.ylabel("Rows") plt.show() - - - def plot_grid_state(grid, timestep): - #Plot Ausgabe für klarere Visualisierung, momentan noch über States für Farbwahl: Evtl besser mit cell.color? + def plot_grid_state(self, timestep): + # Plot Ausgabe für klarere Visualisierung, momentan noch über States für Farbwahl: Evtl besser mit cell.color? # Convert grid to a DataFrame for easy visualization - data = [[cell.state for cell in row] for row in grid.grid] + data = [[cell.state for cell in row] for row in self.grid] # Define a custom color map for the cell states custom_colors = { 0: 'white', # Empty cells @@ -541,29 +619,7 @@ def plot_grid_state(grid, timestep): - def get_agent_positions(self): - """ - Extract the positions of all agents as a NumPy array. - Returns: - np.ndarray: Array of shape (num_agents, 2) with rows [row, col]. - """ - return np.array([[agent.row, agent.col] for agent in self.agents]) - - def get_agent_arrival_status(self): - """ - Extract whether each agent has arrived. - Returns: - np.ndarray: Boolean array of shape (num_agents,) indicating arrival status. - """ - return np.array([agent.arrived for agent in self.agents]) - def get_agent_velocities(self): - """ - Extract the velocities of all agents. - Returns: - np.ndarray: Array of shape (num_agents,) with agent velocities. - """ - return np.array([agent.velocity for agent in self.agents]) # def plot_fundamental_diagram(self): # """ diff --git a/Application/run.py b/Application/run.py index 5156a22..6a75ff6 100644 --- a/Application/run.py +++ b/Application/run.py @@ -6,7 +6,7 @@ from tests import room_square, ChickenTest, RiMEA9, RiMEA4, Experiment import matplotlib matplotlib.use("Qt5Agg") -grid, door_cells, roi = RiMEA4("dijkstra") +grid, door_cells, roi = RiMEA9(2,"dijkstra") print(roi) #exp_grid, roi = Experiment("dijkstra") @@ -23,8 +23,9 @@ timesteps = 10000 grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary -for i in range(800): +for i in range(60): grid.update(target_list=grid.target_cells, timestep=i) + print("--------------------------------------------------------------------------------------------------") #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) area = grid.select_area_by_coordinates(roi[0], roi[1], roi[2], roi[3]) if i == 0: From 7602223cc953e4eb235152c5f3749a501433183a Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 21:25:01 +0100 Subject: [PATCH 15/26] Passing all unit tests, simulation looks fine also --- Application/Cell.py | 66 ++++----- Application/Grid.py | 6 +- Application/test_components.py | 237 +++++++++++---------------------- 3 files changed, 119 insertions(+), 190 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index b272e51..dca8797 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -166,14 +166,15 @@ def __init__(self, row, col, cell_size): super().__init__(state=47, row=row, col=col, cell_size=cell_size) # Set the agent state as before self.arrived = False #velocity wird später verwendet um die Gehgeschwindigkeit der einzelnen Agenten zu verändern - self.velocity = random.uniform(0.5, 1.5) + #self.velocity = random.uniform(0.5, 1.5) self.id = self.__hash__() self.route = [] - self.movement_range = self.velocity self.target = None self.group = None - self.original_velocity = self.velocity - self.original_movement_range = self.movement_range + self._original_velocity = random.uniform(0.5, 1.5) + self._original_movement_range = self.original_velocity + self.velocity = self._original_velocity + self.movement_range = self._original_movement_range self.idle = None def log_state(self, timestep, log_file="logs/agent_states.log"): @@ -185,7 +186,19 @@ def log_state(self, timestep, log_file="logs/agent_states.log"): f"State: {self.state}, Arrived: {self.arrived}, Velocity: {self.velocity}\n" ) + @property + def original_velocity(self): + """Access the original velocity.""" + return self._original_velocity + @property + def original_movement_range(self): + """Access the original movement range.""" + return self._original_movement_range + def restore_velocity(self): + self.velocity = self._original_velocity + def restore_movement_range(self): + self.movement_range = self._original_movement_range def is_passable(self): @@ -306,15 +319,18 @@ def social_penalty(self, grid): # @log_decorator def adjust_movement_range(self): #Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> + print(self.idle) if self.idle: - self.movement_range = self.velocity + self.movement_range + print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") + self.movement_range += self.velocity + print("MOVEMENT INCREASE") elif not self.idle: #We enter this part when the agent was able to move after being idle(for example stuck in crowd) --> We reset velocity and movement range to original states self.movement_range = self.original_movement_range print("MOVEMENT RESET") self.velocity = self.original_velocity - return self.movement_range + @@ -352,15 +368,15 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): # Reduce weight of social penalties near the target if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): social_penalty *= 0.5 # Halve the effect of social penalties near the target - random_bias = random.uniform(-0.5, 0.5) - total_cost = distance_to_target + random_bias +social_penalty + staying_penalty + #random_bias = random.uniform(-0.5, 0.5) + total_cost = distance_to_target +social_penalty + staying_penalty if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor #Set lowest possible velocity before reducing it any further to 0.40, graceful penalty only if self.velocity >= 0.40: - if social_penalty > 0: + if social_penalty is not None: self.velocity = self.velocity - social_penalty / 3 if self.velocity <= 0: #we dont want negative velocities @@ -451,7 +467,7 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): # self.arrived = True # grid.agents.remove(self) # grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - def movement_towards_target(self, grid): + def movement_towards_target(self, grid, precomputed_penalties, index): if self.arrived: return @@ -465,34 +481,22 @@ def movement_towards_target(self, grid): best_move = self smallest_cost = float('inf') - - for neighbor in valid_neighbors: - if grid.movement_method =="dijkstra": - distance_to_target = grid.dijkstra_distance_maps[target][neighbor.row][neighbor.col] - elif grid.movement_method == "floodfill": - distance_to_target = grid.flood_fill_distance_maps[target][neighbor.row][neighbor.col] - - total_cost = distance_to_target # Add penalties if needed - if total_cost < smallest_cost: - smallest_cost = total_cost - best_move = neighbor - if isinstance(grid.grid[best_move.row][best_move.col], TargetCell): - self.arrived = True - grid.agents.remove(self) - # Leave the target cell unchanged - grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + new_best_move = self.movement_decision(grid,precomputed_penalties, index) + if new_best_move is None: return - - if best_move != self and self.euclidean_distance_to(best_move) <= self.movement_range: + #print(new_best_move) + print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) + if new_best_move != self and self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) - grid.grid[best_move.row][best_move.col] = self - self.row, self.col = best_move.row, best_move.col + grid.grid[new_best_move[0]][new_best_move[1]] = self + self.row, self.col = new_best_move[0], new_best_move[1] print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False self.adjust_movement_range() - elif best_move != self: + elif new_best_move != self: self.idle = True + print("Range to short, adjusting") self.adjust_movement_range() diff --git a/Application/Grid.py b/Application/Grid.py index a4eee77..b98688e 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -332,8 +332,8 @@ def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5 total_penalties = penalties.sum(axis=1) # Shape: (num_agents,) # Add a small penalty for staying in place - stay_penalty = 0.5 - total_penalties += stay_penalty + #stay_penalty = 0.5 + #total_penalties += stay_penalty return total_penalties @@ -390,7 +390,7 @@ def update(self, target_list, timestep): # Call the agent's movement logic print(f"Agent {agent.id} moving from ({agent.row}, {agent.col})") - agent.movement_towards_target(self) + agent.movement_towards_target(self, precomputed_penalties, i) # Debugging print(f"Agent {agent.id} now at ({agent.row}, {agent.col})") diff --git a/Application/test_components.py b/Application/test_components.py index 42c281e..9fb2571 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -300,7 +300,7 @@ def test_precomputed_penalties(self): np.exp(-(distances[j] ** 2) / (2 * 0.5 ** 2)) for j in range(len(distances)) if within_cutoff[j] and i != j - ) + 0.5 # Include stay penalty + ) self.assertAlmostEqual(penalty, expected_penalty, places=2) @@ -318,186 +318,111 @@ def test_update_with_precomputed_penalties(self): penalty = precomputed_penalties[i] self.assertGreater(penalty, 0) -class TestAgentSocialPenalty(unittest.TestCase): - def setUp(self): - self.grid = Grid( - length=5, - height=5, - cell_size=1, - spawn_cells=[], - target_cells=[], - obstacle_cells=[] - ) - self.agent = Agent(2, 2, cell_size=1) - self.grid.grid[2][2] = self.agent +class TestGridPenalties(unittest.TestCase): - def test_social_penalty(self): - # Place other agents around the main agent - self.grid.grid[1][1] = Agent(1, 1, cell_size=1) - self.grid.grid[1][3] = Agent(1, 3, cell_size=1) - self.grid.grid[3][1] = Agent(3, 1, cell_size=1) - self.grid.grid[3][3] = Agent(3, 3, cell_size=1) + def setUp(self): + # Create a Grid instance + self.cell_size = 1.0 + self.length = 5 # 5 cells wide + self.height = 5 # 5 cells tall - # Calculate the social penalty - penalty = self.agent.social_penalty(self.grid) + # Define the initial setup for the grid + spawn_cells = [] # No spawn cells for this test + target_cells = [(4, 4)] # Target at the bottom-right corner + obstacle_cells = [] # No obstacles for simplicity - # Verify the penalty is calculated correctly - self.assertLess(penalty, 0, "Penalty should be smaller than 0 when there are neighboring agents.") + # Create the grid + self.grid = Grid(self.length, self.height, spawn_cells, target_cells, obstacle_cells, cell_size=self.cell_size) -class TestRepulsiveForceAndSocialPenalty(unittest.TestCase): - def setUp(self): - # Create a 5x5 grid with agents and targets - self.grid = Grid( - length=5, - height=5, - spawn_cells=[], - target_cells=[(4, 4)], - obstacle_cells=[], - cell_size=1.0, - movement_method="dijkstra" - ) - self.grid.update_distance_maps() - - # Place agents - self.agent1 = Agent(2, 2, cell_size=1.0) - self.agent2 = Agent(2, 3, cell_size=1.0) - self.grid.grid[2][2] = self.agent1 - self.grid.grid[2][3] = self.agent2 - self.grid.agents.extend([self.agent1, self.agent2]) - - def test_social_penalty_computation(self): - penalty = self.agent1.social_penalty(self.grid) - self.assertGreater(penalty, 0, "Social penalty should be positive with nearby agents.") - - def test_repulsive_force_computation(self): - height, width = self.agent1.manhattan_difference_to(self.agent2) - repulsive_force = self.agent1.repulsive_force(width, height) - self.assertGreater(repulsive_force, 0, "Repulsive force should be positive for neighboring agents.") - - def test_agent_avoids_high_penalty(self): - # Add additional agents to simulate crowding - additional_agents = [ - Agent(1, 1, cell_size=1.0), - Agent(1, 2, cell_size=1.0), - Agent(1, 3, cell_size=1.0) + # Place agents in the grid + self.grid.place_agent(1, 1) + self.grid.place_agent(1, 2) + self.grid.place_agent(2, 1) + self.grid.place_agent(2, 2) + self.agents = self.grid.agents + + def test_social_penalties_computation(self): + # Compute social penalties for all agents + cutoff_distance = 2.0 + penalty_decay_factor = 0.5 + precomputed_penalties = self.grid.compute_social_penalties(cutoff_distance, penalty_decay_factor) + + # Print computed penalties for debugging + print(f"Computed Penalties: {precomputed_penalties}") + + # Validate penalties are computed correctly + self.assertEqual(len(precomputed_penalties), len(self.agents)) + + # Example manual calculations based on the logic + expected_penalties = [ + 0.28898620536195957, # Adjust these values based on your manual calculations + 0.28898620536195957, + 0.28898620536195957, + 0.28898620536195957 ] - for agent in additional_agents: - self.grid.grid[agent.row][agent.col] = agent - self.grid.agents.append(agent) - # Update and ensure agent1 moves to minimize penalty - self.grid.update(self.grid.target_cells, timestep=1) - self.assertNotEqual((self.agent1.row, self.agent1.col), (2, 2), "Agent should move away from high-penalty area.") + # Check penalties against expected values + for computed, expected in zip(precomputed_penalties, expected_penalties): + self.assertAlmostEqual(computed, expected, delta=0.2) - def test_agent_movement_with_penalties(self): - original_position = (self.agent1.row, self.agent1.col) - self.grid.update(self.grid.target_cells, timestep=1) - new_position = (self.agent1.row, self.agent1.col) - self.assertNotEqual(original_position, new_position, "Agent should move considering both distance and penalties.") -class TestParallelizedAgentMovement(unittest.TestCase): - def setUp(self): - """ - Set up a small grid with multiple agents for testing parallelization. - """ - self.grid = Grid( - length=5, - height=5, - spawn_cells=[], - target_cells=[(4, 4)], - obstacle_cells=[], - cell_size=1.0, - movement_method="dijkstra" - ) - self.grid.update_distance_maps() +class TestAgentMovementRange(unittest.TestCase): - # Add agents - self.agent1 = Agent(0, 0, cell_size=1.0) - self.agent2 = Agent(0, 1, cell_size=1.0) - self.agent3 = Agent(1, 0, cell_size=1.0) - self.grid.grid[0][0] = self.agent1 - self.grid.grid[0][1] = self.agent2 - self.grid.grid[1][0] = self.agent3 - self.grid.agents.extend([self.agent1, self.agent2, self.agent3]) + def setUp(self): + # Create a Grid instance + self.cell_size = 1.0 + self.length = 5 # 5 cells wide + self.height = 5 # 5 cells tall - def test_unique_agent_positions_after_update(self): - """ - Ensure no two agents occupy the same cell after an update. - """ - def move_agent(agent): - if not agent.arrived: - agent.movement_towards_target(self.grid) + # Define the initial setup for the grid + spawn_cells = [] # No spawn cells for this test + target_cells = [(4, 4)] # Target at the bottom-right corner + obstacle_cells = [] # No obstacles for simplicity - # Parallelize agent movement - with ThreadPoolExecutor() as executor: - executor.map(move_agent, self.grid.agents) + # Create the grid + self.grid = Grid(self.length, self.height, spawn_cells, target_cells, obstacle_cells, cell_size=self.cell_size) - # Collect agent positions - positions = [(agent.row, agent.col) for agent in self.grid.agents] - unique_positions = set(positions) + # Add a target at (4, 4) + self.grid.place_target(4, 4) - # Ensure all positions are unique - self.assertEqual(len(positions), len(unique_positions), "Agents occupy the same cell after update.") + # Place an agent at (0, 0) + self.grid.place_agent(0, 0) + self.agent = self.grid.grid[0][0] - def test_correct_movement_logic(self): - """ - Verify each agent independently follows the correct movement logic. - """ - initial_positions = [(agent.row, agent.col) for agent in self.grid.agents] + # Precompute distance maps for the grid + self.grid.update_distance_maps() - def move_agent(agent): - if not agent.arrived: - agent.movement_towards_target(self.grid) + def test_movement_range_increases(self): + # Set initial conditions + # self.agent.movement_range = 0.4 # Start with this range + #self.agent.velocity = 0.4 # Velocity determines the increment + original_range = self.agent._original_movement_range + original_velocity = self.agent._original_velocity + # Debug initial values + print(f"Initial Movement Range: {original_range}") + print(f"Original Velocity: {original_velocity}") + precomputed_penalties = self.grid.compute_social_penalties() - with ThreadPoolExecutor() as executor: - executor.map(move_agent, self.grid.agents) + # Simulate insufficient movement range + self.agent.movement_towards_target(self.grid, precomputed_penalties, 0) - new_positions = [(agent.row, agent.col) for agent in self.grid.agents] + # Assert no movement happened due to insufficient range + self.assertEqual(self.agent.row, 0) + self.assertEqual(self.agent.col, 0) - # Ensure agents have moved and are closer to the target - for i, (old_pos, new_pos) in enumerate(zip(initial_positions, new_positions)): - self.assertNotEqual(old_pos, new_pos, f"Agent {i} did not move.") - self.assertLess( - self.grid.dijkstra_distance_maps[(4, 4)][new_pos[0]][new_pos[1]], - self.grid.dijkstra_distance_maps[(4, 4)][old_pos[0]][old_pos[1]], - f"Agent {i} did not move closer to the target." - ) + # Verify movement range increased - def test_grid_consistency_after_update(self): - """ - Ensure grid consistency after parallel updates. - """ - def move_agent(agent): - if not agent.arrived: - agent.movement_towards_target(self.grid) + expected_range = self.agent._original_movement_range+self.agent.velocity + print(f"Current Velocity afet movement update{self.agent.velocity} range {self.agent.movement_range}")# Initial range + velocity + print(f"New Movement Range: {self.agent.movement_range} (Expected: {expected_range})") + self.assertAlmostEqual(self.agent.movement_range, expected_range, delta=0.1) - with ThreadPoolExecutor() as executor: - executor.map(move_agent, self.grid.agents) + self.agent.movement_towards_target(self.grid, precomputed_penalties, 0) - # Verify grid reflects agent positions correctly - for agent in self.grid.agents: - cell = self.grid.grid[agent.row][agent.col] - self.assertIs(agent, cell, f"Grid inconsistency: Agent not correctly placed at ({agent.row}, {agent.col}).") - def test_arrived_agents_removed_correctly(self): - """ - Verify agents that arrive at the target are removed from the grid and agent list. - """ - # Place one agent directly on the target - target_agent = Agent(4, 4, cell_size=1.0) - self.grid.grid[4][4] = target_agent - self.grid.agents.append(target_agent) - def move_agent(agent): - if not agent.arrived: - agent.movement_towards_target(self.grid) - with ThreadPoolExecutor() as executor: - executor.map(move_agent, self.grid.agents) - # Ensure the agent is removed - self.assertNotIn(target_agent, self.grid.agents, "Arrived agent was not removed.") - self.assertNotIsInstance(self.grid.grid[4][4], Agent, "Arrived agent is still present in the target cell.") if __name__ == "__main__": unittest.main() From 6d1cad7cf3a991f6bd0e96b3266f13f25d7d6239 Mon Sep 17 00:00:00 2001 From: Silvano <118846332+Silivanili@users.noreply.github.com> Date: Sun, 29 Dec 2024 22:19:59 +0100 Subject: [PATCH 16/26] =?UTF-8?q?Visualisierungsklasse=20=C3=BCberarbeitet?= =?UTF-8?q?=20und=20kommentiert.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Grid.py | 49 +++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index b98688e..63398d5 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -749,7 +749,7 @@ def __init__(self, grid): self.fig, self.ax = plt.subplots() def plot_grid_state(self, timestep): - data = [[cell.state for cell in row] for row in self.grid.grid] + data = [[cell.state for cell in row] for row in self.grid.grid] #2D Liste mit jedem Zellenstatus custom_colors = { 0: 'white', @@ -759,46 +759,39 @@ def plot_grid_state(self, timestep): 4: 'gray' } - agent_color_map = {0: 'blue', 1: 'red'} # Map agent groups to colors + agent_color_map = {0: 'lightblue', 1: 'darkblue'} - cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) + cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) #erstelle eigne Colormap durch obige Farbwahl bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] norm = mcolors.BoundaryNorm(bounds, cmap.N) - self.ax.imshow(data, cmap=cmap, norm=norm) - - # Create a new array to hold the agent colors - agent_data = np.zeros((self.grid.rows, self.grid.cols), dtype=int) - for row in range(self.grid.rows): - for col in range(self.grid.cols): - cell = self.grid.grid[row][col] - if isinstance(cell, Agent): - # Map the agent's group to a unique value - agent_data[row, col] = 47 + cell.group + self.ax.cla() #Lösche den vorherigen Plotinhalt. + self.ax.imshow(data, cmap=cmap, norm=norm) #Plotte die Zellen mit eigner Colormap - # Update the custom_colors dictionary with the new values for agents - custom_colors[47] = 'black' # Default color for agents (not used here) - custom_colors[48] = agent_color_map[0] # Color for group 0 agents - custom_colors[49] = agent_color_map[1] # Color for group 1 agents + for agent in self.grid.agents: #Plotte jeden Agenten auf dem Grid. + if isinstance(self.grid.grid[agent.row][agent.col], Agent): + self.ax.add_patch(plt.Rectangle((agent.col-0.5, agent.row-0.5), 1, 1, edgecolor='black', facecolor=agent_color_map[agent.group])) #Mit add_patch wird der Agent platziert. Wir ziehen -0.5 ab um den Agenten in der Mitte der ZELLE zu setzen. (Position) - # Update the bounds and norm to include the new values - cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) - bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] - norm = mcolors.BoundaryNorm(bounds, cmap.N) + # Gitterlinien hinzufügen + for i in range(len(data)): + self.ax.axhline(i+0.5, color='black') #Wir addieren 0.5 dazu um den Agenten in der Mitte der GITTERLINIEN zu setzen. (Visuell) + for j in range(len(data[0])): + self.ax.axvline(j+0.5, color='black') - # Plot the agent data using the updated colormap - self.ax.imshow(agent_data, cmap=cmap, norm=norm, alpha=0.8) # Alpha for layering + #Genauer definierter Bereich + self.ax.set_xlim(-1, len(data[0])) #Von Links nach Rechts + self.ax.set_ylim(-1, len(data)) self.ax.set_title(f"Grid State at Timestep {timestep}") self.ax.set_xlabel("Columns") self.ax.set_ylabel("Rows") - plt.pause(0.1) + plt.pause(0.01) - def animate_grid_states(self, timesteps): + def animate_grid_states(self, timesteps): #Für jeden Zeitschritt, rufe die Funktion Update auf, welche die Zellen im Grid updated und dann plottet. def update(frame): - self.grid.update(target_list=self.grid.target_cells, timestep=frame) + self.grid.update(target_list=self.grid.target_cells, timestep=frame) self.plot_grid_state(frame) - ani = animation.FuncAnimation(self.fig, update, frames=timesteps, interval=10) - plt.show() + ani = animation.FuncAnimation(self.fig, update, frames=timesteps, interval=10) #FuncAnimation regelt die Synchronisation, in diesem Fall 10 Milisekunden + plt.show() \ No newline at end of file From ac27fe37efe980042b7b56886a671b1d62d55989 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 23:13:09 +0100 Subject: [PATCH 17/26] Passing all unit tests, simulation looks fine also --- Application/Cell.py | 28 ++++++++++++++++------------ Application/Grid.py | 6 +++--- Application/run.py | 6 +++--- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index dca8797..70511fd 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -319,16 +319,17 @@ def social_penalty(self, grid): # @log_decorator def adjust_movement_range(self): #Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> - print(self.idle) + #print(self.idle) if self.idle: - print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") + #print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") self.movement_range += self.velocity print("MOVEMENT INCREASE") + print(f"new range is{self.movement_range}") elif not self.idle: #We enter this part when the agent was able to move after being idle(for example stuck in crowd) --> We reset velocity and movement range to original states self.movement_range = self.original_movement_range - print("MOVEMENT RESET") + print(f"MOVEMENT RESET: new range is {self.movement_range} and velocity is {self.velocity}") self.velocity = self.original_velocity @@ -363,26 +364,28 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): for neighbor in valid_neighbors: distance_to_target = distance_map[neighbor.row][neighbor.col] social_penalty = precomputed_penalties[agent_index] - staying_penalty = 2 if neighbor == self else 0 + staying_penalty = 5 if neighbor == self else 0 # Reduce weight of social penalties near the target if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): social_penalty *= 0.5 # Halve the effect of social penalties near the target - #random_bias = random.uniform(-0.5, 0.5) - total_cost = distance_to_target +social_penalty + staying_penalty + random_bias = random.uniform(-0.5, 0.5) + total_cost = distance_to_target +random_bias +social_penalty + staying_penalty if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor #Set lowest possible velocity before reducing it any further to 0.40, graceful penalty only + print(f"Agent{self.id} velocity before penalty is {self.velocity}. Social penalty is {social_penalty}") if self.velocity >= 0.40: if social_penalty is not None: - self.velocity = self.velocity - social_penalty / 3 + self.velocity = self.velocity - (social_penalty / 2) if self.velocity <= 0: #we dont want negative velocities self.velocity = abs(self.velocity) self.movement_range = self.velocity - #print(f"new velocity for {agent_index} is {self.velocity}") + + print(f"new velocity for {agent_index} is {self.velocity}") # Mark as arrived if moving onto the target @@ -480,23 +483,24 @@ def movement_towards_target(self, grid, precomputed_penalties, index): return best_move = self - smallest_cost = float('inf') + new_best_move = self.movement_decision(grid,precomputed_penalties, index) if new_best_move is None: + print(f"Agent {self.id} at position {self.row,self.col} has no best move") return #print(new_best_move) - print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) + #print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) if new_best_move != self and self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) grid.grid[new_best_move[0]][new_best_move[1]] = self self.row, self.col = new_best_move[0], new_best_move[1] - print(f"Agent {self.id} moved to ({self.row}, {self.col})") + #print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False self.adjust_movement_range() elif new_best_move != self: self.idle = True - print("Range to short, adjusting") + print(f"Agent{self.id}: Range {self.movement_range} to short, adjusting. Distance is{self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])}") self.adjust_movement_range() diff --git a/Application/Grid.py b/Application/Grid.py index b98688e..931d73c 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -389,15 +389,15 @@ def update(self, target_list, timestep): continue # Call the agent's movement logic - print(f"Agent {agent.id} moving from ({agent.row}, {agent.col})") + #print(f"Agent {agent.id} moving from ({agent.row}, {agent.col})") agent.movement_towards_target(self, precomputed_penalties, i) # Debugging - print(f"Agent {agent.id} now at ({agent.row}, {agent.col})") + #print(f"Agent {agent.id} now at ({agent.row}, {agent.col})") # Remove agents that have arrived for agent in agents_to_remove: - print(f"Removing agent {agent} from ({agent.row}, {agent.col})") + #print(f"Removing agent {agent} from ({agent.row}, {agent.col})") self.agents.remove(agent) # Restore target cell explicitly diff --git a/Application/run.py b/Application/run.py index 6a75ff6..748abb5 100644 --- a/Application/run.py +++ b/Application/run.py @@ -35,17 +35,17 @@ visualization.plot_grid_state(i) #grid.plot_grid_state(i) plt.pause(1) - print(f"len_agentscrossed:{len(agents_crossed)}") + #print(f"len_agentscrossed:{len(agents_crossed)}") # Calculate density, speed, and flow: Set Boundarys to be doorcells (as defined in Rimea), pass current timestep, already_crossed agents and the area for density calculation #print(agents_crossed) fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) - print(type(fd_results)) + #print(type(fd_results)) fundamental_data.append(fd_results) #print(fundamental_data) # Store results for plotting - print(type(fundamental_data)) + # print(type(fundamental_data)) # Update previous positions for the next timestep agent_count = len(grid.agents) agent_count_list.append(agent_count) From 37906ed340349b33250b0043aeb90d75b6372cfb Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 23:22:46 +0100 Subject: [PATCH 18/26] Some more changes --- Application/Cell.py | 50 +++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index dca8797..18478e6 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -318,22 +318,20 @@ def social_penalty(self, grid): # @log_decorator def adjust_movement_range(self): - #Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> - print(self.idle) + # Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> + # print(self.idle) if self.idle: - print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") + # print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") self.movement_range += self.velocity print("MOVEMENT INCREASE") + print(f"new range is{self.movement_range}") elif not self.idle: - #We enter this part when the agent was able to move after being idle(for example stuck in crowd) --> We reset velocity and movement range to original states + # We enter this part when the agent was able to move after being idle(for example stuck in crowd) --> We reset velocity and movement range to original states self.movement_range = self.original_movement_range - print("MOVEMENT RESET") + print(f"MOVEMENT RESET: new range is {self.movement_range} and velocity is {self.velocity}") self.velocity = self.original_velocity - - - def movement_decision(self, grid, precomputed_penalties, agent_index): """ Decide the next move for the agent. Mark as arrived if reaching the target. @@ -363,27 +361,28 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): for neighbor in valid_neighbors: distance_to_target = distance_map[neighbor.row][neighbor.col] social_penalty = precomputed_penalties[agent_index] - staying_penalty = 2 if neighbor == self else 0 + staying_penalty = 5 if neighbor == self else 0 # Reduce weight of social penalties near the target if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): social_penalty *= 0.5 # Halve the effect of social penalties near the target - #random_bias = random.uniform(-0.5, 0.5) - total_cost = distance_to_target +social_penalty + staying_penalty + random_bias = random.uniform(-0.5, 0.5) + total_cost = distance_to_target + random_bias + social_penalty + staying_penalty if total_cost < smallest_cost: smallest_cost = total_cost best_move = neighbor - #Set lowest possible velocity before reducing it any further to 0.40, graceful penalty only + # Set lowest possible velocity before reducing it any further to 0.40, graceful penalty only + print(f"Agent{self.id} velocity before penalty is {self.velocity}. Social penalty is {social_penalty}") if self.velocity >= 0.40: if social_penalty is not None: - self.velocity = self.velocity - social_penalty / 3 + self.velocity = self.velocity - (social_penalty / 2) if self.velocity <= 0: - #we dont want negative velocities + # we dont want negative velocities self.velocity = abs(self.velocity) self.movement_range = self.velocity - #print(f"new velocity for {agent_index} is {self.velocity}") + print(f"new velocity for {agent_index} is {self.velocity}") # Mark as arrived if moving onto the target if isinstance(grid.grid[best_move.row][best_move.col], TargetCell): @@ -472,31 +471,34 @@ def movement_towards_target(self, grid, precomputed_penalties, index): return target = self.find_target(grid.target_cells) - #print(f"Agent {self.id} moving towards target {target}") + # print(f"Agent {self.id} moving towards target {target}") valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) if not valid_neighbors: - #print(f"Agent {self.id} has no valid neighbors and is stuck.") + # print(f"Agent {self.id} has no valid neighbors and is stuck.") return best_move = self - smallest_cost = float('inf') - new_best_move = self.movement_decision(grid,precomputed_penalties, index) + + new_best_move = self.movement_decision(grid, precomputed_penalties, index) if new_best_move is None: + print(f"Agent {self.id} at position {self.row, self.col} has no best move") return - #print(new_best_move) - print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) - if new_best_move != self and self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: + # print(new_best_move) + # print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) + if new_best_move != self and self.euclidean_distance_to( + grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) grid.grid[new_best_move[0]][new_best_move[1]] = self self.row, self.col = new_best_move[0], new_best_move[1] - print(f"Agent {self.id} moved to ({self.row}, {self.col})") + # print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False self.adjust_movement_range() elif new_best_move != self: self.idle = True - print("Range to short, adjusting") + print( + f"Agent{self.id}: Range {self.movement_range} to short, adjusting. Distance is{self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])}") self.adjust_movement_range() From 5c406d25d57210e6c431987fa44119b7b7dde714 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Sun, 29 Dec 2024 23:50:47 +0100 Subject: [PATCH 19/26] Y2k hysteria was probably more relaxing then this --- Application/Cell.py | 84 +++++--------------------------------------- Application/Grid.py | 52 +++++++++++++++++++++++++++ Application/run.py | 75 ++++----------------------------------- Application/tests.py | 14 +++++--- 4 files changed, 76 insertions(+), 149 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 18478e6..586162e 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -317,6 +317,12 @@ def social_penalty(self, grid): # @log_decorator + + # Bewegungslogik --> Untestehend sind die 3 Methoden über welche die Bewegung gesteuert wird. + # adjust_movement_range --> Erhöht die movement_range um die momentane velocity (ich komme in t+1 nicht an, also warte ich und bei t+2 kann ich das Ziel erreichen) + # movement_decision --> Basierend auf den social penalties und der distance map wird das beste ziel gewählt. Falls der aktuelle standort bestes ziel ist wird None returned (bei None wird sich nicht bewegt) + # movement_towards_target --> nutzt die beiden oberen methoden um sich zum Ziel zu bewegen. Ist die euklidische Distanz zu gross erhähen wir die movement range für t+1 mit adjust_movement_range. Ist dies der Fall wird der Agent auf "idle" gestellt + # sobald der Agent sich bewegt hat wird der "idle" Modus beendet und die Movement range sowie geschwindigkeit sollten wieder zum standardwert zurückkehren def adjust_movement_range(self): # Methode wird aufgerufen wenn Ziel nicht in einem Zeitschritt erreicht werden kann --> # print(self.idle) @@ -390,82 +396,8 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): return None return (best_move.row, best_move.col) if best_move != self else None - #Bewegungslogik - # sure this method does make sense here from a architectural point of view? -# def movement_towards_target(self, grid): -# """ -# Decide movement based on target proximity and social penalties. -# """ -# if self.arrived: -# return -# -# # Determine the target and select the distance map -# target = self.find_target(grid.target_cells) -# if not target: -# return -# -# target_key = (target[0], target[1]) -# if grid.movement_method == "dijkstra": -# distance_map = grid.dijkstra_distance_maps.get(target_key) -# elif grid.movement_method == "floodfill": -# distance_map = grid.flood_fill_distance_maps.get(target_key) -# -# if not distance_map: -# return # Ensure the distance map is available -# -# # Get valid neighbors -# valid_neighbors = self.valid_neighbors(self.get_neighbors(grid, radius=1)) -# valid_neighbors.append(self) # Include the current position as a fallback -# social_penalties = { -# (neighbor.row, neighbor.col): self.social_penalty(grid) -# for neighbor in valid_neighbors -# } -# -# # Determine the best move -# best_move = self -# smallest_cost = float('inf') -# -# for neighbor in valid_neighbors: -# # Cache distance to target for efficiency -# distance_to_target = distance_map[neighbor.row][neighbor.col] -# -# # Fetch precomputed social penalty -# penalty = social_penalties[(neighbor.row, neighbor.col)] -# -# # Add penalty for staying in place -# staying_penalty = 1.0 if neighbor == self else 0 -# -# total_cost = distance_to_target + penalty + staying_penalty -# -# if total_cost < smallest_cost: -# smallest_cost = total_cost -# best_move = neighbor -# -# # Check if the best move is onto the target -# if (best_move.row, best_move.col) == target: -# self.arrived = True -# grid.agents.remove(self) -# # Leave the target cell unchanged -# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) -# return -# -# # Move to the best neighbor -# #print(f"agent id{self.id} has {self.euclidean_distance_to(best_move)} distance to best_move and {self.movement_range} movement_range") -# if best_move != self and self.euclidean_distance_to(best_move)<=self.movement_range: -# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) -# grid.grid[best_move.row][best_move.col] = self -# self.row, self.col = best_move.row, best_move.col -# self.idle = False -# self.adjust_movement_range() -# elif best_move != self and self.euclidean_distance_to(best_move)>self.movement_range: -# self.idle = True -# print(f"agent{self.id} is idle: {self.idle}") -# self.adjust_movement_range() -# print(f"increase of movement range to {self.movement_range}") -# if smallest_cost == 0: # If reached the target -# self.arrived = True -# grid.agents.remove(self) -# grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) + + def movement_towards_target(self, grid, precomputed_penalties, index): if self.arrived: return diff --git a/Application/Grid.py b/Application/Grid.py index fee635b..e0f2a59 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -794,4 +794,56 @@ def update(frame): self.plot_grid_state(frame) ani = animation.FuncAnimation(self.fig, update, frames=timesteps, interval=10) #FuncAnimation regelt die Synchronisation, in diesem Fall 10 Milisekunden + plt.show() + + def plot_fundamental_diagram(self,data): + """Generate two separate plots from fundamental diagram data.""" + + # Extract data, remove entries where flow was 0 (no agents crossing yet) + timesteps = [entry["timestep"] for entry in data if entry["flow"]>0] + densities = [entry["density"] for entry in data if entry["flow"]>0] + avg_speeds = [entry["avg_speed"] for entry in data if entry["flow"]>0] + flows = [entry["flow"] for entry in data if entry["flow"]>0] + + # Plot 1: Density over time + plt.figure(figsize=(10, 6)) + plt.plot(timesteps, densities, marker="o", label="Density over time") + plt.title("Density Over Time") + plt.xlabel("Timestep") + plt.ylabel("Density (agents/m^2)") + plt.grid(True) + plt.legend() + plt.savefig("Density_over_time") + plt.show() + + # Plot 2: Flow over time + plt.figure(figsize=(10, 6)) + plt.plot(timesteps, flows, marker="o", label="Density over time") + plt.title("Flow Over Time") + plt.xlabel("Timestep") + plt.ylabel("Flow (agents/m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Flow_over_time") + plt.show() + # Plot 3: Flow over time + plt.figure(figsize=(10, 6)) + plt.plot(densities, flows, marker="o", label="Density over time") + plt.title("Flows over Densities") + plt.xlabel("Densities") + plt.ylabel("Flow (agents/m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Flow_over_densities") + plt.show() + + # Plot 2: Average speed relative to density + plt.figure(figsize=(10, 6)) + plt.plot(densities, avg_speeds, marker="s", label="Avg Speed vs. Density", color="red") + plt.title("Average Speed Relative to Density") + plt.xlabel("Density (agents/m^2)") + plt.ylabel("Average Speed (m/s)") + plt.grid(True) + plt.legend() + plt.savefig("Speed_relative_to_density") plt.show() \ No newline at end of file diff --git a/Application/run.py b/Application/run.py index 748abb5..cee10c5 100644 --- a/Application/run.py +++ b/Application/run.py @@ -5,8 +5,11 @@ from Grid import Grid, Visualization from tests import room_square, ChickenTest, RiMEA9, RiMEA4, Experiment import matplotlib +#This line is needed for the plots to render in Pychamr Sciplot-View matplotlib.use("Qt5Agg") -grid, door_cells, roi = RiMEA9(2,"dijkstra") +#Using the Maps from tests build a grid. every map returns a grid, door_cells (used as boundary zones for flow measurements) and roi (region used to calculate agent density) +#grid, door_cells, roi = RiMEA9(2,"dijkstra") +grid, door_cells, roi = Experiment("dijkstra") print(roi) #exp_grid, roi = Experiment("dijkstra") @@ -31,21 +34,15 @@ if i == 0: initial_count = len(grid.agents) print(f"init{initial_count}") - visualization.plot_grid_state(i) #grid.plot_grid_state(i) plt.pause(1) #print(f"len_agentscrossed:{len(agents_crossed)}") - # Calculate density, speed, and flow: Set Boundarys to be doorcells (as defined in Rimea), pass current timestep, already_crossed agents and the area for density calculation - #print(agents_crossed) - fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) - #print(type(fd_results)) fundamental_data.append(fd_results) - #print(fundamental_data) - # Store results for plotting - # print(type(fundamental_data)) + + # Update previous positions for the next timestep agent_count = len(grid.agents) agent_count_list.append(agent_count) @@ -54,65 +51,7 @@ break - - -#Dataprep before plotting - - -#grid.plot_fundamental_diagram(fundamental_data) - def plot_fundamental_diagram(data): - """Generate two separate plots from fundamental diagram data.""" - - # Extract data, remove entries where flow was 0 (no agents crossing yet) - timesteps = [entry["timestep"] for entry in data if entry["flow"]>0] - densities = [entry["density"] for entry in data if entry["flow"]>0] - avg_speeds = [entry["avg_speed"] for entry in data if entry["flow"]>0] - flows = [entry["flow"] for entry in data if entry["flow"]>0] - - # Plot 1: Density over time - plt.figure(figsize=(10, 6)) - plt.plot(timesteps, densities, marker="o", label="Density over time") - plt.title("Density Over Time") - plt.xlabel("Timestep") - plt.ylabel("Density (agents/m^2)") - plt.grid(True) - plt.legend() - plt.savefig("Density_over_time") - plt.show() - - # Plot 2: Flow over time - plt.figure(figsize=(10, 6)) - plt.plot(timesteps, flows, marker="o", label="Density over time") - plt.title("Flow Over Time") - plt.xlabel("Timestep") - plt.ylabel("Flow (agents/m/s)") - plt.grid(True) - plt.legend() - plt.savefig("Flow_over_time") - plt.show() - # Plot 3: Flow over time - plt.figure(figsize=(10, 6)) - plt.plot(densities, flows, marker="o", label="Density over time") - plt.title("Flows over Densities") - plt.xlabel("Densities") - plt.ylabel("Flow (agents/m/s)") - plt.grid(True) - plt.legend() - plt.savefig("Flow_over_densities") - plt.show() - - # Plot 2: Average speed relative to density - plt.figure(figsize=(10, 6)) - plt.plot(densities, avg_speeds, marker="s", label="Avg Speed vs. Density", color="red") - plt.title("Average Speed Relative to Density") - plt.xlabel("Density (agents/m^2)") - plt.ylabel("Average Speed (m/s)") - plt.grid(True) - plt.legend() - plt.savefig("Speed_relative_to_density") - plt.show() - -plot = plot_fundamental_diagram(fundamental_data) +plot = visualization.plot_fundamental_diagram(fundamental_data) diff --git a/Application/tests.py b/Application/tests.py index 4316163..d158add 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -143,18 +143,22 @@ def Experiment(movement_method): obstacle_cells += grid.select_area_by_coordinates(0, height/2, 0, height/2) obstacle_cells += grid.select_area_by_coordinates(length, height/2, length, height/2) spawn_cells = grid.select_area_by_coordinates(3.5, height,4.5 , height) + agent_pos_1 = grid.meter_to_rowcol(3.5, 8) agent_pos_2 = grid.meter_to_rowcol(4.5, 8) - grid.place_agent(agent_pos_1[0], agent_pos_1[1]) - grid.place_agent(agent_pos_2[0], agent_pos_2[1]) + door_cells = grid.select_area_by_coordinates(1, 8, 3, 8) + door_cells += grid.select_area_by_coordinates(5, 8, length-1, 8) + grid.place_obstacle(agent_pos_1[0], agent_pos_1[1]) + grid.place_obstacle(agent_pos_2[0], agent_pos_2[1]) roi = (0,0,length,height) for target in target_cells: - grid.place_agent(target.row, target.col) + grid.place_target(target.row, target.col) for cell in obstacle_cells: grid.place_obstacle(cell.row, cell.col) for cell in spawn_cells: grid.place_spawn_cell(cell.row, cell.col) for agent in grid.agents: #Set velocity to 0 so these Agents dont move - agent.velocity = 0 - return grid, roi \ No newline at end of file + agent._original_velocity = 0 + agent.original_velocity = 0 + return grid, door_cells, roi \ No newline at end of file From b075779f7ac5da8ae6b39526cefbef967b19eea6 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Mon, 30 Dec 2024 08:59:36 +0100 Subject: [PATCH 20/26] =?UTF-8?q?run.py=20f=C3=BCr=20besseres=20Testing=20?= =?UTF-8?q?am=20anpassen,=20noch=20nicht=20fertig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Cell.py | 81 ++++---------------------------------------- Application/Grid.py | 9 ++--- Application/run.py | 27 +++++++++++---- Application/tests.py | 4 +-- 4 files changed, 33 insertions(+), 88 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 586162e..7d9c35b 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -126,8 +126,10 @@ def __repr__(self): return '%' # Spawn Zelle. Generiert pro Zeitschritt eine vordefinierte Anzahl agenten auf seinen Moore Nachbar Zellen class SpawnCell(Cell): - def __init__(self, row, col, cell_size): - super().__init__(state=2, row=row, col=col, cell_size=cell_size) # Spawn cells are active + def __init__(self, row, col, cell_size, spawn_rate): + super().__init__(state=2, row=row, col=col, cell_size=cell_size) + self.spawn_rate = spawn_rate + # Spawn cells are active #Spawn eine definierte Anzahl Agenten auf deinen Moore Nachbarn und füge die neuen Agenten der grid.agents liste Hinzu @@ -136,7 +138,8 @@ def spawn_agents(self, grid, max_agents): random.shuffle(valid_neighbors) chance = random.randrange(0,1) agents_to_spawn = min(max_agents, len(valid_neighbors)) - if chance < 0.8: + #Check the random chance against a set rate in order to control how + if chance < self.spawn_rate: for _ in range(agents_to_spawn): cell = valid_neighbors.pop(0) row, col = cell.row, cell.col @@ -243,78 +246,6 @@ def bresenham_line(self, x1, y1, x2, y2): return cells - # @log_decorator - def social_force(self, grid): - print("entering social force") - neighbors = self.get_neighbors(grid, radius=2) # Get neighbors within the radius - total_neighbors = sum(len(cells) for cells in neighbors.values()) - penalty = 0 # Initialize the penalty accumulator - penalty_factor = 0.6 - agent_cells = [] # List to store agent cells - for distance, cells in neighbors.items(): # neighbors are grouped by distance layers - for cell in cells: - if isinstance(cell, Agent): # Check if the cell contains an agent - agent_cells.append(cell) - - # Calculate the percentage of agents among all neighbors - - - #Sollten wir wo anders abfangen, bei get_neighbors --> darf nicht 0 returnen - if total_neighbors == 0: # Avoid division by zero - return 0 # No penalty if no neighbors - agent_percentage = len(agent_cells) / total_neighbors - - # Apply penalty only if at least 40% of neighbors are agents - # if agent_percentage < 0.2: - # return 0 # No penalty applied if less than 40% are agents - - # Calculate penalty - penalty = 0 # Initialize the penalty accumulator - for agent in agent_cells: - euclidean_distance = self.euclidean_distance_to(agent) # Calculate distance - - if euclidean_distance > 0: # Avoid division by zero for self - penalty_contribution = 1 / euclidean_distance # Inverse distance penalty - penalty += penalty_contribution - print(f"Social force penalty for {self.__hash__()} is {penalty}") - return penalty - # @log_decorator - - def repulsive_force(self, width, height): - if width == 0: # Ensure no division by zero for width - width = 1e-6 # Substitute with a very small number - repulsive_force = -height + math.exp(1 / (2/width)**2 -1) - return repulsive_force - # @log_decorator - def social_penalty(self, grid): - """ - Calculate the social penalty based on agent proximity and movement preference. - Returns a penalty score to discourage crowding. - """ - total_penalty = 0 - cutoff_distance = 3.0 # Maximum distance in meters to consider for social penalty - penalty_decay_factor = 0.5 # Control the steepness of the Gaussian decay - stay_penalty = 0.5 # Additional penalty for remaining stationary - - # Get neighbors within a cutoff radius - neighbors = self.get_neighbors(grid, radius=int(cutoff_distance / grid.cell_size)) - - for distance, cells in neighbors.items(): - for cell in cells: - if isinstance(cell, Agent) and not cell.arrived: # Only consider other agents - # Compute Euclidean distance in meters - euclidean_distance = self.euclidean_distance_to(cell) - - if euclidean_distance <= cutoff_distance: - # Apply Gaussian decay penalty - penalty_contribution = math.exp(-(euclidean_distance ** 2) / (2 * penalty_decay_factor ** 2)) - total_penalty += penalty_contribution - - # Add penalty for staying in place - total_penalty += stay_penalty - - return total_penalty - # @log_decorator diff --git a/Application/Grid.py b/Application/Grid.py index e0f2a59..3e57d6f 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -20,7 +20,7 @@ def clamp(value, min_value, max_value): #Grid Klasse: Auf dem Grid befinden sich Zellobjekte und über das Grid wird das update() der Zellen durchgeführt class Grid: #Im Init wird das grid entsprechend aufgebaut, es können Listen mit Tuplen für die entsprechenden Zell Objekte mitgegeben werden - def __init__(self, length, height, spawn_cells, target_cells, obstacle_cells, cell_size=1.0, movement_method="dijkstra"): + def __init__(self, length, height, spawn_cells, target_cells, obstacle_cells, cell_size=1.0, movement_method="dijkstra", spawn_rate=1): self.length = length self.height = height self.cell_size = cell_size @@ -29,7 +29,8 @@ def __init__(self, length, height, spawn_cells, target_cells, obstacle_cells, ce self.grid = [ [Cell(row, col, cell_size=cell_size) for col in range(self.cols)] for row in range(self.rows) ] # Aufbau Grid - self.spawn_cells = spawn_cells # Listen für Spawns, Ziele und Hindernisse + self.spawn_cells = spawn_cells + self.spawn_rate = spawn_rate# Listen für Spawns, Ziele und Hindernisse self.target_cells = target_cells self.obstacle_cells = obstacle_cells self.agents = [] # Liste mit allen Agenten die sich auf dem Feld befinden @@ -41,7 +42,7 @@ def __init__(self, length, height, spawn_cells, target_cells, obstacle_cells, ce # Aufbau von Spawn, Zielen und Hindernissen for row, col in spawn_cells: #print(row, col) - self.grid[row][col] = SpawnCell(row=row, col=col, cell_size=cell_size) + self.grid[row][col] = SpawnCell(row=row, col=col, cell_size=cell_size,spawn_rate=self.spawn_rate) if obstacle_cells is not None: for row, col in obstacle_cells: #print(obstacle_cells) @@ -116,7 +117,7 @@ def place_border(self): def place_spawn_cell(self, row,col): """Place a spawn cell at a specific position on the grid""" #row, col = self.meter_to_rowcol(x, y) - self.grid[row][col] = SpawnCell(row=row, col=col, cell_size=self.cell_size) + self.grid[row][col] = SpawnCell(row=row, col=col, cell_size=self.cell_size, spawn_rate=self.spawn_rate) self.spawn_cells.append((row, col)) def place_empty_cell(self, row,col): diff --git a/Application/run.py b/Application/run.py index cee10c5..c9bf520 100644 --- a/Application/run.py +++ b/Application/run.py @@ -9,18 +9,31 @@ matplotlib.use("Qt5Agg") #Using the Maps from tests build a grid. every map returns a grid, door_cells (used as boundary zones for flow measurements) and roi (region used to calculate agent density) #grid, door_cells, roi = RiMEA9(2,"dijkstra") -grid, door_cells, roi = Experiment("dijkstra") -print(roi) +#grid, door_cells, roi = RiMEA9(1, "dijsktra") +#grid, door_cells, roi = RiMEA9(3, "dijkstra") +#grid, door_cells, roi = RiMEA9(4, "dijkstra") +grid, door_cells, roi = RiMEA4(movement_method="dijkstra", spawn_rate=0.6) +#grid, door_cells, roi = Experiment("dijkstra") +user_input = input("Bitte geben sie an welchen Test (RiMEA4, RiMEA9 oder Experiment) sie durchführen wollen") +if user_input == "RiMEA4": + door_input = input("Bitte Anzahl (1-4) Türen angeben") + pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") + if pathfinding == "floodfill": + algo = "floodfill" + elif pathfinding == "dijkstra": + algo = "dijkstra" + doors = int(door_input) + grid, door_cells, roi = RiMEA4(movement_method=pathfinding, doors=doors ) + + + + + #exp_grid, roi = Experiment("dijkstra") #exp_grid.plot_grid_state(timestep=0) visualization = Visualization(grid) -#region_cord = (frameSize + 4, frameSize+1, frameSize + 5, frameSize+1) -#region_2_cord = (length - frameSize - 5, height - frameSize, length - frameSize - 4, height - frameSize) - -#region = grid.select_area_by_coordinates(frameSize + 4, frameSize, frameSize + 5, frameSize) agent_count_list = [] - fundamental_data = [] timesteps = 10000 diff --git a/Application/tests.py b/Application/tests.py index d158add..f18c937 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -106,12 +106,12 @@ def RiMEA9(Doors, movement_method): -def RiMEA4(movement_method): +def RiMEA4(movement_method, spawn_rate=1): height = 10 length = 100 warm_up = 25 corridor_length = warm_up + length - grid = Grid(height=height, length=corridor_length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=1, movement_method=movement_method) + grid = Grid(height=height, length=corridor_length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5, movement_method=movement_method, spawn_rate=spawn_rate) # Door_cells sind in diesem Fall eine Zelle vor Ende des Ganges door_cells = grid.select_area_by_coordinates(corridor_length-1, 0, corridor_length-1, height) From 40cb9b8c87dea4dbd84f9a8c05aa5f1074dab043 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Mon, 30 Dec 2024 11:45:02 +0100 Subject: [PATCH 21/26] =?UTF-8?q?Run.py=20kann=20jezt=20ausgef=C3=BChrt=20?= =?UTF-8?q?werden=20und=20bei=20Ausf=C3=BChrung=20k=C3=B6nnen=20die=20Simu?= =?UTF-8?q?lationsparameter=20gesezt=20werden?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Application/Cell.py | 2 +- Application/run.py | 37 +++++++++++++++++++++++++++++-------- Application/tests.py | 4 ++-- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 7d9c35b..c824477 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -139,7 +139,7 @@ def spawn_agents(self, grid, max_agents): chance = random.randrange(0,1) agents_to_spawn = min(max_agents, len(valid_neighbors)) #Check the random chance against a set rate in order to control how - if chance < self.spawn_rate: + if chance <= self.spawn_rate: for _ in range(agents_to_spawn): cell = valid_neighbors.pop(0) row, col = cell.row, cell.col diff --git a/Application/run.py b/Application/run.py index c9bf520..6a058a0 100644 --- a/Application/run.py +++ b/Application/run.py @@ -12,34 +12,55 @@ #grid, door_cells, roi = RiMEA9(1, "dijsktra") #grid, door_cells, roi = RiMEA9(3, "dijkstra") #grid, door_cells, roi = RiMEA9(4, "dijkstra") -grid, door_cells, roi = RiMEA4(movement_method="dijkstra", spawn_rate=0.6) +#grid, door_cells, roi = RiMEA4(movement_method="dijkstra", spawn_rate=0.6) #grid, door_cells, roi = Experiment("dijkstra") +time_input = input("Bitte geben sie die Anzahl Zeitschritte an (ein Zeitschritt ist 1 Sekunde)") +timesteps = int(time_input) user_input = input("Bitte geben sie an welchen Test (RiMEA4, RiMEA9 oder Experiment) sie durchführen wollen") -if user_input == "RiMEA4": +if user_input == "RiMEA9": door_input = input("Bitte Anzahl (1-4) Türen angeben") pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": algo = "floodfill" elif pathfinding == "dijkstra": algo = "dijkstra" + else: + algo = "dijkstra" #Standardwert ist dijkstra doors = int(door_input) - grid, door_cells, roi = RiMEA4(movement_method=pathfinding, doors=doors ) - - - + grid, door_cells, roi = RiMEA9(movement_method=algo, Doors=doors) +elif user_input == "RiMEA4": + pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") + if pathfinding == "floodfill": + algo = "floodfill" + elif pathfinding == "dijkstra": + algo = "dijkstra" + else: + algo = "dijkstra" #Standardwert ist dijkstra + spawn_input = input("Bitte spawnrate zwischen 0 und 1 eingeben um Personendichte zu variieren (Wird mit einer Zufallsvariable zwischen 0 und 1 verglichen um spawn zu bestimmen)") + grid, door_cells, roi = RiMEA4(movement_method=algo,spawn_rate=float(spawn_input)) +elif user_input == "Experiment": + pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") + if pathfinding == "floodfill": + algo = "floodfill" + elif pathfinding == "dijkstra": + algo = "dijkstra" + else: + algo = "dijkstra" # Standardwert ist dijkstra + spawn_input = input("Bitte spawnrate zwischen 0 und 1 eingeben um Personendichte zu variieren (Wird mit einer Zufallsvariable zwischen 0 und 1 verglichen um spawn zu bestimmen)") + grid, door_cells, roi = Experiment(movement_method=algo, spawn_rate=float(spawn_input)) #exp_grid, roi = Experiment("dijkstra") #exp_grid.plot_grid_state(timestep=0) visualization = Visualization(grid) agent_count_list = [] fundamental_data = [] -timesteps = 10000 + grid.update_distance_maps() agents_crossed = {} # Dictionary to track agents crossing the boundary -for i in range(60): +for i in range(timesteps): grid.update(target_list=grid.target_cells, timestep=i) print("--------------------------------------------------------------------------------------------------") #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) diff --git a/Application/tests.py b/Application/tests.py index f18c937..1bc60d6 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -131,10 +131,10 @@ def RiMEA4(movement_method, spawn_rate=1): return grid, door_cells, roi -def Experiment(movement_method): +def Experiment(movement_method, spawn_rate): length = 8 height = 16 - grid = Grid(height=height, length=length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5) + grid = Grid(height=height, length=length, spawn_cells=[], obstacle_cells=[], target_cells=[], cell_size=0.5, spawn_rate=spawn_rate) target_cells = grid.select_area_by_coordinates(3.5, 0, 4.5,1) #Tische beim Einweiser obstacle_cells = grid.select_area_by_coordinates(0, 16, 3, 16) From 63c204b99fd504b9acd919c173732a01ab648588 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Mon, 30 Dec 2024 14:58:55 +0100 Subject: [PATCH 22/26] Almost there, maybe 1-2 more pushes --- Application/Cell.py | 63 ++++++--------- Application/Grid.py | 140 +++------------------------------ Application/calculations.py | 11 --- Application/run.py | 124 +++-------------------------- Application/test_components.py | 4 +- Application/tests.py | 13 +-- 6 files changed, 52 insertions(+), 303 deletions(-) delete mode 100644 Application/calculations.py diff --git a/Application/Cell.py b/Application/Cell.py index c824477..5899cb4 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -8,11 +8,12 @@ # one file per cell? class Cell: #Grundbaustein, jede Zelle kennt seine Position auf dem Grid und den entsprechenden state - def __init__(self, row, col,cell_size, state=0): + def __init__(self, row, col,cell_size, state=0, is_passable=True): self.row = row # Store the row position self.col = col # Store the column position self.state = state # 0 for dead/inactive, 1 for alive/active self.cell_size = cell_size + self.is_passable = is_passable #Methode welche die naheliegendste Target Zelle anhand einer target list (Tuples aus Koordinaten) sucht @log_decorator @@ -60,12 +61,12 @@ def get_neighbors(self, grid, radius=2): neighbors[r] = layer_neighbors # Store the current layer of neighbors return neighbors - + @log_decorator def valid_neighbors(self, neighbors): valid_neighbors = [ cell for layer in neighbors.values() for cell in layer - if cell.is_passable() + if cell.is_passable ] return valid_neighbors @log_decorator @@ -83,7 +84,7 @@ def manhattan_difference_to(self, other_cell): #Jedes Feld hat einen Potentialwert zu der naheliegendsten Target Zelle @log_decorator - + #Alte Funktion welche bein den ersten Versionen verwendet wurde. Jede zelle hatte als potenzial die negative euklidische Distanz zum nächstgelegenem Ziel def potential(self, grid, target_list): """Calculate potential based on the negative Euclidean distance to the target cell.""" target = self.find_target(target_list) @@ -95,10 +96,7 @@ def potential(self, grid, target_list): # Return the negative distance as potential return -distance - #is_passable (Kann ich von einem Agenten besucht werden). Evtl als Variable statt Methode? - # check naming - def is_passable(self): - return True # Most cells are passable by default + #Momentane Visualisierung noch in Konsole mit string repr. Für später dann Plots mit Daten aller Timesteps def __repr__(self): return "0" @@ -106,28 +104,24 @@ def __repr__(self): # Randzellen die das Feld umschliessen (etwa im Fall eines Raums mit Türen kann ein Border plaziert und danach Targets als Türen auf dem Border definiert werden) class BorderCell(Cell): - def __init__(self,row, col, cell_size): - super().__init__(state=1, row=row, col=col, cell_size=cell_size) # Border cells are always active - + def __init__(self,row, col, cell_size, is_passable=False): + super().__init__(state=1, row=row, col=col, cell_size=cell_size, is_passable=is_passable) # Border cells are always active - def is_passable(self): - return False # Border cells are impassable def __repr__(self): return 'B' #Hindernisse auf dem Feld class ObstacleCell(Cell): - def __init__(self, row, col, cell_size): - super().__init__(state=4, row=row, col=col, cell_size=cell_size) - def is_passable(self): - return False #Obstacles are impassable + def __init__(self, row, col, cell_size, is_passable=False): + super().__init__(state=4, row=row, col=col, cell_size=cell_size, is_passable=False) + def __repr__(self): return '%' # Spawn Zelle. Generiert pro Zeitschritt eine vordefinierte Anzahl agenten auf seinen Moore Nachbar Zellen class SpawnCell(Cell): - def __init__(self, row, col, cell_size, spawn_rate): - super().__init__(state=2, row=row, col=col, cell_size=cell_size) + def __init__(self, row, col, cell_size, spawn_rate, is_passable=False): + super().__init__(state=2, row=row, col=col, cell_size=cell_size, is_passable=False) self.spawn_rate = spawn_rate # Spawn cells are active #Spawn eine definierte Anzahl Agenten auf deinen Moore Nachbarn und füge die neuen Agenten der grid.agents liste Hinzu @@ -148,25 +142,23 @@ def spawn_agents(self, grid, max_agents): grid.grid[row][col] = agent grid.agents.append(agent) - def is_passable(self): - return False #Agenten können nicht Spawnzellen laufen + def __repr__(self): return 'S' # Represent spawn cells with 'S' #Ziele: Agenten bewegen sich auf die Ziele class TargetCell(Cell): - def __init__(self, row, col, cell_size): - super().__init__(state=3, row=row, col=col, cell_size=cell_size) # Target cells are active + def __init__(self, row, col, cell_size, is_passable=True): + super().__init__(state=3, row=row, col=col, cell_size=cell_size, is_passable=is_passable) # Target cells are active + - def is_passable(self): - return True # Targets are passable to agents def __repr__(self): return 'T' # Represent target cells with 'T' class Agent(Cell): - def __init__(self, row, col, cell_size): - super().__init__(state=47, row=row, col=col, cell_size=cell_size) # Set the agent state as before + def __init__(self, row, col, cell_size,is_passable=False): + super().__init__(state=47, row=row, col=col, cell_size=cell_size, is_passable=is_passable) # Set the agent state as before self.arrived = False #velocity wird später verwendet um die Gehgeschwindigkeit der einzelnen Agenten zu verändern #self.velocity = random.uniform(0.5, 1.5) @@ -188,12 +180,10 @@ def log_state(self, timestep, log_file="logs/agent_states.log"): f"Timestep: {timestep}, Agent ID: {self.id}, Position: (row: {self.row}, col: {self.col}), Route: {self.route}\n " f"State: {self.state}, Arrived: {self.arrived}, Velocity: {self.velocity}\n" ) - @property def original_velocity(self): """Access the original velocity.""" return self._original_velocity - @property def original_movement_range(self): """Access the original movement range.""" @@ -204,9 +194,6 @@ def restore_movement_range(self): self.movement_range = self._original_movement_range - def is_passable(self): - return False # Agents are impassable (to other agents, for instance) - #Momentan noch nicht in Verwendung aber wird für die Sichtlinie zum Ziel verwendet def line_of_sight(self, grid): target = self.find_nearest_target(grid) @@ -244,10 +231,7 @@ def bresenham_line(self, x1, y1, x2, y2): y1 += sy return cells - - - # @log_decorator # Bewegungslogik --> Untestehend sind die 3 Methoden über welche die Bewegung gesteuert wird. # adjust_movement_range --> Erhöht die movement_range um die momentane velocity (ich komme in t+1 nicht an, also warte ich und bei t+2 kann ich das Ziel erreichen) @@ -298,7 +282,7 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): for neighbor in valid_neighbors: distance_to_target = distance_map[neighbor.row][neighbor.col] social_penalty = precomputed_penalties[agent_index] - staying_penalty = 5 if neighbor == self else 0 + staying_penalty = 4 if neighbor == self else 0 # Reduce weight of social penalties near the target if isinstance(grid.grid[neighbor.row][neighbor.col], TargetCell): @@ -313,7 +297,7 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): print(f"Agent{self.id} velocity before penalty is {self.velocity}. Social penalty is {social_penalty}") if self.velocity >= 0.40: if social_penalty is not None: - self.velocity = self.velocity - (social_penalty / 2) + self.velocity = self.velocity - (social_penalty / 4) if self.velocity <= 0: # we dont want negative velocities self.velocity = abs(self.velocity) @@ -349,17 +333,18 @@ def movement_towards_target(self, grid, precomputed_penalties, index): return # print(new_best_move) # print(self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])) - if new_best_move != self and self.euclidean_distance_to( - grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: + if new_best_move != self and self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]]) <= self.movement_range: grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) grid.grid[new_best_move[0]][new_best_move[1]] = self self.row, self.col = new_best_move[0], new_best_move[1] # print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False + self.log_state() self.adjust_movement_range() elif new_best_move != self: self.idle = True + self.log_state() print( f"Agent{self.id}: Range {self.movement_range} to short, adjusting. Distance is{self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])}") self.adjust_movement_range() diff --git a/Application/Grid.py b/Application/Grid.py index 3e57d6f..5bf38c7 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -93,7 +93,6 @@ def select_area_by_coordinates(self, start_x, start_y, end_x, end_y): ] return selected_cells - def get_agents_in_rectangular_roi(self, start_x, start_y, end_x, end_y): """ Get all agents within a rectangular ROI defined by real-world coordinates. @@ -148,6 +147,8 @@ def is_cell_occupied(self,row,col): # row, col = self.meter_to_rowcol(x, y) cell = self.grid[row][col] return isinstance(cell, Agent) + + #Methoden für die Social Penalty berechnung (mit numpy für bessere leistung da anfänglich zu langsam) def get_agent_positions(self): """ Extract the positions of all agents as a NumPy array. @@ -175,7 +176,7 @@ def get_agent_velocities(self): #Helper Methode für Djkstra Algorithmus - def compute_distance_map(self, target): + def dijkstra_map(self, target): """ Compute the shortest path distances from the target to all cells using Dijkstra's algorithm. This version allows setting distances on Agent cells but prohibits Obstacle cells. @@ -231,7 +232,7 @@ def compute_distance_map(self, target): #Helper Methode für Flood Fill, returned Distance map - def flood_fill(self, target_row, target_col, target_state): + def flood_fill_map(self, target_row, target_col, target_state): """ Perform reverse flood-fill starting from the target (target_row, target_col). Computes a distance map where cells closer to the target have smaller values. @@ -250,7 +251,7 @@ def flood_fill(self, target_row, target_col, target_state): target_cell = self.grid[target_row][target_col] # Early exit if the target cell doesn't match the target state or is impassable - if target_cell.state != target_state or not target_cell.is_passable(): + if target_cell.state != target_state or not target_cell.is_passable: raise ValueError("Target cell is invalid or impassable.") # Initialize the distance map with infinity @@ -291,13 +292,14 @@ def flood_fill(self, target_row, target_col, target_state): return distance_map - # Display Methode: Momentan noch in Konsole, später mit Plots + # Konsolenausgabe def display(self): """Print the current state of the grid""" for row in self.grid: print(" ".join(str(cell) for cell in row)) print() + #Berechne Social Penalties für gesamtes Grid def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5): """ Compute social penalties for all agents using a vectorized approach. @@ -341,36 +343,7 @@ def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5 - #Update funktion: Wir müssen nur die Agenten bewegen und die Spawns für den nächsten Zeitschritt durchführen - # def update(self, target_list, timestep): - # - # if timestep == 0: - # self.update_distance_maps() - # - # #Bewege Agenten - # for agent in self.agents: - # if agent.arrived == True: - # self.agents.remove(agent) - # - # #print(agent) - # if self.movement_method == "floodfill": - # agent.movement_towards_target(self) - # elif self.movement_method == "dijkstra": - # agent.movement_towards_target(self) # Pass the grid instance - # - # agent.log_state(timestep) - # densities, speeds, flows = self.calculate_density_speed_flow() - # self.density_data.append(densities) - # self.speed_data.append(speeds) - # self.flow_data.append(flows) - # #Spawne Agenten ( - # for row, col in self.spawn_cells: - # cell = self.grid[row][col] - # if isinstance(cell, SpawnCell): # Check if the cell at (row, col) is a SpawnCell - # max_agents = 1 # Adjust the number of agents to spawn as needed - # cell.spawn_agents(self, max_agents) - # - # self.log_grid_state(timestep) + def update(self, target_list, timestep): """ Update the grid by moving agents and handling arrivals. @@ -401,7 +374,7 @@ def update(self, target_list, timestep): #print(f"Removing agent {agent} from ({agent.row}, {agent.col})") self.agents.remove(agent) - # Restore target cell explicitly + # Restore target cell explicitly (We Had alot of issues with Targets being overwritten, so we try to catch it at multiple levels) if (agent.row, agent.col) in target_list: self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) else: @@ -413,7 +386,8 @@ def update(self, target_list, timestep): if isinstance(cell, SpawnCell): max_agents = 1 cell.spawn_agents(self, max_agents) - + if timestep ==0: + self.create_logfile() self.log_grid_state(timestep) # def update(self, target_list, timestep): @@ -461,71 +435,11 @@ def update(self, target_list, timestep): # max_agents = 1 # cell.spawn_agents(self, max_agents) # self.log_grid_state(timestep) - #def update(self, target_list, timestep): -# """ -# Update the grid by moving agents and handling arrivals. -# """ -# if timestep == 0: -# self.update_distance_maps() - -# # Precompute penalties for agents -# precomputed_penalties = self.compute_social_penalties() - -# # List to track agents to remove -# agents_to_remove = [] - -# for agent in self.agents[:]: # Iterate over a copy of the agents list -# if agent.arrived: -# agents_to_remove.append(agent) -# continue - -# # Use the agent's own movement logic -# print(f"moving agent {agent.id}") -# agent.movement_towards_target(self) - -# # Remove agents that have arrived -# for agent in agents_to_remove: -# print(f"Removing agent {agent} from ({agent.row}, {agent.col})") -# self.agents.remove(agent) - -# # Restore target cell explicitly -# if (agent.row, agent.col) in target_list: -# self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) -# else: -# self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) - -# # Spawn new agents -# for row, col in self.spawn_cells: -# cell = self.grid[row][col] -# if isinstance(cell, SpawnCell): -# max_agents = 1 -# cell.spawn_agents(self, max_agents) - -# self.log_grid_state(timestep) - - - - - - - - - - - - def calculate_movement(self,agent): - if not agent.arrived: - # Store the agent's movement decision (current and next position) - return (agent, agent.movement_decision(self)) # New method to compute move - return None - - - def update_distance_maps(self): """ Compute and store both flood-fill and Dijkstra-based distance maps for all targets. @@ -539,11 +453,11 @@ def update_distance_maps(self): # Flood-fill distance map if self.movement_method == "floodfill": - self.flood_fill_distance_maps[(row, col)] = self.flood_fill(row, col, target_state=3) + self.flood_fill_distance_maps[(row, col)] = self.flood_fill_map(row, col, target_state=3) elif self.movement_method == "dijkstra": # Dijkstra distance map - self.dijkstra_distance_maps[(row, col)] = self.compute_distance_map((row, col)) + self.dijkstra_distance_maps[(row, col)] = self.dijkstra_map((row, col)) def plot_distance_map(self, distance_map, title="Distance Map"): """ @@ -704,35 +618,7 @@ def calculate_fundamental_diagram(self, boundary, timestep, agents_crossed, area print(f"Timestep {timestep}: Density={density:.2f}, Flow={flow:.2f}, AvgSpeed={avg_speed:.2f}") return results - def plot_fundamental_diagram(self, data): - """ - Plot the Fundamental Diagram based on collected data. - Parameters: - data (list): A list of dictionaries containing density, flow, and average speed values. - """ - densities = [entry["density"] for entry in data] - flows = [entry["flow"] for entry in data] - avg_speeds = [entry["avg_speed"] for entry in data] - - fig, ax1 = plt.subplots() - - # Plot Flow vs. Density - ax1.plot(densities, flows, 'b-o', label="Flow") - ax1.set_xlabel("Density (agents/m^2)") - ax1.set_ylabel("Flow (agents/s)", color="blue") - ax1.tick_params(axis='y', labelcolor="blue") - - # Plot Avg Speed vs. Density - ax2 = ax1.twinx() - ax2.plot(densities, avg_speeds, 'r-s', label="Average Speed") - ax2.set_ylabel("Average Speed (m/s)", color="red") - ax2.tick_params(axis='y', labelcolor="red") - - fig.tight_layout() - plt.title("Fundamental Diagram") - plt.show() - def create_logfile(self): diff --git a/Application/calculations.py b/Application/calculations.py deleted file mode 100644 index 8db66db..0000000 --- a/Application/calculations.py +++ /dev/null @@ -1,11 +0,0 @@ -import numpy as np - -def euclidian_distance(cellAgent, cellTarget): - x1, y1 = cellAgent - x2, y2 = cellTarget - sum = (y1-x1)^2+(y2-x2)^2 - if sum >= 0: - result = np.sqrt(sum) - return result - else: - return print("Error - negative value under square root") diff --git a/Application/run.py b/Application/run.py index 6a058a0..9512f6d 100644 --- a/Application/run.py +++ b/Application/run.py @@ -7,13 +7,7 @@ import matplotlib #This line is needed for the plots to render in Pychamr Sciplot-View matplotlib.use("Qt5Agg") -#Using the Maps from tests build a grid. every map returns a grid, door_cells (used as boundary zones for flow measurements) and roi (region used to calculate agent density) -#grid, door_cells, roi = RiMEA9(2,"dijkstra") -#grid, door_cells, roi = RiMEA9(1, "dijsktra") -#grid, door_cells, roi = RiMEA9(3, "dijkstra") -#grid, door_cells, roi = RiMEA9(4, "dijkstra") -#grid, door_cells, roi = RiMEA4(movement_method="dijkstra", spawn_rate=0.6) -#grid, door_cells, roi = Experiment("dijkstra") +#Abschnitt in dem Die simulations Parameter angegeben werden. time_input = input("Bitte geben sie die Anzahl Zeitschritte an (ein Zeitschritt ist 1 Sekunde)") timesteps = int(time_input) user_input = input("Bitte geben sie an welchen Test (RiMEA4, RiMEA9 oder Experiment) sie durchführen wollen") @@ -28,7 +22,6 @@ algo = "dijkstra" #Standardwert ist dijkstra doors = int(door_input) grid, door_cells, roi = RiMEA9(movement_method=algo, Doors=doors) - elif user_input == "RiMEA4": pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": @@ -39,8 +32,6 @@ algo = "dijkstra" #Standardwert ist dijkstra spawn_input = input("Bitte spawnrate zwischen 0 und 1 eingeben um Personendichte zu variieren (Wird mit einer Zufallsvariable zwischen 0 und 1 verglichen um spawn zu bestimmen)") grid, door_cells, roi = RiMEA4(movement_method=algo,spawn_rate=float(spawn_input)) - - elif user_input == "Experiment": pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": @@ -51,118 +42,27 @@ algo = "dijkstra" # Standardwert ist dijkstra spawn_input = input("Bitte spawnrate zwischen 0 und 1 eingeben um Personendichte zu variieren (Wird mit einer Zufallsvariable zwischen 0 und 1 verglichen um spawn zu bestimmen)") grid, door_cells, roi = Experiment(movement_method=algo, spawn_rate=float(spawn_input)) -#exp_grid, roi = Experiment("dijkstra") -#exp_grid.plot_grid_state(timestep=0) +#################################################### +#Visualisierung initialisieren und Variablen für Resultate anlegen visualization = Visualization(grid) agent_count_list = [] fundamental_data = [] -grid.update_distance_maps() +grid.update_distance_maps()# Befor wir die Simulation starten setzen wir bereits die Distance-Maps für Dijkstra/Floodfill agents_crossed = {} # Dictionary to track agents crossing the boundary for i in range(timesteps): grid.update(target_list=grid.target_cells, timestep=i) + #Print statement trent einzelne Zeitschritte von einander (für einfacheres debugging) print("--------------------------------------------------------------------------------------------------") - #Select current area inside of Hallway or Room to see how many agents are still inside (for FundamentalDiagram) - area = grid.select_area_by_coordinates(roi[0], roi[1], roi[2], roi[3]) - if i == 0: - initial_count = len(grid.agents) - print(f"init{initial_count}") + #Visualisierung aktueller grid-state visualization.plot_grid_state(i) + #Bereich für Density berechnung von aktuelem Grid state wählen + area = grid.select_area_by_coordinates(roi[0], roi[1], roi[2], roi[3]) #grid.plot_grid_state(i) - plt.pause(1) - #print(f"len_agentscrossed:{len(agents_crossed)}") - # Calculate density, speed, and flow: Set Boundarys to be doorcells (as defined in Rimea), pass current timestep, already_crossed agents and the area for density calculation + + #Berechnung fürs Fundamentaldiagram (Aktuelle Agenten dichte, Fluss und durchschnittsgeschwindigkeit) fd_results = grid.calculate_fundamental_diagram(door_cells, i, agents_crossed, area) fundamental_data.append(fd_results) - - - # Update previous positions for the next timestep - agent_count = len(grid.agents) - agent_count_list.append(agent_count) - if agent_count == 0: - print("All agents have reached their targets. Stopping simulation.") - break - - -plot = visualization.plot_fundamental_diagram(fundamental_data) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#plot_fundamental_diagram_with_dual_axis(densities, speeds, flows) -#plt.figure(figsize=(10,5)) - -#plt.subplot(1, 4, 1) -#plt.plot(agent_count_list) -#plt.title('Anzahl Agenten über Zeit') -#plt.xlabel('Zeitschritt') -#plt.ylabel('Anzahl Agenten') - -#plt.subplot(1, 4, 2) -#plt.plot(average_distance_list) -#plt.title('Mittlere Distanz Agenten zum Ziel') -#plt.xlabel('Zeitschritt') -#plt.ylabel('Distanz') - -#plt.subplot(1, 4, 3) -#plt.plot(average_speed_list) -#plt.title('Mittlere Geschwindigkeit der Agenten') -#plt.xlabel('Zeitschritt') -#plt.ylabel('Geschwindigkeit') - -#plt.subplot(1, 4, 4) -#if len(density_list) > len(average_speed_list): -# plt.plot(density_list[:-1], average_speed_list) -#else: -# plt.plot(density_list, average_speed_list[:len(density_list)]) -#plt.title('Fundamental Diagram of Traffic Flow') -#plt.xlabel('Dichte') -#plt.ylabel('Mittlere Geschwindigkeit') -# -#plt.tight_layout() -#plt.show() - -# visualization.animate_grid_states(timesteps) \ No newline at end of file +#Nachdem die Simulation durchgeführt wurde plotten wir noch die Ergebnisse der Fundamentaldiagram Berechnungen +visualization.plot_fundamental_diagram(fundamental_data) \ No newline at end of file diff --git a/Application/test_components.py b/Application/test_components.py index 9fb2571..b33231f 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -231,7 +231,7 @@ def test_flood_fill_distance_map(self): # Compute the distance map target_row, target_col = 2, 2 - distance_map = grid.flood_fill(target_row, target_col, target_state=3) + distance_map = grid.flood_fill_map(target_row, target_col, target_state=3) # Expected distance map expected_map = [ @@ -370,7 +370,7 @@ class TestAgentMovementRange(unittest.TestCase): def setUp(self): # Create a Grid instance - self.cell_size = 1.0 + self.cell_size = 1 self.length = 5 # 5 cells wide self.height = 5 # 5 cells tall diff --git a/Application/tests.py b/Application/tests.py index 1bc60d6..37d8d5c 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -5,16 +5,11 @@ def room_square(): height = 50 length = 50 - spawn_cells = [(0, 1), (0, 2), (0, 3), (0, 4)] obstacle_cells = [(5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), (5, 7), (5, 8), (5, 9), (5, 10), (5, 11), (5, 12), (5, 13), (5, 14), (5, 15), (5, 16), (5, 17), (5, 18), (5, 19), (5, 20), (5, 21), (5, 22), (5, 23), (5, 24), (5, 25), (5, 26), (5, 27), (5, 28), (5, 29), (5, 30), (5, 31), (5, 32), (5, 33), (5, 34), (5, 35), (5, 36), (5, 37), (5, 38), (5, 39), (5, 40), (5, 41), (5, 42), (5, 43), (5, 44), (5, 45), (5, 46), (5, 47), (5, 48), (5, 49), (2, 4), (3, 25)] - target_cells = [(0, 49), (1, 49), (2, 49), (3, 49), (4, 49)] - + target_cells = [(0, 49), (1, 49), (2, 49), (3, 49), (4, 49)] grid = Grid(height=height, length=length, spawn_cells=spawn_cells, obstacle_cells=obstacle_cells, target_cells=target_cells, cell_size=0.5) - return grid - - def ChickenTest(movement_method): height = 20 length = 20 @@ -43,8 +38,6 @@ def ChickenTest(movement_method): return grid - - def RiMEA9(Doors, movement_method): room_height = 20 room_length = 30 @@ -104,8 +97,6 @@ def RiMEA9(Doors, movement_method): return grid, door_cells, roi - - def RiMEA4(movement_method, spawn_rate=1): height = 10 length = 100 @@ -129,8 +120,6 @@ def RiMEA4(movement_method, spawn_rate=1): grid.place_empty_cell(cell.row, cell.col) roi = (0,0,corridor_length,height) return grid, door_cells, roi - - def Experiment(movement_method, spawn_rate): length = 8 height = 16 From ed28bc0ff5534475ce74afc707356b36001902d6 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Mon, 30 Dec 2024 15:19:26 +0100 Subject: [PATCH 23/26] Almost there, maybe 1-2 more pushes --- Application/Cell.py | 18 +++++------ Application/Grid.py | 59 ++++++++++------------------------ Application/run.py | 2 +- Application/test_components.py | 7 ++-- 4 files changed, 29 insertions(+), 57 deletions(-) diff --git a/Application/Cell.py b/Application/Cell.py index 5899cb4..ce7ef70 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -88,8 +88,6 @@ def manhattan_difference_to(self, other_cell): def potential(self, grid, target_list): """Calculate potential based on the negative Euclidean distance to the target cell.""" target = self.find_target(target_list) - #print(f"TARGET:{target[0]},{target[1]}") - #print(f"SELF: {self.row}, {self.col}") # Euclidean distance calculation ( Check if row and col are right) distance = self.euclidean_distance_to(target) @@ -103,13 +101,13 @@ def __repr__(self): # Randzellen die das Feld umschliessen (etwa im Fall eines Raums mit Türen kann ein Border plaziert und danach Targets als Türen auf dem Border definiert werden) -class BorderCell(Cell): - def __init__(self,row, col, cell_size, is_passable=False): - super().__init__(state=1, row=row, col=col, cell_size=cell_size, is_passable=is_passable) # Border cells are always active +#class BorderCell(Cell): + # def __init__(self,row, col, cell_size, is_passable=False): + # super().__init__(state=1, row=row, col=col, cell_size=cell_size, is_passable=is_passable) # Border cells are always active - def __repr__(self): - return 'B' + # def __repr__(self): + # return 'B' #Hindernisse auf dem Feld class ObstacleCell(Cell): @@ -313,7 +311,7 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): return (best_move.row, best_move.col) if best_move != self else None - def movement_towards_target(self, grid, precomputed_penalties, index): + def movement_towards_target(self, grid, precomputed_penalties, index, timestep): if self.arrived: return @@ -339,12 +337,12 @@ def movement_towards_target(self, grid, precomputed_penalties, index): self.row, self.col = new_best_move[0], new_best_move[1] # print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False - self.log_state() + self.log_state(timestep) self.adjust_movement_range() elif new_best_move != self: self.idle = True - self.log_state() + self.log_state(timestep) print( f"Agent{self.id}: Range {self.movement_range} to short, adjusting. Distance is{self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])}") self.adjust_movement_range() diff --git a/Application/Grid.py b/Application/Grid.py index 5bf38c7..1e81508 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -1,5 +1,5 @@ -from Cell import Cell, BorderCell, SpawnCell, Agent, TargetCell, ObstacleCell +from Cell import Cell, SpawnCell, Agent, TargetCell, ObstacleCell import random import os @@ -93,25 +93,7 @@ def select_area_by_coordinates(self, start_x, start_y, end_x, end_y): ] return selected_cells - def get_agents_in_rectangular_roi(self, start_x, start_y, end_x, end_y): - """ - Get all agents within a rectangular ROI defined by real-world coordinates. - Parameters: - start_x, start_y (float): Bottom-left corner of the rectangle in meters. - end_x, end_y (float): Top-right corner of the rectangle in meters. - Returns: - List[Agent]: List of agents within the ROI. - """ - selected_cells = self.select_area_by_coordinates(start_x, start_y, end_x, end_y) - agents_in_roi = [cell for cell in selected_cells if isinstance(cell, Agent)] - return agents_in_roi - #Plaziere Wand um Feld - def place_border(self): - """Place a border around the grid""" - for r in range(self.rows): - for c in range(self.cols): - if r == 0 or r == self.rows - 1 or c == 0 or c == self.cols - 1: - self.grid[r][c] = BorderCell(cell_size=self.cell_size) + #Die Untenstehenden methoden erlauben eine Interaktion mit dem Grid ausserhalb der initialisierung def place_spawn_cell(self, row,col): """Place a spawn cell at a specific position on the grid""" @@ -165,16 +147,16 @@ def get_agent_arrival_status(self): """ return np.array([agent.arrived for agent in self.agents]) - def get_agent_velocities(self): - """ - Extract the velocities of all agents. - Returns: - np.ndarray: Array of shape (num_agents,) with agent velocities. - """ - return np.array([agent.velocity for agent in self.agents]) + # def get_agent_velocities(self): + # """ + # Extract the velocities of all agents. + # Returns: + # np.ndarray: Array of shape (num_agents,) with agent velocities. + # """ + # return np.array([agent.velocity for agent in self.agents]) - #Helper Methode für Djkstra Algorithmus + #Methode für Djkstra Algorithmus def dijkstra_map(self, target): """ @@ -230,7 +212,7 @@ def dijkstra_map(self, target): return distance_map - #Helper Methode für Flood Fill, returned Distance map + #Methode für Flood Fill, returned Distance map def flood_fill_map(self, target_row, target_col, target_state): """ @@ -311,17 +293,15 @@ def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5 np.ndarray: Social penalties for all agents. """ positions = grid.get_agent_positions() # Shape: (num_agents, 2) - # If positions is 1D, reshape it + # If positions is 1D, reshape it --> Maybe this part here causes some of the Agents to get locked in place, not sure tho if positions.ndim == 1: positions = positions.reshape(-1, 2) - #print(f"Position : None :{positions[:, None, :]}") - #print(f"Position None ::{positions[None, :, :]}") arrived = grid.get_agent_arrival_status() # Shape: (num_agents,) num_agents = positions.shape[0] - #print(num_agents) + # Compute pairwise Euclidean distances (broadcasting) - deltas = positions[:, None, :] - positions[None, :, :] # Shape: (num_agents, num_agents, 2) - distances = np.linalg.norm(deltas, axis=2) # Shape: (num_agents, num_agents) + deltas = positions[:, None, :] - positions[None, :, :] # Expected Shape: (num_agents, num_agents, 2) + distances = np.linalg.norm(deltas, axis=2) # Expected Shape: (num_agents, num_agents) # Apply cutoff distance (set penalties to 0 beyond this distance) mask = (distances <= cutoff_distance) & ~np.eye(num_agents, dtype=bool) # Ignore self-distances @@ -334,10 +314,6 @@ def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5 # Sum penalties for each agent total_penalties = penalties.sum(axis=1) # Shape: (num_agents,) - # Add a small penalty for staying in place - #stay_penalty = 0.5 - #total_penalties += stay_penalty - return total_penalties @@ -364,7 +340,7 @@ def update(self, target_list, timestep): # Call the agent's movement logic #print(f"Agent {agent.id} moving from ({agent.row}, {agent.col})") - agent.movement_towards_target(self, precomputed_penalties, i) + agent.movement_towards_target(self, precomputed_penalties, i, timestep=timestep) # Debugging #print(f"Agent {agent.id} now at ({agent.row}, {agent.col})") @@ -386,8 +362,7 @@ def update(self, target_list, timestep): if isinstance(cell, SpawnCell): max_agents = 1 cell.spawn_agents(self, max_agents) - if timestep ==0: - self.create_logfile() + self.log_grid_state(timestep) # def update(self, target_list, timestep): diff --git a/Application/run.py b/Application/run.py index 9512f6d..3dd84a9 100644 --- a/Application/run.py +++ b/Application/run.py @@ -1,5 +1,5 @@ from Grid import Grid -from Cell import Cell, SpawnCell, BorderCell, ObstacleCell, Agent, TargetCell +from Cell import Cell, SpawnCell, ObstacleCell, Agent, TargetCell import matplotlib.pyplot as plt import numpy as np from Grid import Grid, Visualization diff --git a/Application/test_components.py b/Application/test_components.py index b33231f..bf87865 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -394,8 +394,7 @@ def setUp(self): def test_movement_range_increases(self): # Set initial conditions - # self.agent.movement_range = 0.4 # Start with this range - #self.agent.velocity = 0.4 # Velocity determines the increment + original_range = self.agent._original_movement_range original_velocity = self.agent._original_velocity # Debug initial values @@ -404,7 +403,7 @@ def test_movement_range_increases(self): precomputed_penalties = self.grid.compute_social_penalties() # Simulate insufficient movement range - self.agent.movement_towards_target(self.grid, precomputed_penalties, 0) + self.agent.movement_towards_target(self.grid, precomputed_penalties, 0, 0) # Assert no movement happened due to insufficient range self.assertEqual(self.agent.row, 0) @@ -417,7 +416,7 @@ def test_movement_range_increases(self): print(f"New Movement Range: {self.agent.movement_range} (Expected: {expected_range})") self.assertAlmostEqual(self.agent.movement_range, expected_range, delta=0.1) - self.agent.movement_towards_target(self.grid, precomputed_penalties, 0) + self.agent.movement_towards_target(self.grid, precomputed_penalties, 0, 0) From 1823434c0c8f567ce6b79fa8638e88a71256b7d3 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Mon, 30 Dec 2024 15:51:34 +0100 Subject: [PATCH 24/26] Almost there, maybe 1-2 more pushes --- Application/Grid.py | 2 +- Application/tests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/Grid.py b/Application/Grid.py index 1e81508..d2e9dce 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -282,7 +282,7 @@ def display(self): print() #Berechne Social Penalties für gesamtes Grid - def compute_social_penalties(grid, cutoff_distance=2.0, penalty_decay_factor=0.5): + def compute_social_penalties(grid, cutoff_distance=1.5, penalty_decay_factor=0.85): """ Compute social penalties for all agents using a vectorized approach. Parameters: diff --git a/Application/tests.py b/Application/tests.py index 37d8d5c..46142dc 100644 --- a/Application/tests.py +++ b/Application/tests.py @@ -107,7 +107,7 @@ def RiMEA4(movement_method, spawn_rate=1): # Door_cells sind in diesem Fall eine Zelle vor Ende des Ganges door_cells = grid.select_area_by_coordinates(corridor_length-1, 0, corridor_length-1, height) obstacle_placement = grid.select_area_by_coordinates(24, 4, 25, 5) - obstacle_placement += grid.select_area_by_coordinates(74, 4, 75, 7) + obstacle_placement += grid.select_area_by_coordinates(74, 2, 75, 5) for i in range(0, grid.rows-1): grid.place_spawn_cell(i, 0) From d3ab801113a3b692359cb79ee2f3500eb33c20a7 Mon Sep 17 00:00:00 2001 From: elFleppo Date: Tue, 31 Dec 2024 10:16:23 +0100 Subject: [PATCH 25/26] Final push (probably) --- Application/Cell.py | 17 ++---- Application/Grid.py | 94 +++++++++++++++---------------- Application/packages.txt | 110 ++++++++++++++++++++++++++++++++++++ Application/run.py | 9 +-- README.md | 117 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 283 insertions(+), 64 deletions(-) create mode 100644 Application/packages.txt diff --git a/Application/Cell.py b/Application/Cell.py index ce7ef70..1aae968 100644 --- a/Application/Cell.py +++ b/Application/Cell.py @@ -206,7 +206,7 @@ def line_of_sight(self, grid): return True # Line of sight is clear - #bresenham_line fürht eine Liste aller Zellen die auf der line_of_sight zum Ziel sind (um Sichtkontakt zum Ziel zu prüfen) + #bresenham_line fürht eine Liste aller Zellen die auf der line_of_sight zum Ziel sind (um Sichtkontakt zum Ziel zu prüfen). Könnte def bresenham_line(self, x1, y1, x2, y2): """Bresenham's Line Algorithm to calculate all cells between two points.""" cells = [] @@ -241,6 +241,9 @@ def adjust_movement_range(self): # print(self.idle) if self.idle: # print(f"Original Range{self._original_movement_range} and Original velocity{self._original_velocity}, adjusted velocity was {self.velocity} and movement was {self.movement_range}") + if self.velocity <= self.cell_size: + self.velocity += self.cell_size + self.movement_range += self.velocity print("MOVEMENT INCREASE") @@ -310,7 +313,6 @@ def movement_decision(self, grid, precomputed_penalties, agent_index): return (best_move.row, best_move.col) if best_move != self else None - def movement_towards_target(self, grid, precomputed_penalties, index, timestep): if self.arrived: return @@ -335,25 +337,18 @@ def movement_towards_target(self, grid, precomputed_penalties, index, timestep): grid.grid[self.row][self.col] = Cell(self.row, self.col, cell_size=self.cell_size) grid.grid[new_best_move[0]][new_best_move[1]] = self self.row, self.col = new_best_move[0], new_best_move[1] + self.route.append((new_best_move[0], new_best_move[1])) # print(f"Agent {self.id} moved to ({self.row}, {self.col})") self.idle = False self.log_state(timestep) self.adjust_movement_range() - elif new_best_move != self: + elif new_best_move != self and self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]]) > self.movement_range: self.idle = True self.log_state(timestep) print( f"Agent{self.id}: Range {self.movement_range} to short, adjusting. Distance is{self.euclidean_distance_to(grid.grid[new_best_move[0]][new_best_move[1]])}") self.adjust_movement_range() - - - - - - - - def __repr__(self): return 'A' # Represent agent with 'A' diff --git a/Application/Grid.py b/Application/Grid.py index d2e9dce..fc04aed 100644 --- a/Application/Grid.py +++ b/Application/Grid.py @@ -282,7 +282,7 @@ def display(self): print() #Berechne Social Penalties für gesamtes Grid - def compute_social_penalties(grid, cutoff_distance=1.5, penalty_decay_factor=0.85): + def compute_social_penalties(grid, cutoff_distance=1.5, penalty_decay_factor=0.5): """ Compute social penalties for all agents using a vectorized approach. Parameters: @@ -365,51 +365,51 @@ def update(self, target_list, timestep): self.log_grid_state(timestep) -# def update(self, target_list, timestep): -# """ -# Update the grid by moving agents and handling arrivals. -# """ -# if timestep == 0: -# self.update_distance_maps() -# # Compute social penalties -# precomputed_penalties = self.compute_social_penalties() -# # List to track agents to remove -# agents_to_remove = [] -# for i, agent in enumerate(self.agents[:]): # Iterate over a copy of the agents list -# if agent.arrived: -# agents_to_remove.append(agent) -# continue -# new_position = agent.movement_decision(self, precomputed_penalties, i) -# if new_position: -# current_row, current_col = agent.row, agent.col -# new_row, new_col = new_position -# # Update grid: Move the agent -# if not isinstance(self.grid[new_row][new_col], TargetCell): -# self.grid[new_row][new_col] = agent -# # Restore the current cell -# self.grid[current_row][current_col] = ( -# TargetCell(current_row, current_col, self.cell_size) -# if (current_row, current_col) in target_list -# else Cell(current_row, current_col, self.cell_size) -# ) -# # Update agent position -# agent.row, agent.col = new_row, new_col -# # Remove agents that have arrived -# for agent in agents_to_remove: -# print(f"Removing agent {agent} from ({agent.row}, {agent.col})") -# self.agents.remove(agent) -# # Restore target cell explicitly -# if (agent.row, agent.col) in target_list: -# self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) -# else: -# self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) -# # Spawn new agents -# for row, col in self.spawn_cells: -# cell = self.grid[row][col] -# if isinstance(cell, SpawnCell): -# max_agents = 1 -# cell.spawn_agents(self, max_agents) -# self.log_grid_state(timestep) + def update_no_penalties(self, target_list, timestep): + """ + Update the grid by moving agents and handling arrivals. + """ + if timestep == 0: + self.update_distance_maps() + # Compute social penalties + precomputed_penalties = self.compute_social_penalties() + # List to track agents to remove + agents_to_remove = [] + for i, agent in enumerate(self.agents[:]): # Iterate over a copy of the agents list + if agent.arrived: + agents_to_remove.append(agent) + continue + new_position = agent.movement_decision(self, precomputed_penalties, i) + if new_position: + current_row, current_col = agent.row, agent.col + new_row, new_col = new_position + # Update grid: Move the agent + if not isinstance(self.grid[new_row][new_col], TargetCell): + self.grid[new_row][new_col] = agent + # Restore the current cell + self.grid[current_row][current_col] = ( + TargetCell(current_row, current_col, self.cell_size) + if (current_row, current_col) in target_list + else Cell(current_row, current_col, self.cell_size) + ) + # Update agent position + agent.row, agent.col = new_row, new_col + # Remove agents that have arrived + for agent in agents_to_remove: + print(f"Removing agent {agent} from ({agent.row}, {agent.col})") + self.agents.remove(agent) + # Restore target cell explicitly + if (agent.row, agent.col) in target_list: + self.grid[agent.row][agent.col] = TargetCell(agent.row, agent.col, self.cell_size) + else: + self.grid[agent.row][agent.col] = Cell(agent.row, agent.col, self.cell_size) + # Spawn new agents + for row, col in self.spawn_cells: + cell = self.grid[row][col] + if isinstance(cell, SpawnCell): + max_agents = 1 + cell.spawn_agents(self, max_agents) + self.log_grid_state(timestep) @@ -621,7 +621,7 @@ def plot_grid_state(self, timestep): 4: 'gray' } - agent_color_map = {0: 'lightblue', 1: 'darkblue'} + agent_color_map = {0: 'purple', 1: 'darkblue'} cmap = mcolors.ListedColormap([custom_colors[key] for key in sorted(custom_colors.keys())]) #erstelle eigne Colormap durch obige Farbwahl bounds = list(sorted(custom_colors.keys())) + [max(custom_colors.keys()) + 1] diff --git a/Application/packages.txt b/Application/packages.txt new file mode 100644 index 0000000..b584856 --- /dev/null +++ b/Application/packages.txt @@ -0,0 +1,110 @@ +# Name Version Build Channel +asttokens 2.4.1 pyhd8ed1ab_0 conda-forge +blas 1.0 mkl +bottleneck 1.4.2 py312h4b0e54e_0 +brotli 1.0.9 h2bbff1b_8 +brotli-bin 1.0.9 h2bbff1b_8 +bzip2 1.0.8 h2bbff1b_6 +ca-certificates 2024.9.24 haa95532_0 +colorama 0.4.6 pyhd8ed1ab_0 conda-forge +comm 0.2.2 pyhd8ed1ab_0 conda-forge +contourpy 1.2.0 py312h59b6b97_0 +cycler 0.11.0 pyhd3eb1b0_0 +debugpy 1.6.7 py312hd77b12b_0 +decorator 5.1.1 pyhd8ed1ab_0 conda-forge +exceptiongroup 1.2.2 pyhd8ed1ab_0 conda-forge +executing 2.1.0 pyhd8ed1ab_0 conda-forge +expat 2.6.3 h5da7b33_0 +fonttools 4.51.0 py312h2bbff1b_0 +freetype 2.12.1 ha860e81_0 +icc_rt 2022.1.0 h6049295_2 +icu 73.1 h6c2663c_0 +importlib-metadata 8.5.0 pyha770c72_0 conda-forge +intel-openmp 2023.1.0 h59b6b97_46320 +ipykernel 6.29.5 pyh4bbf305_0 conda-forge +ipython 8.29.0 pyh7428d3b_0 conda-forge +jedi 0.19.2 pyhff2d567_0 conda-forge +joblib 1.4.2 py312haa95532_0 +jpeg 9e h827c3e9_3 +jupyter_client 8.6.3 pyhd8ed1ab_0 conda-forge +jupyter_core 5.7.2 py312haa95532_0 +kiwisolver 1.4.4 py312hd77b12b_0 +krb5 1.20.1 h5b6d351_0 +lcms2 2.12 h83e58a3_0 +lerc 3.0 hd77b12b_0 +libbrotlicommon 1.0.9 h2bbff1b_8 +libbrotlidec 1.0.9 h2bbff1b_8 +libbrotlienc 1.0.9 h2bbff1b_8 +libclang 14.0.6 default_hb5a9fac_1 +libclang13 14.0.6 default_h8e68704_1 +libdeflate 1.17 h2bbff1b_1 +libffi 3.4.4 hd77b12b_1 +libpng 1.6.39 h8cc25b3_0 +libpq 12.20 h70ee33d_0 +libsodium 1.0.18 h8d14728_1 conda-forge +libtiff 4.5.1 hd77b12b_0 +libwebp-base 1.3.2 h3d04722_1 +lz4-c 1.9.4 h2bbff1b_1 +matplotlib 3.9.2 py312haa95532_0 +matplotlib-base 3.9.2 py312hbdc63d0_0 +matplotlib-inline 0.1.7 pyhd8ed1ab_0 conda-forge +mkl 2023.1.0 h6b88ed4_46358 +mkl-service 2.4.0 py312h2bbff1b_1 +mkl_fft 1.3.11 py312h827c3e9_0 +mkl_random 1.2.8 py312h0158946_0 +nest-asyncio 1.6.0 pyhd8ed1ab_0 conda-forge +numexpr 2.10.1 py312h4cd664f_0 +numpy 1.26.4 py312hfd52020_0 +numpy-base 1.26.4 py312h4dde369_0 +openjpeg 2.5.2 hae555c5_0 +openssl 3.0.15 h827c3e9_0 +packaging 24.1 py312haa95532_0 +pandas 2.2.2 py312h0158946_0 +parso 0.8.4 pyhd8ed1ab_0 conda-forge +patsy 0.5.6 py312haa95532_0 +pickleshare 0.7.5 py_1003 conda-forge +pillow 10.4.0 py312h827c3e9_0 +pip 24.2 py312haa95532_0 +platformdirs 4.3.6 pyhd8ed1ab_0 conda-forge +ply 3.11 py312haa95532_1 +prompt-toolkit 3.0.48 pyha770c72_0 conda-forge +psutil 5.9.0 py312h2bbff1b_0 +pure_eval 0.2.3 pyhd8ed1ab_0 conda-forge +pybind11-abi 5 hd3eb1b0_0 +pygments 2.18.0 pyhd8ed1ab_0 conda-forge +pyparsing 3.2.0 py312haa95532_0 +pyqt 5.15.10 py312hd77b12b_0 +pyqt5-sip 12.13.0 py312h2bbff1b_0 +python 3.12.7 h14ffc60_0 +python-dateutil 2.9.0post0 py312haa95532_2 +python-tzdata 2023.3 pyhd3eb1b0_0 +pytz 2024.1 py312haa95532_0 +pywin32 305 py312h2bbff1b_0 +pyzmq 25.1.2 py312hd77b12b_0 +qt-main 5.15.2 h19c9488_10 +scikit-learn 1.5.1 py312h0158946_0 +scipy 1.13.1 py312hbb039d4_0 +seaborn 0.13.2 py312haa95532_0 +setuptools 75.1.0 py312haa95532_0 +sip 6.7.12 py312hd77b12b_0 +six 1.16.0 pyhd3eb1b0_1 +sqlite 3.45.3 h2bbff1b_0 +stack_data 0.6.2 pyhd8ed1ab_0 conda-forge +statsmodels 0.14.2 py312h4b0e54e_0 anaconda +tbb 2021.8.0 h59b6b97_0 +threadpoolctl 3.5.0 py312hfc267ef_0 +tk 8.6.14 h0416ee5_0 +tornado 6.4.1 py312h827c3e9_0 +traitlets 5.14.3 pyhd8ed1ab_0 conda-forge +typing_extensions 4.12.2 pyha770c72_0 conda-forge +tzdata 2024b h04d1e81_0 +unicodedata2 15.1.0 py312h2bbff1b_0 +vc 14.40 h2eaa2aa_1 +vs2015_runtime 14.40.33807 h98bb1dd_1 +wcwidth 0.2.13 pyhd8ed1ab_0 conda-forge +wheel 0.44.0 py312haa95532_0 +xz 5.4.6 h8cc25b3_1 +zeromq 4.3.5 hd77b12b_0 +zipp 3.21.0 pyhd8ed1ab_0 conda-forge +zlib 1.2.13 h8cc25b3_1 +zstd 1.5.6 h8880b57_0 \ No newline at end of file diff --git a/Application/run.py b/Application/run.py index 3dd84a9..c98e57e 100644 --- a/Application/run.py +++ b/Application/run.py @@ -11,7 +11,8 @@ time_input = input("Bitte geben sie die Anzahl Zeitschritte an (ein Zeitschritt ist 1 Sekunde)") timesteps = int(time_input) user_input = input("Bitte geben sie an welchen Test (RiMEA4, RiMEA9 oder Experiment) sie durchführen wollen") -if user_input == "RiMEA9": +user_input = user_input.lower() +if user_input == "rimea9": door_input = input("Bitte Anzahl (1-4) Türen angeben") pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": @@ -22,7 +23,7 @@ algo = "dijkstra" #Standardwert ist dijkstra doors = int(door_input) grid, door_cells, roi = RiMEA9(movement_method=algo, Doors=doors) -elif user_input == "RiMEA4": +elif user_input == "rimea4": pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": algo = "floodfill" @@ -32,7 +33,7 @@ algo = "dijkstra" #Standardwert ist dijkstra spawn_input = input("Bitte spawnrate zwischen 0 und 1 eingeben um Personendichte zu variieren (Wird mit einer Zufallsvariable zwischen 0 und 1 verglichen um spawn zu bestimmen)") grid, door_cells, roi = RiMEA4(movement_method=algo,spawn_rate=float(spawn_input)) -elif user_input == "Experiment": +elif user_input == "experiment": pathfinding = input("Bitte Algorithmus wählen (dijkstra, floodfill)") if pathfinding == "floodfill": algo = "floodfill" @@ -49,7 +50,7 @@ fundamental_data = [] -grid.update_distance_maps()# Befor wir die Simulation starten setzen wir bereits die Distance-Maps für Dijkstra/Floodfill + agents_crossed = {} # Dictionary to track agents crossing the boundary for i in range(timesteps): grid.update(target_list=grid.target_cells, timestep=i) diff --git a/README.md b/README.md index a4c7e4e..e853b17 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,118 @@ Um die Tests zu überprüfen, ins Verzeichnis Application wechseln und folgenden ` python -m unittest -v test_components` # Rimea Test -Um den Rimea Test durchzuführen muss die Datei run.py gestartet werden. Der Algorithmus (Floodfill, Dijkstra) kann in der run.py movement_method angepasst werden. -Visualisierung (in der Timestep loop ausgeklammert) kann auch angepasst werden. \ No newline at end of file +Um den Rimea Test durchzuführen muss die Datei run.py gestartet werden. Die Parameter für die Simulation werden per Konsole übergeben und danach wird automatisch die Simulation gestartet. + +# Packages +Da wir bei der Visualisierung einige Fehler wegen fehlender Packages erhalten haben, sind hier noch die Packages aufgelistet. +| Name | Version | Build |Channel | +| ----------------------|--------------------------|---------|------------| +asttokens | 2.4.1 | pyhd8ed1ab_0 |conda-forge | +blas | 1.0 | mkl | | +bottleneck | 1.4.2 |py312h4b0e54e_0 | | +brotli | 1.0.9 | h2bbff1b_8 | | +brotli-bin | 1.0.9 | h2bbff1b_8 | | +bzip2 | 1.0.8 | h2bbff1b_6 | | +ca-certificates | 2024.9.24 | haa95532_0 | | +colorama | 0.4.6 | pyhd8ed1ab_0 |conda-forge | +comm | 0.2.2 | pyhd8ed1ab_0 |conda-forge | +contourpy | 1.2.0 |py312h59b6b97_0 | | +cycler | 0.11.0 | pyhd3eb1b0_0 | | +debugpy | 1.6.7 |py312hd77b12b_0 | | +decorator | 5.1.1 | pyhd8ed1ab_0 |conda-forge | +exceptiongroup | 1.2.2 | pyhd8ed1ab_0 |conda-forge | +executing | 2.1.0 | pyhd8ed1ab_0 |conda-forge | +expat | 2.6.3 | h5da7b33_0 | | +fonttools | 4.51.0 |py312h2bbff1b_0 | | +freetype | 2.12.1 | ha860e81_0 | | +icc_rt | 2022.1.0 | h6049295_2 | | +icu | 73.1 | h6c2663c_0 | | +importlib-metadata | 8.5.0 | pyha770c72_0 |conda-forge | +intel-openmp | 2023.1.0 | h59b6b97_46320 | | +ipykernel | 6.29.5 | pyh4bbf305_0 |conda-forge | +ipython | 8.29.0 | pyh7428d3b_0 |conda-forge | +jedi | 0.19.2 | pyhff2d567_0 |conda-forge | +joblib | 1.4.2 |py312haa95532_0 | | +jpeg | 9e | h827c3e9_3 | | +jupyter_client | 8.6.3 | pyhd8ed1ab_0 |conda-forge | +jupyter_core | 5.7.2 |py312haa95532_0 | | +kiwisolver | 1.4.4 |py312hd77b12b_0 | | +krb5 | 1.20.1 | h5b6d351_0 | | +lcms2 | 2.12 | h83e58a3_0 | | +lerc | 3.0 | hd77b12b_0 | | +libbrotlicommon | 1.0.9 | h2bbff1b_8 | | +libbrotlidec | 1.0.9 | h2bbff1b_8 | | +libbrotlienc | 1.0.9 | h2bbff1b_8 | | +libclang | 14.0.6 |default_hb5a9fac_1 | | +libclang13 | 14.0.6 |default_h8e68704_1 | | +libdeflate | 1.17 | h2bbff1b_1 | | +libffi | 3.4.4 | hd77b12b_1 | | +libpng | 1.6.39 | h8cc25b3_0 | | +libpq | 12.20 | h70ee33d_0 | | +libsodium | 1.0.18 | h8d14728_1 |conda-forge | +libtiff | 4.5.1 | hd77b12b_0 | | +libwebp-base | 1.3.2 | h3d04722_1 | | +lz4-c | 1.9.4 | h2bbff1b_1 | | +matplotlib | 3.9.2 |py312haa95532_0 | | +matplotlib-base | 3.9.2 |py312hbdc63d0_0 | | +matplotlib-inline | 0.1.7 | pyhd8ed1ab_0 |conda-forge | +mkl | 2023.1.0 | h6b88ed4_46358 | | +mkl-service | 2.4.0 |py312h2bbff1b_1 | | +mkl_fft | 1.3.11 |py312h827c3e9_0 | | +mkl_random | 1.2.8 |py312h0158946_0 | | +nest-asyncio | 1.6.0 | pyhd8ed1ab_0 |conda-forge | +numexpr | 2.10.1 |py312h4cd664f_0 | | +numpy | 1.26.4 |py312hfd52020_0 | | +numpy-base | 1.26.4 |py312h4dde369_0 | | +openjpeg | 2.5.2 | hae555c5_0 | | +openssl | 3.0.15 | h827c3e9_0 | | +packaging | 24.1 |py312haa95532_0 | | +pandas | 2.2.2 |py312h0158946_0 | | +parso | 0.8.4 | pyhd8ed1ab_0 |conda-forge | +patsy | 0.5.6 |py312haa95532_0 | | +pickleshare | 0.7.5 | py_1003 |conda-forge | +pillow | 10.4.0 |py312h827c3e9_0 | | +pip | 24.2 |py312haa95532_0 | | +platformdirs | 4.3.6 | pyhd8ed1ab_0 |conda-forge | +ply | 3.11 |py312haa95532_1 | | +prompt-toolkit | 3.0.48 | pyha770c72_0 |conda-forge | +psutil | 5.9.0 |py312h2bbff1b_0 | | +pure_eval | 0.2.3 | pyhd8ed1ab_0 |conda-forge | +pybind11-abi | 5 | hd3eb1b0_0 | | +pygments | 2.18.0 | pyhd8ed1ab_0 |conda-forge | +pyparsing | 3.2.0 |py312haa95532_0 | | +pyqt | 5.15.10 |py312hd77b12b_0 | | +pyqt5-sip | 12.13.0 |py312h2bbff1b_0 | | +python | 3.12.7 | h14ffc60_0 | | +python-dateutil | 2.9.0post0 |py312haa95532_2 | | +python-tzdata | 2023.3 | pyhd3eb1b0_0 | | +pytz | 2024.1 |py312haa95532_0 | | +pywin32 | 305 |py312h2bbff1b_0 | | +pyzmq | 25.1.2 |py312hd77b12b_0 | | +qt-main | 5.15.2 | h19c9488_10 | | +scikit-learn | 1.5.1 |py312h0158946_0 | | +scipy | 1.13.1 |py312hbb039d4_0 | | +seaborn | 0.13.2 |py312haa95532_0 | | +setuptools | 75.1.0 |py312haa95532_0 | | +sip | 6.7.12 |py312hd77b12b_0 | | +six | 1.16.0 | pyhd3eb1b0_1 | | +sqlite | 3.45.3 | h2bbff1b_0 | | +stack_data | 0.6.2 | pyhd8ed1ab_0 |conda-forge | +statsmodels | 0.14.2 |py312h4b0e54e_0 |anaconda | +tbb | 2021.8.0 | h59b6b97_0 | | +threadpoolctl | 3.5.0 |py312hfc267ef_0 | | +tk | 8.6.14 | h0416ee5_0 | | +tornado | 6.4.1 |py312h827c3e9_0 | | +traitlets | 5.14.3 | pyhd8ed1ab_0 |conda-forge | +typing_extensions | 4.12.2 | pyha770c72_0 |conda-forge | +tzdata | 2024b | h04d1e81_0 | | +unicodedata2 | 15.1.0 |py312h2bbff1b_0 | | +vc | 14.40 | h2eaa2aa_1 | | +vs2015_runtime | 14.40.33807 | h98bb1dd_1 | | +wcwidth | 0.2.13 | pyhd8ed1ab_0 |conda-forge | +wheel | 0.44.0 |py312haa95532_0 | | +xz | 5.4.6 | h8cc25b3_1 | | +zeromq | 4.3.5 | hd77b12b_0 | | +zipp | 3.21.0 | pyhd8ed1ab_0 |conda-forge | +zlib | 1.2.13 | h8cc25b3_1 | | +zstd | 1.5.6 | h8880b57_0 | \ No newline at end of file From 0f210e06468a7ccafa81a0fa9422c0de627b1f1a Mon Sep 17 00:00:00 2001 From: elFleppo Date: Tue, 31 Dec 2024 11:49:03 +0100 Subject: [PATCH 26/26] Last one --- Application/test_components.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Application/test_components.py b/Application/test_components.py index bf87865..5e54469 100644 --- a/Application/test_components.py +++ b/Application/test_components.py @@ -353,9 +353,9 @@ def test_social_penalties_computation(self): # Validate penalties are computed correctly self.assertEqual(len(precomputed_penalties), len(self.agents)) - # Example manual calculations based on the logic + expected_penalties = [ - 0.28898620536195957, # Adjust these values based on your manual calculations + 0.28898620536195957, 0.28898620536195957, 0.28898620536195957, 0.28898620536195957