From 73538d26a08ec505d55ff27e7b307376947ccf93 Mon Sep 17 00:00:00 2001 From: Mohcine Chraibi Date: Wed, 3 Apr 2024 15:49:28 +0200 Subject: [PATCH] Add different exits --- simulation.py | 76 +++++++++++++++++++++++++++++++----------------- src/plotting.py | 1 + src/utilities.py | 60 +++++++++++++++++++++++++++++--------- 3 files changed, 96 insertions(+), 41 deletions(-) diff --git a/simulation.py b/simulation.py index 691fdfa..52e239f 100644 --- a/simulation.py +++ b/simulation.py @@ -6,10 +6,9 @@ import json import logging import pathlib -import sys import time from typing import Any, Dict, Iterator, List, Tuple, TypeAlias - +import sys import _io import jupedsim as jps from jupedsim.distributions import distribute_by_number @@ -21,7 +20,6 @@ parse_distribution_polygons, parse_fps, parse_motivation_doors, - parse_motivation_parameter, parse_motivation_strategy, parse_normal_time_gap, parse_normal_v_0, @@ -60,9 +58,9 @@ def init_motivation_model( _data: Dict[str, Any], ped_ids: List[int] ) -> mm.MotivationModel: """Init motuvation model based on parsed streategy.""" - width = parse_motivation_parameter(_data, "width") - height = parse_motivation_parameter(_data, "height") - seed = parse_motivation_parameter(_data, "seed") + width = _data["motivation_parameters"]["width"] + height = _data["motivation_parameters"]["height"] + seed = _data["motivation_parameters"]["seed"] motivation_doors = parse_motivation_doors(_data) if not motivation_doors: log_error("json file does not contain any motivation door") @@ -81,6 +79,7 @@ def init_motivation_model( if choose_motivation_strategy == "default": motivation_strategy = mm.DefaultMotivationStrategy(width=width, height=height) if choose_motivation_strategy == "EVC": + logging.info(f"init EVC with {width = }, {height = }") motivation_strategy = mm.EVCStrategy( width=width, height=height, @@ -201,8 +200,6 @@ def run_simulation( and simulation.elapsed_time() < _simulation_time ): simulation.iterate() - - # msg.code(f"Agents in the simulation: {simulation.agent_count()}") if simulation.iteration_count() % 100 == 0: number_agents_in_simulation = simulation.agent_count() for agent in simulation.agents(): @@ -255,13 +252,13 @@ def main( way_points = parse_way_points(_data) destinations_dict = parse_destinations(_data) destinations = list(destinations_dict.values()) - journey_id, stage_id = init_journey(simulation, way_points, destinations[0]) + journey_id, exit_ids = init_journey(simulation, way_points, destinations) distribution_polygons = parse_distribution_polygons(_data) positions = [] total_agents = _number_agents for s_polygon in distribution_polygons.values(): - logging.info(f"Distribute {total_agents} agents in {s_polygon}") + logging.info(f"Distribute {total_agents} agents") pos = distribute_by_number( polygon=s_polygon, number_of_agents=total_agents, @@ -277,16 +274,21 @@ def main( normal_v_0 = parse_normal_v_0(_data) normal_time_gap = parse_normal_time_gap(_data) radius = parse_radius(_data) - agent_parameters = jps.CollisionFreeSpeedModelAgentParameters( - journey_id=journey_id, - stage_id=stage_id, - radius=radius, - v0=normal_v_0, - time_gap=normal_time_gap, - ) - ped_ids = distribute_and_add_agents(simulation, agent_parameters, positions) + agent_parameters_list = [] + for exit_id in exit_ids: + agent_parameters = jps.CollisionFreeSpeedModelAgentParameters( + journey_id=journey_id, + stage_id=exit_id, + radius=radius, + v0=normal_v_0, + time_gap=normal_time_gap, + ) + agent_parameters_list.append(agent_parameters) + + ped_ids = distribute_and_add_agents(simulation, agent_parameters_list, positions) motivation_model = init_motivation_model(_data, ped_ids) logging.info(f"Running simulation for {len(ped_ids)} agents:") + logging.info(f"{motivation_model.motivation_strategy.width = }") run_simulation(simulation, motivation_model, _simulation_time, ped_ids, msg) logging.info( f"Simulation completed after {simulation.iteration_count()} iterations" @@ -319,32 +321,52 @@ def start_simulation(config_path, output_path): def modify_and_save_config(base_config, modification_dict, new_config_path): """Modify base configuration and save as a new JSON file.""" - config = base_config.copy() + config = json.loads(json.dumps(base_config)) # Deep copy for key, value in modification_dict.items(): - config[key] = value + nested_keys = key.split("/") + last_key = nested_keys.pop() + temp = config + for nk in nested_keys: + temp = temp[nk] + temp[last_key] = value with open(new_config_path, "w", encoding="utf8") as f: json.dump(config, f, ensure_ascii=False, indent=4) if __name__ == "__main__": init_logger() - base_config = "files/bottleneck.json" + base_config = "files/inifile.json" # Load base configuration with open(base_config, "r", encoding="utf8") as f: base_config = json.load(f) variations = [ - {"number_agents": 10, "fps": 30}, - {"number_agents": 20, "fps": 30}, + {"motivation_parameters/width": 0.5, "motivation_parameters/seed": 1.0}, + {"motivation_parameters/width": 0.5, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/height": 0.5, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/width": 0.1, "motivation_parameters/seed": 1.0}, + {"motivation_parameters/width": 0.1, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/width": 1.0, "motivation_parameters/seed": 1.0}, + {"motivation_parameters/width": 1.0, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/width": 1.5, "motivation_parameters/seed": 200.0}, + {"motivation_parameters/width": 1.5, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/width": 2.0, "motivation_parameters/seed": 200.0}, + {"motivation_parameters/width": 2.0, "motivation_parameters/seed": 300.0}, + {"motivation_parameters/width": 1.2, "motivation_parameters/seed": 300.0}, ] + file_path = "files/variations/variations.json" + + # Write the list of dictionaries to a JSON file + with open(file_path, "w") as f: + json.dump(variations, f, indent=4) # Run simulations with variations for i, variation in enumerate(variations, start=1): - logging.info(f"running simulation with {i}: {variation}") - new_config_path = f"config_variation_{i}.json" - output_path = f"trajectory_variation_{i}.sqlite" + logging.info(f"running variation {i:03d}: {variation}") + new_config_path = f"config_variation_{i:03d}.json" + output_path = f"files/trajectory_variation_{i:03d}.sqlite" # Modify and save the new configuration modify_and_save_config(base_config, variation, new_config_path) evac_time = start_simulation(new_config_path, output_path) - print(f"Variation {i}: {evac_time = }") + logging.info(f"Variation {i:03d}: {evac_time = }") diff --git a/src/plotting.py b/src/plotting.py index 4969213..a524a85 100644 --- a/src/plotting.py +++ b/src/plotting.py @@ -22,6 +22,7 @@ def plot_density_time_series(df_data: pd.DataFrame) -> None: xaxis_title="Time Steps", yaxis_title="Density (1/m/m)", ) + fig_density.update_yaxes(range=[0, 12]) st.plotly_chart(fig_density) diff --git a/src/utilities.py b/src/utilities.py index 0d6bfcd..5ea1b52 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -4,7 +4,8 @@ import json import os from pathlib import Path -from typing import Any, Dict, List, Tuple, TypeAlias +from typing import Any, Dict, List, Tuple, TypeAlias, Union +from types import SimpleNamespace import jupedsim as jps import numpy as np @@ -16,12 +17,38 @@ from scipy import stats from shapely import GeometryCollection, Polygon from shapely.ops import unary_union - from .logger_config import log_error, log_info Point: TypeAlias = Tuple[float, float] +def parse(data: Union[List, Dict, Any]) -> Union[List, SimpleNamespace, Any]: + """ + Recursively converts a nested structure of lists and dictionaries into + a structure of lists and SimpleNamespace objects. Other data types are left unchanged. + + Parameters: + - data (Union[List, Dict, Any]): The input data to parse. This can be a list, + dictionary, or any other data type. If it's a list or dictionary, the function + will recursively parse its content. + + Returns: + - Union[List, SimpleNamespace, Any]: The parsed data where dictionaries are + converted to SimpleNamespace objects, lists are recursively parsed, and + other data types are returned unchanged. + + """ + if isinstance(data, list): + return list(map(parse, data)) + elif isinstance(data, dict): + sns = SimpleNamespace() + for key, value in data.items(): + setattr(sns, key, parse(value)) + return sns + else: + return data + + def delete_txt_files() -> None: """Delete all *.sqlite files in the current directory.""" files = glob.glob("files/*.sqlite") @@ -82,24 +109,27 @@ def init_journey( wp_ids.append(wp_id) journey.add(wp_id) - exit_id = simulation.add_exit_stage(exits) - exit_ids.append(exit_id) - journey.add(exit_id) + for e in exits: + exit_id = simulation.add_exit_stage(e) + exit_ids.append(exit_id) + journey.add(exit_id) - # todo: using only one exit here - stage_id = exit_ids[0] - for wp_id in wp_ids: - journey.set_transition_for_stage( - wp_id, jps.Transition.create_fixed_transition(stage_id) - ) + # chosen_id = random.choice(exit_ids) + # logging.info(f"{chosen_id}, {exit_ids}") + # stage_id = chosen_id + # # todo these wp id are not needed and not properly initialized + # for wp_id in wp_ids: + # journey.set_transition_for_stage( + # wp_id, jps.Transition.create_fixed_transition(stage_id) + # ) journey_id = int(simulation.add_journey(journey)) - return journey_id, stage_id + return journey_id, exit_ids def distribute_and_add_agents( simulation: jps.Simulation, - agent_parameters: jps.CollisionFreeSpeedModelAgentParameters, + agent_parameters_list: List[jps.CollisionFreeSpeedModelAgentParameters], positions: List[Point], ) -> List[int]: """Initialize positions of agents and insert them into the simulation. @@ -112,7 +142,9 @@ def distribute_and_add_agents( """ # log_info("Distribute and Add Agent") ped_ids = [] - for pos_x, pos_y in positions: + size = len(agent_parameters_list) + for i, (pos_x, pos_y) in enumerate(positions): + agent_parameters = agent_parameters_list[i % size] agent_parameters.position = (pos_x, pos_y) ped_id = simulation.add_agent(agent_parameters) ped_ids.append(ped_id)