From 90162859f7bb9e935327b8797daa05f92ce716c4 Mon Sep 17 00:00:00 2001 From: ExoRoy Date: Sat, 14 Mar 2026 12:36:13 +0100 Subject: [PATCH 1/9] Try d'implementer launch_one_simu dans le refactoring --- src/simulation/scripts/lanch_one_simu.py | 160 +++++++++++++++++++---- src/simulation/src/utils/__init__.py | 5 + 2 files changed, 138 insertions(+), 27 deletions(-) diff --git a/src/simulation/scripts/lanch_one_simu.py b/src/simulation/scripts/lanch_one_simu.py index 8779f0b..8d4ee3c 100644 --- a/src/simulation/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/lanch_one_simu.py @@ -1,35 +1,130 @@ -raise NotImplementedError("This file is currently begin worked on") - import os import sys +from typing import * +import numpy as np import onnxruntime as ort +import gymnasium as gym -from simulation import ( - VehicleEnv, -) -from simulation import config as c -from utils import onnx_utils +from utils import run_onnx_model +from simulation import config + +from extractors import ( # noqa: F401 + CNN1DResNetExtractor, + TemporalResNetExtractor, +) # ------------------------------------------------------------------------- +def log(s: str): + if B_DEBUG: + print(s, file=open("/tmp/autotech/logs", "a")) -# --- Chemin vers le fichier ONNX --- ONNX_MODEL_PATH = "model.onnx" - # --- Initialisation du moteur d'inférence ONNX Runtime (ORT) --- def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: if not os.path.exists(onnx_path): - raise FileNotFoundError( - f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord." - ) + raise FileNotFoundError(f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord.") # Crée la session d'inférence - return ort.InferenceSession( - onnx_path - ) # On peut modifier le providers afin de mettre une CUDA + return ort.InferenceSession(onnx_path) #On peut modifier le providers afin de mettre une CUDA + + +class WebotsSimulationGymEnvironment(gym.Env): + """ + One environment for each vehicle + + n: index of the vehicle + supervisor: the supervisor of the simulation + """ + + def __init__(self, simulation_rank: int): + super().__init__() + self.simulation_rank = simulation_rank + + # this is only true if lidar_horizontal_resolution = camera_horizontal_resolution + box_min = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) + box_max = np.ones([2, context_size, lidar_horizontal_resolution], dtype=np.float32) * 30 + + self.observation_space = gym.spaces.Box(box_min, box_max, dtype=np.float32) + self.action_space = gym.spaces.MultiDiscrete([n_actions_steering, n_actions_speed]) + + if not os.path.exists("/tmp/autotech"): + os.mkdir("/tmp/autotech") + + log(f"SERVER{simulation_rank} : {simulation_rank=}") + + os.mkfifo(f"/tmp/autotech/{simulation_rank}toserver.pipe") + os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}.pipe") + + # --mode=fast --minimize --no-rendering --batch --stdout + os.system(f""" + webots {__file__.rsplit('/', 1)[0]}/worlds/piste{simulation_rank % n_map}.wbt --mode=fast --minimize --no-rendering --batch --stdout & + echo $! {simulation_rank} >>/tmp/autotech/simulationranks + """) + log(f"SERVER{simulation_rank} : {simulation_rank}toserver.pipe") + self.fifo_r = open(f"/tmp/autotech/{simulation_rank}toserver.pipe", "rb") + log(f"SERVER{simulation_rank} : serverto{simulation_rank}.pipe") + self.fifo_w = open(f"/tmp/autotech/serverto{simulation_rank}.pipe", "wb") + log(f"SERVER{simulation_rank} : fifo opened :D and init done") + log("-------------------------------------------------------------------") + + def reset(self, seed=0): + # basically useless function + # lidar data + # this is true for lidar_horizontal_resolution = camera_horizontal_resolution + self.context = obs = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) + info = {} + return obs, info + + def step(self, action): + log(f"SERVER{self.simulation_rank} : sending {action=}") + self.fifo_w.write(action.astype(np.int64).tobytes()) + self.fifo_w.flush() + + # communication with the supervisor + cur_state = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize * (n_sensors + lidar_horizontal_resolution + camera_horizontal_resolution)), dtype=np.float32) + log(f"SERVER{self.simulation_rank} : received {cur_state=}") + reward = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize), dtype=np.float32)[0] # scalar + log(f"SERVER{self.simulation_rank} : received {reward=}") + done = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar + log(f"SERVER{self.simulation_rank} : received {done=}") + truncated = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar + log(f"SERVER{self.simulation_rank} : received {truncated=}") + info = {} + + cur_state = np.nan_to_num(cur_state[n_sensors:], nan=0., posinf=30.) + + lidar_obs = cur_state[:lidar_horizontal_resolution] + camera_obs = cur_state[lidar_horizontal_resolution:] + + # apply dropout to the camera + # p = 0.5 + # camera_obs *= np.random.binomial(1, 1-p, camera_obs.shape) # random values in {0, 1} + + self.context = obs = np.concatenate([ + self.context[:, 1:], + [lidar_obs[None], camera_obs[None]] + ], axis=1) + # check if the context is correct + # if self.simulation_rank == 0: + # print(f"{(obs[0] == 0).mean():.3f} {(obs[1] == 0).mean():.3f}") + return obs, reward, done, truncated, info + + def close(self): + print("Fermeture de l'environnement...") + if hasattr(self, 'fifo_r') and self.fifo_r: + self.fifo_r.close() + if hasattr(self, 'fifo_w') and self.fifo_w: + self.fifo_w.close() + + # Nettoyage des fichiers pipes + if hasattr(self, 'pipe_name_read') and os.path.exists(self.pipe_name_read): + os.unlink(self.pipe_name_read) + if hasattr(self, 'pipe_name_write') and os.path.exists(self.pipe_name_write): + os.unlink(self.pipe_name_write) if __name__ == "__main__": @@ -37,6 +132,9 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: os.mkdir("/tmp/autotech/") os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') + if B_DEBUG: + print("Webots started", file=open("/tmp/autotech/logs", "w")) + # 2. Initialisation de la session ONNX Runtime try: @@ -48,32 +146,40 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: except FileNotFoundError as e: print(f"ERREUR : {e}") print( - "Veuillez vous assurer que vous avez exécuté une fois le script d'entraînement pour exporter 'model.onnx'." - ) + "Veuillez vous assurer que vous avez exécuté une fois le script d'entraînement pour exporter 'model.onnx'.") sys.exit(1) - # 3. Boucle d'inférence (Test) - env = VehicleEnv(0, 0) - obs = env.reset() + env = WebotsSimulationGymEnvironment(0) + obs,_ = env.reset() + print("Début de la simulation en mode inférence...") max_steps = 5000 step_count = 0 while True: - action = onnx_utils.run_onnx_model(ort_session, obs) + action = run_onnx_model(ort_session, obs[None]) # 4. Exécuter l'action dans l'environnement - obs, reward, done, info = env.step(action) + obs, reward, done, truncated, info = env.step(action) - # Note: L'environnement Webots gère généralement son propre affichage - # env.render() # Décommenter si votre env supporte le rendu externe + step_count += 1 # Pense à incrémenter pour tes logs ! # Gestion des fins d'épisodes if done: print(f"Épisode(s) terminé(s) après {step_count} étapes.") - obs = env.reset() + step_count = 0 + + # AU LIEU DE FAIRE env.reset() QUI MET TOUT À ZÉRO : + # On récupère la toute dernière frame (qui est valide et envoyée par Webots après sa téléportation) + fresh_frame = obs[:, -1:] + + # On vide l'historique du contexte + env.context = np.zeros_like(env.context) + + # On replace la vraie frame valide à la fin pour que l'IA ne soit pas aveugle + env.context[:, -1:] = fresh_frame + obs = env.context - # Fermeture propre (très important pour les processus parallèles SubprocVecEnv) envs.close() - print("Simulation terminée. Environnements fermés.") + print("Simulation terminée. Environnements fermés.") \ No newline at end of file diff --git a/src/simulation/src/utils/__init__.py b/src/simulation/src/utils/__init__.py index 4b7ee72..7e56893 100644 --- a/src/simulation/src/utils/__init__.py +++ b/src/simulation/src/utils/__init__.py @@ -1,3 +1,8 @@ from .plot_model_io import PlotModelIO +import onnxruntime as ort +import numpy as np __all__ = ["PlotModelIO"] + +def run_onnx_model(session: ort.InferenceSession, x: np.ndarray): + return session.run(None, {"input": x})[0] \ No newline at end of file From a8a4cb191e817c70ebd7edf13826f96dd794e0c2 Mon Sep 17 00:00:00 2001 From: ExoRoy Date: Sat, 14 Mar 2026 13:13:20 +0100 Subject: [PATCH 2/9] Try d'implementer launch_one_simu dans le refactoring 2 --- src/simulation/scripts/lanch_one_simu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/simulation/scripts/lanch_one_simu.py b/src/simulation/scripts/lanch_one_simu.py index 8d4ee3c..507b583 100644 --- a/src/simulation/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/lanch_one_simu.py @@ -17,11 +17,11 @@ # ------------------------------------------------------------------------- def log(s: str): - if B_DEBUG: + if False: print(s, file=open("/tmp/autotech/logs", "a")) -ONNX_MODEL_PATH = "model.onnx" +ONNX_MODEL_PATH = "/home/exo/Bureau/CoVAPSy/model.onnx" #mettre la bonne adresse # --- Initialisation du moteur d'inférence ONNX Runtime (ORT) --- def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: @@ -132,7 +132,7 @@ def close(self): os.mkdir("/tmp/autotech/") os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - if B_DEBUG: + if False: print("Webots started", file=open("/tmp/autotech/logs", "w")) From be2e0788b33ffd0eaa61df10b66bf19b4d5108f8 Mon Sep 17 00:00:00 2001 From: ExoRoy Date: Thu, 19 Mar 2026 14:26:41 +0100 Subject: [PATCH 3/9] Fix : lanch_one_simu.py marche a un peu. Mais Crash au bout de 3 collisions. Je ne trouve pas pk --- src/simulation/scripts/lanch_one_simu.py | 55 ++++++++++++++++-------- src/simulation/src/simulation/config.py | 4 +- uv.lock | 10 +++++ 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/simulation/scripts/lanch_one_simu.py b/src/simulation/scripts/lanch_one_simu.py index 507b583..776a810 100644 --- a/src/simulation/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/lanch_one_simu.py @@ -6,9 +6,8 @@ import onnxruntime as ort import gymnasium as gym - +from simulation.config import * from utils import run_onnx_model -from simulation import config from extractors import ( # noqa: F401 CNN1DResNetExtractor, @@ -17,7 +16,7 @@ # ------------------------------------------------------------------------- def log(s: str): - if False: + if B_DEBUG: print(s, file=open("/tmp/autotech/logs", "a")) @@ -43,6 +42,7 @@ class WebotsSimulationGymEnvironment(gym.Env): def __init__(self, simulation_rank: int): super().__init__() self.simulation_rank = simulation_rank + self.vehicle_rank = 0 # this is only true if lidar_horizontal_resolution = camera_horizontal_resolution box_min = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) @@ -56,18 +56,33 @@ def __init__(self, simulation_rank: int): log(f"SERVER{simulation_rank} : {simulation_rank=}") - os.mkfifo(f"/tmp/autotech/{simulation_rank}toserver.pipe") - os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}.pipe") + for i in range(n_vehicles): + os.mkfifo(f"/tmp/autotech/{simulation_rank}_{i}toserver.pipe") + os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}_{i}.pipe") + # Si le superviseur en a aussi besoin : + os.mkfifo(f"/tmp/autotech/{simulation_rank}_{i}tosupervisor.pipe") + + current_dir = os.path.dirname(os.path.abspath(__file__)) + world_path = os.path.normpath( + os.path.join(current_dir, "..", "src", "webots", "worlds", f"piste{simulation_rank % n_map}.wbt")) # --mode=fast --minimize --no-rendering --batch --stdout os.system(f""" - webots {__file__.rsplit('/', 1)[0]}/worlds/piste{simulation_rank % n_map}.wbt --mode=fast --minimize --no-rendering --batch --stdout & - echo $! {simulation_rank} >>/tmp/autotech/simulationranks - """) - log(f"SERVER{simulation_rank} : {simulation_rank}toserver.pipe") - self.fifo_r = open(f"/tmp/autotech/{simulation_rank}toserver.pipe", "rb") - log(f"SERVER{simulation_rank} : serverto{simulation_rank}.pipe") - self.fifo_w = open(f"/tmp/autotech/serverto{simulation_rank}.pipe", "wb") + webots {world_path} --mode=fast --minimize --no-rendering --batch --stdout & + echo $! {simulation_rank} >>/tmp/autotech/simulationranks + """) + log(f"SERVER{simulation_rank} : Ouverture des pipes en ECRITURE en premier...") + + # 1. On ouvre les pipes où Webots attend de LIRE ("rb") pour le débloquer + self.fifo_w = open(f"/tmp/autotech/serverto{simulation_rank}_{self.vehicle_rank}.pipe", "wb") + + # NOUVEAU : On ouvre le pipe du Superviseur pour le débloquer aussi ! + self.fifo_w_sup = open(f"/tmp/autotech/{simulation_rank}_{self.vehicle_rank}tosupervisor.pipe", "wb") + + log(f"SERVER{simulation_rank} : Ouverture des pipes en LECTURE...") + # 2. Ensuite seulement, on ouvre le pipe où Webots va ÉCRIRE ("wb") + self.fifo_r = open(f"/tmp/autotech/{simulation_rank}_{self.vehicle_rank}toserver.pipe", "rb") + log(f"SERVER{simulation_rank} : fifo opened :D and init done") log("-------------------------------------------------------------------") @@ -120,11 +135,13 @@ def close(self): if hasattr(self, 'fifo_w') and self.fifo_w: self.fifo_w.close() - # Nettoyage des fichiers pipes - if hasattr(self, 'pipe_name_read') and os.path.exists(self.pipe_name_read): - os.unlink(self.pipe_name_read) - if hasattr(self, 'pipe_name_write') and os.path.exists(self.pipe_name_write): - os.unlink(self.pipe_name_write) + # Clearing of pipes' files + for i in range(n_vehicles): + for suffix in [f"{self.simulation_rank}_{i}toserver.pipe", f"serverto{self.simulation_rank}_{i}.pipe", + f"{self.simulation_rank}_{i}tosupervisor.pipe"]: + pipe_path = f"/tmp/autotech/{suffix}" + if os.path.exists(pipe_path): + os.unlink(pipe_path) if __name__ == "__main__": @@ -132,11 +149,11 @@ def close(self): os.mkdir("/tmp/autotech/") os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - if False: + if B_DEBUG: print("Webots started", file=open("/tmp/autotech/logs", "w")) - # 2. Initialisation de la session ONNX Runtime + #Starting of OnnxSession try: ort_session = init_onnx_runtime_session(ONNX_MODEL_PATH) input_name = ort_session.get_inputs()[0].name diff --git a/src/simulation/src/simulation/config.py b/src/simulation/src/simulation/config.py index 1e8864d..7e572eb 100644 --- a/src/simulation/src/simulation/config.py +++ b/src/simulation/src/simulation/config.py @@ -11,7 +11,7 @@ n_map = 2 n_simulations = 1 -n_vehicles = 2 +n_vehicles = 1 n_stupid_vehicles = 0 n_actions_steering = 16 n_actions_speed = 16 @@ -26,3 +26,5 @@ LOG_LEVEL = logging.INFO FORMATTER = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") + +B_DEBUG = False \ No newline at end of file diff --git a/uv.lock b/uv.lock index 7effdcb..7ac586b 100644 --- a/uv.lock +++ b/uv.lock @@ -852,6 +852,11 @@ dependencies = [ { name = "zmq" }, ] +[package.optional-dependencies] +controller = [ + { name = "pygame" }, +] + [package.metadata] requires-dist = [ { name = "adafruit-blinka", specifier = ">=8.0.0" }, @@ -869,6 +874,7 @@ requires-dist = [ { name = "onnxruntime", specifier = ">=1.8.0" }, { name = "opencv-python", specifier = ">=4.12.0.88" }, { name = "picamera2", specifier = ">=0.3.0" }, + { name = "pygame", marker = "extra == 'controller'", specifier = ">=2.6.1" }, { name = "pyps4controller", specifier = ">=1.2.0" }, { name = "rpi-gpio", specifier = ">=0.7.1" }, { name = "rpi-hardware-pwm", specifier = ">=0.1.0" }, @@ -879,6 +885,7 @@ requires-dist = [ { name = "websockets", specifier = ">=16.0" }, { name = "zmq", specifier = ">=0.0.0" }, ] +provides-extras = ["controller"] [[package]] name = "humanfriendly" @@ -2392,6 +2399,9 @@ dependencies = [ wheels = [ { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, { url = "https://files.pythonhosted.org/packages/ec/23/2c9fe0c9c27f7f6cb865abcea8a4568f29f00acaeadfc6a37f6801f84cb4/torch-2.10.0-2-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:e521c9f030a3774ed770a9c011751fb47c4d12029a3d6522116e48431f2ff89e", size = 79498254, upload-time = "2026-02-10T21:44:44.095Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c6/4dfe238342ffdcec5aef1c96c457548762d33c40b45a1ab7033bb26d2ff2/torch-2.10.0-3-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:80b1b5bfe38eb0e9f5ff09f206dcac0a87aadd084230d4a36eea5ec5232c115b", size = 915627275, upload-time = "2026-03-11T14:16:11.325Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f0/72bf18847f58f877a6a8acf60614b14935e2f156d942483af1ffc081aea0/torch-2.10.0-3-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:46b3574d93a2a8134b3f5475cfb98e2eb46771794c57015f6ad1fb795ec25e49", size = 915523474, upload-time = "2026-03-11T14:17:44.422Z" }, { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, From c93157ce7acd6f1b9abedd2e2096387aba6a82d7 Mon Sep 17 00:00:00 2001 From: ExoRoy Date: Thu, 19 Mar 2026 14:58:27 +0100 Subject: [PATCH 4/9] Fix : lanch_one_simu.py marche a 100% je crois Je le prie actuellement --- src/simulation/scripts/lanch_one_simu.py | 176 +++++------------------ 1 file changed, 32 insertions(+), 144 deletions(-) diff --git a/src/simulation/scripts/lanch_one_simu.py b/src/simulation/scripts/lanch_one_simu.py index 776a810..2c309e8 100644 --- a/src/simulation/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/lanch_one_simu.py @@ -13,135 +13,19 @@ CNN1DResNetExtractor, TemporalResNetExtractor, ) -# ------------------------------------------------------------------------- -def log(s: str): - if B_DEBUG: - print(s, file=open("/tmp/autotech/logs", "a")) +from simulation import VehicleEnv + +# ------------------------------------------------------------------------- +ONNX_MODEL_PATH = "/home/exo/Bureau/CoVAPSy/model.onnx" -ONNX_MODEL_PATH = "/home/exo/Bureau/CoVAPSy/model.onnx" #mettre la bonne adresse -# --- Initialisation du moteur d'inférence ONNX Runtime (ORT) --- +# --- Launching of inference motor ONNX Runtime (ORT) --- def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: if not os.path.exists(onnx_path): raise FileNotFoundError(f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord.") - - # Crée la session d'inférence - return ort.InferenceSession(onnx_path) #On peut modifier le providers afin de mettre une CUDA - - -class WebotsSimulationGymEnvironment(gym.Env): - """ - One environment for each vehicle - - n: index of the vehicle - supervisor: the supervisor of the simulation - """ - - def __init__(self, simulation_rank: int): - super().__init__() - self.simulation_rank = simulation_rank - self.vehicle_rank = 0 - - # this is only true if lidar_horizontal_resolution = camera_horizontal_resolution - box_min = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) - box_max = np.ones([2, context_size, lidar_horizontal_resolution], dtype=np.float32) * 30 - - self.observation_space = gym.spaces.Box(box_min, box_max, dtype=np.float32) - self.action_space = gym.spaces.MultiDiscrete([n_actions_steering, n_actions_speed]) - - if not os.path.exists("/tmp/autotech"): - os.mkdir("/tmp/autotech") - - log(f"SERVER{simulation_rank} : {simulation_rank=}") - - for i in range(n_vehicles): - os.mkfifo(f"/tmp/autotech/{simulation_rank}_{i}toserver.pipe") - os.mkfifo(f"/tmp/autotech/serverto{simulation_rank}_{i}.pipe") - # Si le superviseur en a aussi besoin : - os.mkfifo(f"/tmp/autotech/{simulation_rank}_{i}tosupervisor.pipe") - - current_dir = os.path.dirname(os.path.abspath(__file__)) - world_path = os.path.normpath( - os.path.join(current_dir, "..", "src", "webots", "worlds", f"piste{simulation_rank % n_map}.wbt")) - - # --mode=fast --minimize --no-rendering --batch --stdout - os.system(f""" - webots {world_path} --mode=fast --minimize --no-rendering --batch --stdout & - echo $! {simulation_rank} >>/tmp/autotech/simulationranks - """) - log(f"SERVER{simulation_rank} : Ouverture des pipes en ECRITURE en premier...") - - # 1. On ouvre les pipes où Webots attend de LIRE ("rb") pour le débloquer - self.fifo_w = open(f"/tmp/autotech/serverto{simulation_rank}_{self.vehicle_rank}.pipe", "wb") - - # NOUVEAU : On ouvre le pipe du Superviseur pour le débloquer aussi ! - self.fifo_w_sup = open(f"/tmp/autotech/{simulation_rank}_{self.vehicle_rank}tosupervisor.pipe", "wb") - - log(f"SERVER{simulation_rank} : Ouverture des pipes en LECTURE...") - # 2. Ensuite seulement, on ouvre le pipe où Webots va ÉCRIRE ("wb") - self.fifo_r = open(f"/tmp/autotech/{simulation_rank}_{self.vehicle_rank}toserver.pipe", "rb") - - log(f"SERVER{simulation_rank} : fifo opened :D and init done") - log("-------------------------------------------------------------------") - - def reset(self, seed=0): - # basically useless function - # lidar data - # this is true for lidar_horizontal_resolution = camera_horizontal_resolution - self.context = obs = np.zeros([2, context_size, lidar_horizontal_resolution], dtype=np.float32) - info = {} - return obs, info - - def step(self, action): - log(f"SERVER{self.simulation_rank} : sending {action=}") - self.fifo_w.write(action.astype(np.int64).tobytes()) - self.fifo_w.flush() - - # communication with the supervisor - cur_state = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize * (n_sensors + lidar_horizontal_resolution + camera_horizontal_resolution)), dtype=np.float32) - log(f"SERVER{self.simulation_rank} : received {cur_state=}") - reward = np.frombuffer(self.fifo_r.read(np.dtype(np.float32).itemsize), dtype=np.float32)[0] # scalar - log(f"SERVER{self.simulation_rank} : received {reward=}") - done = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar - log(f"SERVER{self.simulation_rank} : received {done=}") - truncated = np.frombuffer(self.fifo_r.read(np.dtype(np.bool).itemsize), dtype=np.bool)[0] # scalar - log(f"SERVER{self.simulation_rank} : received {truncated=}") - info = {} - - cur_state = np.nan_to_num(cur_state[n_sensors:], nan=0., posinf=30.) - - lidar_obs = cur_state[:lidar_horizontal_resolution] - camera_obs = cur_state[lidar_horizontal_resolution:] - - # apply dropout to the camera - # p = 0.5 - # camera_obs *= np.random.binomial(1, 1-p, camera_obs.shape) # random values in {0, 1} - - self.context = obs = np.concatenate([ - self.context[:, 1:], - [lidar_obs[None], camera_obs[None]] - ], axis=1) - # check if the context is correct - # if self.simulation_rank == 0: - # print(f"{(obs[0] == 0).mean():.3f} {(obs[1] == 0).mean():.3f}") - return obs, reward, done, truncated, info - - def close(self): - print("Fermeture de l'environnement...") - if hasattr(self, 'fifo_r') and self.fifo_r: - self.fifo_r.close() - if hasattr(self, 'fifo_w') and self.fifo_w: - self.fifo_w.close() - - # Clearing of pipes' files - for i in range(n_vehicles): - for suffix in [f"{self.simulation_rank}_{i}toserver.pipe", f"serverto{self.simulation_rank}_{i}.pipe", - f"{self.simulation_rank}_{i}tosupervisor.pipe"]: - pipe_path = f"/tmp/autotech/{suffix}" - if os.path.exists(pipe_path): - os.unlink(pipe_path) + return ort.InferenceSession(onnx_path) if __name__ == "__main__": @@ -149,11 +33,8 @@ def close(self): os.mkdir("/tmp/autotech/") os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - if B_DEBUG: - print("Webots started", file=open("/tmp/autotech/logs", "w")) - - #Starting of OnnxSession + # Starting of OnnxSession try: ort_session = init_onnx_runtime_session(ONNX_MODEL_PATH) input_name = ort_session.get_inputs()[0].name @@ -162,41 +43,48 @@ def close(self): print(f"Input Name: {input_name}, Output Name: {output_name}") except FileNotFoundError as e: print(f"ERREUR : {e}") - print( - "Veuillez vous assurer que vous avez exécuté une fois le script d'entraînement pour exporter 'model.onnx'.") sys.exit(1) - env = WebotsSimulationGymEnvironment(0) - obs,_ = env.reset() + env = VehicleEnv(0, 0) + obs, _ = env.reset() print("Début de la simulation en mode inférence...") - max_steps = 5000 step_count = 0 while True: - action = run_onnx_model(ort_session, obs[None]) + # 1. On récupère les logits (probabilités) bruts de l'ONNX + raw_action = run_onnx_model(ort_session, obs[None]) + logits = np.array(raw_action).flatten() - # 4. Exécuter l'action dans l'environnement - obs, reward, done, truncated, info = env.step(action) + # 2. On sépare le tableau en deux (Direction et Vitesse) + # On utilise n_actions_steering et n_actions_speed venant de config.py + steer_logits = logits[:n_actions_steering] + speed_logits = logits[n_actions_steering:] - step_count += 1 # Pense à incrémenter pour tes logs ! + # 3. L'IA choisit l'action qui a le score (logit) le plus élevé + action_steer = np.argmax(steer_logits) + action_speed = np.argmax(speed_logits) + + # 4. On crée le tableau final parfaitement formaté pour Webots (strictement 2 entiers) + action = np.array([action_steer, action_speed], dtype=np.int64) + + # 5. Exécuter l'action dans l'environnement + next_obs, reward, done, truncated, info = env.step(action) + + step_count += 1 # Gestion des fins d'épisodes if done: print(f"Épisode(s) terminé(s) après {step_count} étapes.") step_count = 0 - # AU LIEU DE FAIRE env.reset() QUI MET TOUT À ZÉRO : - # On récupère la toute dernière frame (qui est valide et envoyée par Webots après sa téléportation) - fresh_frame = obs[:, -1:] - - # On vide l'historique du contexte - env.context = np.zeros_like(env.context) - - # On replace la vraie frame valide à la fin pour que l'IA ne soit pas aveugle + fresh_frame = next_obs[:, -1:] + obs, _ = env.reset() env.context[:, -1:] = fresh_frame obs = env.context + else: + obs = next_obs - envs.close() + env.close() print("Simulation terminée. Environnements fermés.") \ No newline at end of file From 9a82ee7185474cae0714081709b01ae863846692 Mon Sep 17 00:00:00 2001 From: matthiasbienvenu Date: Fri, 20 Mar 2026 22:37:03 +0100 Subject: [PATCH 5/9] fix: moved ai hyperparameters to config.py --- .../scripts/launch_train_multiprocessing.py | 19 ++++--------------- src/simulation/src/simulation/config.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/simulation/scripts/launch_train_multiprocessing.py b/src/simulation/scripts/launch_train_multiprocessing.py index 527baac..422cf3b 100644 --- a/src/simulation/scripts/launch_train_multiprocessing.py +++ b/src/simulation/scripts/launch_train_multiprocessing.py @@ -39,17 +39,6 @@ net_arch=[512, 512, 512], ) - ppo_args: Dict[str, Any] = dict( - n_steps=4096, - n_epochs=10, - batch_size=256, - learning_rate=3e-4, - gamma=0.99, - verbose=1, - normalize_advantage=True, - device=c.device, - ) - save_path = ( Path("~/.cache/autotech/checkpoints").expanduser() / c.ExtractorClass.__name__ ) @@ -61,12 +50,12 @@ if valid_files: model_path = max(valid_files, key=lambda x: int(x.name.rstrip(".zip"))) print(f"Loading model {model_path.name}") - model = PPO.load(model_path, envs, **ppo_args, policy_kwargs=policy_kwargs) + model = PPO.load(model_path, envs, **c.ppo_args, policy_kwargs=policy_kwargs) i = int(model_path.name.rstrip(".zip")) + 1 print(f"Model found, loading {model_path}") else: - model = PPO("MlpPolicy", envs, **ppo_args, policy_kwargs=policy_kwargs) + model = PPO("MlpPolicy", envs, **c.ppo_args, policy_kwargs=policy_kwargs) i = 0 print("Model not found, creating a new one") @@ -93,12 +82,12 @@ from utils import PlotModelIO model.learn( - total_timesteps=500_000, + total_timesteps=c.total_timesteps, progress_bar=False, callback=PlotModelIO(), ) else: - model.learn(total_timesteps=500_000, progress_bar=True) + model.learn(total_timesteps=c.total_timesteps, progress_bar=True) print("iteration over") # TODO: we could just use a callback to save checkpoints or export the model to onnx diff --git a/src/simulation/src/simulation/config.py b/src/simulation/src/simulation/config.py index 1e8864d..05a38d7 100644 --- a/src/simulation/src/simulation/config.py +++ b/src/simulation/src/simulation/config.py @@ -1,5 +1,6 @@ # just a file that lets us define some constants that are used in multiple files the simulation import logging +from typing import Any, Dict from torch.cuda import is_available @@ -9,6 +10,7 @@ TemporalResNetExtractor, ) +# Webots environments config n_map = 2 n_simulations = 1 n_vehicles = 2 @@ -18,11 +20,26 @@ lidar_max_range = 12.0 device = "cuda" if is_available() else "cpu" + +# Training config +total_timesteps = 500_000 +ppo_args: Dict[str, Any] = dict( + n_steps=4096, + n_epochs=10, + batch_size=256, + learning_rate=3e-4, + gamma=0.99, + verbose=1, + normalize_advantage=True, + device=device, +) ExtractorClass = TemporalResNetExtractor context_size = ExtractorClass.context_size lidar_horizontal_resolution = ExtractorClass.lidar_horizontal_resolution camera_horizontal_resolution = ExtractorClass.camera_horizontal_resolution n_sensors = ExtractorClass.n_sensors + +# Logging config LOG_LEVEL = logging.INFO FORMATTER = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") From ac9279799da37e2d4483f464389f1790d0a0c316 Mon Sep 17 00:00:00 2001 From: matthiasbienvenu Date: Tue, 24 Mar 2026 12:23:38 +0100 Subject: [PATCH 6/9] feat: we can choose to go backwards or respawn when crashing I also move some stuff to config.py --- .../scripts/launch_train_multiprocessing.py | 21 ++++--------------- src/simulation/src/simulation/config.py | 19 ++++++++++++++++- src/simulation/src/utils/onnx_utils.py | 3 +-- .../controller_world_supervisor.py | 2 +- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/simulation/scripts/launch_train_multiprocessing.py b/src/simulation/scripts/launch_train_multiprocessing.py index 422cf3b..fc01f97 100644 --- a/src/simulation/scripts/launch_train_multiprocessing.py +++ b/src/simulation/scripts/launch_train_multiprocessing.py @@ -30,18 +30,7 @@ ] ) - policy_kwargs: Dict[str, Any] = dict( - features_extractor_class=c.ExtractorClass, - # features_extractor_kwargs=dict( - # device=c.device, - # ), - activation_fn=nn.ReLU, - net_arch=[512, 512, 512], - ) - - save_path = ( - Path("~/.cache/autotech/checkpoints").expanduser() / c.ExtractorClass.__name__ - ) + save_path = c.save_dir / "checkpoints" / c.ExtractorClass.__name__ save_path.mkdir(parents=True, exist_ok=True) @@ -50,12 +39,12 @@ if valid_files: model_path = max(valid_files, key=lambda x: int(x.name.rstrip(".zip"))) print(f"Loading model {model_path.name}") - model = PPO.load(model_path, envs, **c.ppo_args, policy_kwargs=policy_kwargs) + model = PPO.load(model_path, envs, **c.ppo_args, policy_kwargs=c.policy_kwargs) i = int(model_path.name.rstrip(".zip")) + 1 print(f"Model found, loading {model_path}") else: - model = PPO("MlpPolicy", envs, **c.ppo_args, policy_kwargs=policy_kwargs) + model = PPO("MlpPolicy", envs, **c.ppo_args, policy_kwargs=c.policy_kwargs) i = 0 print("Model not found, creating a new one") @@ -72,9 +61,7 @@ while True: onnx_utils.export_onnx( model, - os.path.expanduser( - f"~/.cache/autotech/model_{c.ExtractorClass.__name__}.onnx" - ), + str(c.save_dir / f"model_{c.ExtractorClass.__name__}.onnx"), ) onnx_utils.test_onnx(model) diff --git a/src/simulation/src/simulation/config.py b/src/simulation/src/simulation/config.py index 05a38d7..5b1dd8c 100644 --- a/src/simulation/src/simulation/config.py +++ b/src/simulation/src/simulation/config.py @@ -1,7 +1,9 @@ # just a file that lets us define some constants that are used in multiple files the simulation import logging +from pathlib import Path from typing import Any, Dict +import torch.nn as nn from torch.cuda import is_available from extractors import ( # noqa: F401 @@ -18,10 +20,12 @@ n_actions_steering = 16 n_actions_speed = 16 lidar_max_range = 12.0 -device = "cuda" if is_available() else "cpu" +respawn_on_crash = True # whether to go backwards or to respawn when crashing # Training config +device = "cuda" if is_available() else "cpu" +save_dir = Path("~/.cache/autotech").expanduser() total_timesteps = 500_000 ppo_args: Dict[str, Any] = dict( n_steps=4096, @@ -33,6 +37,10 @@ normalize_advantage=True, device=device, ) + + +# Common extractor shared between the policy and value networks +# (cf: https://stable-baselines3.readthedocs.io/en/master/guide/custom_policy.html) ExtractorClass = TemporalResNetExtractor context_size = ExtractorClass.context_size lidar_horizontal_resolution = ExtractorClass.lidar_horizontal_resolution @@ -40,6 +48,15 @@ n_sensors = ExtractorClass.n_sensors +# Architecture of the model +policy_kwargs: Dict[str, Any] = dict( + features_extractor_class=ExtractorClass, + activation_fn=nn.ReLU, + # Architecture of the MLP heads for the Value and Policy networks + net_arch=[512, 512, 512], +) + + # Logging config LOG_LEVEL = logging.INFO FORMATTER = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") diff --git a/src/simulation/src/utils/onnx_utils.py b/src/simulation/src/utils/onnx_utils.py index 9ff3784..1b90adb 100644 --- a/src/simulation/src/utils/onnx_utils.py +++ b/src/simulation/src/utils/onnx_utils.py @@ -54,8 +54,7 @@ def test_onnx(model: OnPolicyAlgorithm): try: class_name = model.policy.features_extractor.__class__.__name__ - model_path = os.path.expanduser(f"~/.cache/autotech/model_{class_name}.onnx") - + model_path = c.save_dir / f"model_{class_name}.onnx" session = ort.InferenceSession(model_path) except Exception as e: print(f"Error loading ONNX model: {e}") diff --git a/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py b/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py index 8647300..7b656f4 100644 --- a/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py +++ b/src/simulation/src/webots/controllers/controller_world_supervisor/controller_world_supervisor.py @@ -170,7 +170,7 @@ def step(self): done = np.True_ elif b_collided: reward = np.float32(-0.5) - done = np.False_ + done = np.bool(c.respawn_on_crash) elif b_past_checkpoint: reward = np.float32(1.0) done = np.False_ From 919f057b9550290540c19e183710efe84f5a32a5 Mon Sep 17 00:00:00 2001 From: matthiasbienvenu Date: Tue, 24 Mar 2026 12:43:55 +0100 Subject: [PATCH 7/9] fix: some refactoring and less magic numbers - Refactore some stuff, removed all the gemini useless boilerplate comments - Use config.py instead of hard coding paths and values - Use Pathlib instead of str --- .../{lanch_one_simu.py => launch_one_simu.py} | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) rename src/simulation/scripts/{lanch_one_simu.py => launch_one_simu.py} (52%) diff --git a/src/simulation/scripts/lanch_one_simu.py b/src/simulation/scripts/launch_one_simu.py similarity index 52% rename from src/simulation/scripts/lanch_one_simu.py rename to src/simulation/scripts/launch_one_simu.py index 2c309e8..31c1e15 100644 --- a/src/simulation/scripts/lanch_one_simu.py +++ b/src/simulation/scripts/launch_one_simu.py @@ -1,30 +1,26 @@ import os import sys +from pathlib import Path -from typing import * import numpy as np import onnxruntime as ort -import gymnasium as gym - -from simulation.config import * -from utils import run_onnx_model +import simulation.config as c from extractors import ( # noqa: F401 CNN1DResNetExtractor, TemporalResNetExtractor, ) - from simulation import VehicleEnv +from utils import run_onnx_model -# ------------------------------------------------------------------------- - -ONNX_MODEL_PATH = "/home/exo/Bureau/CoVAPSy/model.onnx" +ONNX_MODEL_PATH = c.save_dir / f"model_{c.ExtractorClass}.onnx" -# --- Launching of inference motor ONNX Runtime (ORT) --- -def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: +def init_onnx_runtime_session(onnx_path: Path) -> ort.InferenceSession: if not os.path.exists(onnx_path): - raise FileNotFoundError(f"Le fichier ONNX est introuvable à : {onnx_path}. Veuillez l'exporter d'abord.") + raise FileNotFoundError( + f"The ONNX file could not be found at: {onnx_path}. Please export it first." + ) return ort.InferenceSession(onnx_path) @@ -34,49 +30,42 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: os.system('if [ -n "$(ls /tmp/autotech)" ]; then rm /tmp/autotech/*; fi') - # Starting of OnnxSession + # Starting the ONNX session try: ort_session = init_onnx_runtime_session(ONNX_MODEL_PATH) input_name = ort_session.get_inputs()[0].name output_name = ort_session.get_outputs()[0].name - print(f"Modèle ONNX chargé depuis {ONNX_MODEL_PATH}") + print(f"ONNX model loaded from {ONNX_MODEL_PATH}") print(f"Input Name: {input_name}, Output Name: {output_name}") except FileNotFoundError as e: - print(f"ERREUR : {e}") + print(f"ERROR: {e}") sys.exit(1) env = VehicleEnv(0, 0) obs, _ = env.reset() - print("Début de la simulation en mode inférence...") + print("Starting simulation in inference mode...") step_count = 0 while True: - # 1. On récupère les logits (probabilités) bruts de l'ONNX raw_action = run_onnx_model(ort_session, obs[None]) logits = np.array(raw_action).flatten() - # 2. On sépare le tableau en deux (Direction et Vitesse) - # On utilise n_actions_steering et n_actions_speed venant de config.py - steer_logits = logits[:n_actions_steering] - speed_logits = logits[n_actions_steering:] + steer_logits = logits[: c.n_actions_steering] + speed_logits = logits[c.n_actions_steering :] - # 3. L'IA choisit l'action qui a le score (logit) le plus élevé action_steer = np.argmax(steer_logits) action_speed = np.argmax(speed_logits) - # 4. On crée le tableau final parfaitement formaté pour Webots (strictement 2 entiers) action = np.array([action_steer, action_speed], dtype=np.int64) - # 5. Exécuter l'action dans l'environnement next_obs, reward, done, truncated, info = env.step(action) step_count += 1 - # Gestion des fins d'épisodes if done: - print(f"Épisode(s) terminé(s) après {step_count} étapes.") + print(f"Episode(s) finished after {step_count} steps.") step_count = 0 fresh_frame = next_obs[:, -1:] @@ -85,6 +74,3 @@ def init_onnx_runtime_session(onnx_path: str) -> ort.InferenceSession: obs = env.context else: obs = next_obs - - env.close() - print("Simulation terminée. Environnements fermés.") \ No newline at end of file From 808f2e5ed6f912872dab0a147506a622e7215fee Mon Sep 17 00:00:00 2001 From: matthiasbienvenu Date: Tue, 24 Mar 2026 12:47:22 +0100 Subject: [PATCH 8/9] fix: forgot the .__name__ --- src/simulation/scripts/launch_one_simu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/simulation/scripts/launch_one_simu.py b/src/simulation/scripts/launch_one_simu.py index 31c1e15..aad8dde 100644 --- a/src/simulation/scripts/launch_one_simu.py +++ b/src/simulation/scripts/launch_one_simu.py @@ -13,7 +13,7 @@ from simulation import VehicleEnv from utils import run_onnx_model -ONNX_MODEL_PATH = c.save_dir / f"model_{c.ExtractorClass}.onnx" +ONNX_MODEL_PATH = c.save_dir / f"model_{c.ExtractorClass.__name__}.onnx" def init_onnx_runtime_session(onnx_path: Path) -> ort.InferenceSession: From 3c9e4242ab41bb3e5982c1116d4a94263f3b6dad Mon Sep 17 00:00:00 2001 From: matthiasbienvenu Date: Tue, 24 Mar 2026 14:40:29 +0100 Subject: [PATCH 9/9] fix: removed B_DEBUG --- src/simulation/src/simulation/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/simulation/src/simulation/config.py b/src/simulation/src/simulation/config.py index 70347d2..5fb064f 100644 --- a/src/simulation/src/simulation/config.py +++ b/src/simulation/src/simulation/config.py @@ -60,5 +60,3 @@ # Logging config LOG_LEVEL = logging.INFO FORMATTER = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - -B_DEBUG = False \ No newline at end of file