""" Module principal : Simulation complète des protocoles LEACH et LEACH-C Supporte à la fois les réseaux statiques et dynamiques. Supports two Simpy integration approaches: - Default: Lightweight wrapper (original approach - faster, compatible) - Hybrid: Full refactor (--simpy-hybrid flag - Paul's approach with Sorti's quality) """ import random import json import sys from datetime import datetime from node import Node from leach import LEACH from leach_c import LEACHC from config import ( FIELD_WIDTH, FIELD_HEIGHT, INITIAL_ENERGY, BS_POSITION, SCENARIOS, get_num_rounds_for_scenario, DEBUG, ENABLE_MOBILITY ) import config class Simulator: """ Contrôleur principal de la simulation. Crée les nœuds, lance les protocoles, et collecte les résultats. """ def __init__(self, scenario, use_simpy_hybrid=False): """ Initialise un simulateur pour un scénario donné. Args: scenario (dict): Configuration du scénario (l, p, n, name) use_simpy_hybrid (bool): Use hybrid SimPy simulator if True """ self.scenario = scenario self.packet_size = scenario["l"] self.probability_ch = scenario["p"] self.num_nodes = scenario["n"] self.scenario_name = scenario["name"] self.use_simpy_hybrid = use_simpy_hybrid self.results = {} self.nodes = [] def initialize_nodes(self): """Crée et initialise les nœuds.""" self.nodes = [] for i in range(self.num_nodes): # Position aléatoire dans le champ x = random.uniform(0, FIELD_WIDTH) y = random.uniform(0, FIELD_HEIGHT) node = Node(i, x, y, INITIAL_ENERGY) self.nodes.append(node) def run_protocol(self, protocol_name, protocol_class): """ Lance un protocole et collecte les résultats. Args: protocol_name (str): "LEACH" ou "LEACH-C" protocol_class: Classe du protocole (LEACH ou LEACHC) Returns: dict: Métriques et données du protocole """ # Réinitialiser les nœuds for node in self.nodes: node.energy = INITIAL_ENERGY node.is_alive = True node.reset_for_round() # Créer et lancer le protocole protocol = protocol_class(self.nodes, self.probability_ch, self.packet_size) num_rounds = get_num_rounds_for_scenario(self.num_nodes) print(f" Exécution {protocol_name} pour {self.scenario_name}...") print(f" - Packets: {self.packet_size} bits") print(f" - Probabilité: {self.probability_ch}") print(f" - Nœuds: {self.num_nodes}") print(f" - Rounds à exécuter: {num_rounds}") # Choose simulation approach if self.use_simpy_hybrid: # Use hybrid full-featured Simpy simulator from simpy_simulator_hybrid import HybridSimPySimulator simulator = HybridSimPySimulator( protocol_name=protocol_name, nodes=self.nodes, packet_size=self.packet_size, probability_ch=self.probability_ch, max_rounds=num_rounds ) result_data = simulator.run() metrics = { "final_alive_nodes": sum(1 for n in self.nodes if n.is_alive), "first_dead_node_round": result_data["fdn"], "first_muted_round": result_data["fmr"], "dlbi": result_data["dlbi"], "rspi": result_data["rspi"], "packets_to_ch": result_data["total_packets_to_ch"], "packets_to_bs": result_data["total_packets_to_bs"], } detailed = result_data["rounds_data"] if hasattr(result_data["metrics"], "rounds_data") else [] else: # Use original lightweight approach protocol.run_simulation(num_rounds) metrics = protocol.get_metrics(num_rounds) detailed = protocol.get_detailed_metrics() print(f" OK - {protocol_name} terminé") print(f" - Alive nodes: {metrics['final_alive_nodes']}") print(f" - FDN: {metrics['first_dead_node_round']}") print(f" - DLBI: {metrics['dlbi']:.4f}") print(f" - RSPI: {metrics['rspi']:.4f}") return { "protocol": protocol_name, "metrics": metrics, "detailed": detailed, } def run_simulation(self): """ Lance la simulation complète (LEACH et LEACH-C). """ print(f"\n{'='*60}") print(f"Simulation: {self.scenario_name}") print(f"{'='*60}") # Initialiser les nœuds self.initialize_nodes() # Lancer LEACH leach_results = self.run_protocol("LEACH", LEACH) self.results["LEACH"] = leach_results # Lancer LEACH-C leachc_results = self.run_protocol("LEACH-C", LEACHC) self.results["LEACH-C"] = leachc_results print(f"{'='*60}\n") return self.results def get_results(self): """Retourne les résultats de la simulation.""" return self.results def run_all_scenarios(is_static=False, use_simpy_hybrid=False): """ Lance les simulations pour tous les scénarios. Args: is_static (bool): Si True, désactive la mobilité (mode statique) use_simpy_hybrid (bool): Use hybrid SimPy full-featured simulator Returns: dict: Résultats pour tous les scénarios """ # Définir le mode de mobilité config.ENABLE_MOBILITY = not is_static all_results = {} mode_label = "STATIQUES" if is_static else "DYNAMIQUES" sim_approach = "HYBRID SIMPY" if use_simpy_hybrid else "STANDARD" print(f"\n{'#'*60}") print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX {mode_label} ({sim_approach})") print(f"# Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'#'*60}\n") for scenario in SCENARIOS: print(f"Scénario: {scenario['name']}") simulator = Simulator(scenario, use_simpy_hybrid=use_simpy_hybrid) results = simulator.run_simulation() all_results[scenario["name"]] = results print(f"\n{'#'*60}") print(f"# SIMULATIONS TERMINÉES - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f"{'#'*60}\n") return all_results def save_results(results, output_file): """ Sauvegarde les résultats en JSON. Args: results (dict): Résultats de toutes les simulations output_file (str): Chemin du fichier de sortie """ # Convertir en format sérialisable JSON json_results = {} for scenario_name, scenario_data in results.items(): json_results[scenario_name] = { "LEACH": { "metrics": scenario_data["LEACH"]["metrics"], "detailed_rounds": scenario_data["LEACH"]["detailed"][:20] # Premiers 20 rounds }, "LEACH-C": { "metrics": scenario_data["LEACH-C"]["metrics"], "detailed_rounds": scenario_data["LEACH-C"]["detailed"][:20] } } with open(output_file, 'w') as f: json.dump(json_results, f, indent=2) print(f"OK - Résultats sauvegardés: {output_file}") if __name__ == "__main__": # Parse command-line arguments use_simpy_hybrid = "--simpy-hybrid" in sys.argv if use_simpy_hybrid: print("\n" + "="*70) print("USING HYBRID SIMPY SIMULATOR (Paul's full refactor + Sorti's quality)") print("="*70) # Graine de randomisation pour reproductibilité random.seed(42) # Lancer les simulations DYNAMIQUES print("\n" + "="*70) print("PHASE 1: SIMULATIONS DYNAMIQUES (avec mobilité)") print("="*70) dynamic_results = run_all_scenarios(is_static=False, use_simpy_hybrid=use_simpy_hybrid) # Sauvegarder les résultats dynamiques save_results(dynamic_results, "/home/sorti/projects/AlgoRep/results/simulation_results_dynamic.json") # Lancer les simulations STATIQUES print("\n" + "="*70) print("PHASE 2: SIMULATIONS STATIQUES (sans mobilité)") print("="*70) random.seed(42) # Réinitialiser la graine pour avoir les mêmes positions initiales static_results = run_all_scenarios(is_static=True, use_simpy_hybrid=use_simpy_hybrid) # Sauvegarder les résultats statiques save_results(static_results, "/home/sorti/projects/AlgoRep/results/simulation_results_static.json") # Afficher un résumé print("\n" + "="*70) print("RÉSUMÉ DES RÉSULTATS - DYNAMIQUE vs STATIQUE") print("="*70) for scenario_name in SCENARIOS[0:1]: # Afficher un exemple scenario_label = scenario_name['name'] print(f"\n{scenario_label}:") print("-" * 70) for protocol in ["LEACH", "LEACH-C"]: dynamic_metrics = dynamic_results[scenario_label][protocol]["metrics"] static_metrics = static_results[scenario_label][protocol]["metrics"] dyn_fdn = dynamic_metrics['first_dead_node_round'] or "N/A" stat_fdn = static_metrics['first_dead_node_round'] or "N/A" dyn_fmr = dynamic_metrics['first_muted_round'] or "N/A" stat_fmr = static_metrics['first_muted_round'] or "N/A" print(f"\n {protocol}:") print(f" Métrique | Dynamique | Statique | Différence") if isinstance(dyn_fdn, int) and isinstance(stat_fdn, int): print(f" FDN (First Dead Node) | {dyn_fdn:10} | {stat_fdn:10} | {stat_fdn - dyn_fdn:10.0f}") else: print(f" FDN (First Dead Node) | {str(dyn_fdn):10} | {str(stat_fdn):10} | N/A") if isinstance(dyn_fmr, int) and isinstance(stat_fmr, int): print(f" FMR (First Muted Rd) | {dyn_fmr:10} | {stat_fmr:10} | {stat_fmr - dyn_fmr:10.0f}") else: print(f" FMR (First Muted Rd) | {str(dyn_fmr):10} | {str(stat_fmr):10} | N/A") print(f" DLBI (Load Balance) | {dynamic_metrics['dlbi']:10.4f} | {static_metrics['dlbi']:10.4f} | {static_metrics['dlbi'] - dynamic_metrics['dlbi']:10.4f}") print(f" RSPI (Resilience) | {dynamic_metrics['rspi']:10.4f} | {static_metrics['rspi']:10.4f} | {static_metrics['rspi'] - dynamic_metrics['rspi']:10.4f}") print(f"\n✓ Résultats dynamiques sauvegardés: /home/sorti/projects/AlgoRep/results/simulation_results_dynamic.json") print(f"✓ Résultats statiques sauvegardés: /home/sorti/projects/AlgoRep/results/simulation_results_static.json") print(f"\n💡 Tip: Use '--simpy-hybrid' flag to run with hybrid full-featured Simpy simulator")