""" Génération de graphiques et analyses visuelles pour les résultats de simulation LEACH/LEACH-C. Lit les métriques JSON et produit des PNG comparatifs. """ import json import matplotlib.pyplot as plt import matplotlib matplotlib.use('Agg') # Pas d'affichage graphique (serveur) class ResultsAnalyzer: """Crée les graphiques de comparaison entre les protocoles et scénarios.""" def __init__(self, results_file): """Charge le fichier JSON contenant les résultats de toutes les simulations.""" with open(results_file, 'r') as f: self.results = json.load(f) def generate_comparison_graphs(self, output_dir): """Crée tous les graphiques PNG en une seule passe.""" self._plot_fdn_comparison(output_dir) self._plot_fmr_comparison(output_dir) self._plot_dlbi_comparison(output_dir) self._plot_rspi_comparison(output_dir) self._plot_alive_nodes(output_dir) def _plot_fdn_comparison(self, output_dir): """Graphique en barres : Premier Nœud Mort pour chaque scénario.""" scenarios = list(self.results.keys()) leach_fdn = [] leachc_fdn = [] for scenario in scenarios: leach_metrics = self.results[scenario]["LEACH"]["metrics"] leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"] leach_fdn.append(leach_metrics.get("fdn") or 0) leachc_fdn.append(leachc_metrics.get("fdn") or 0) fig, ax = plt.subplots(figsize=(12, 6)) x_pos = range(len(scenarios)) width = 0.35 ax.bar([i - width/2 for i in x_pos], leach_fdn, width, label='LEACH', color='#FF6B6B') ax.bar([i + width/2 for i in x_pos], leachc_fdn, width, label='LEACH-C', color='#4ECDC4') ax.set_xlabel('Scénario', fontsize=12) ax.set_ylabel('Premier Nœud Mort (Round)', fontsize=12) ax.set_title('Comparaison FDN (First Dead Node)', fontsize=14, fontweight='bold') ax.set_xticks(x_pos) ax.set_xticklabels(scenarios, rotation=45, ha='right') ax.legend() ax.grid(axis='y', alpha=0.3) # Note si tous les nœuds survivent (valeur nulle = pas de mort) if all(f == 0 for f in leach_fdn + leachc_fdn): ax.text(0.5, 0.5, 'Pas de nœuds morts\n(tous les nœuds vivants)', transform=ax.transAxes, ha='center', va='center', fontsize=12, bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.7)) plt.tight_layout() plt.savefig(f"{output_dir}/01_FDN_Comparison.png", dpi=300) plt.close() def _plot_fmr_comparison(self, output_dir): """Graphique en barres : Premier Round Muet pour chaque scénario.""" scenarios = list(self.results.keys()) leach_fmr = [] leachc_fmr = [] for scenario in scenarios: leach_metrics = self.results[scenario]["LEACH"]["metrics"] leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"] leach_fmr.append(leach_metrics.get("fmr") or 0) leachc_fmr.append(leachc_metrics.get("fmr") or 0) fig, ax = plt.subplots(figsize=(12, 6)) x_pos = range(len(scenarios)) width = 0.35 ax.bar([i - width/2 for i in x_pos], leach_fmr, width, label='LEACH', color='#FF6B6B') ax.bar([i + width/2 for i in x_pos], leachc_fmr, width, label='LEACH-C', color='#4ECDC4') ax.set_xlabel('Scénario', fontsize=12) ax.set_ylabel('Premier Round Muet', fontsize=12) ax.set_title('Comparaison FMR (First Muted Round)', fontsize=14, fontweight='bold') ax.set_xticks(x_pos) ax.set_xticklabels(scenarios, rotation=45, ha='right') ax.legend() ax.grid(axis='y', alpha=0.3) plt.tight_layout() plt.savefig(f"{output_dir}/02_FMR_Comparison.png", dpi=300) plt.close() def _plot_dlbi_comparison(self, output_dir): """Graphique en barres : Indice d'Équilibre de Charge pour chaque scénario.""" scenarios = list(self.results.keys()) leach_dlbi = [] leachc_dlbi = [] for scenario in scenarios: leach_metrics = self.results[scenario]["LEACH"]["metrics"] leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"] leach_dlbi.append(leach_metrics.get("dlbi", 0)) leachc_dlbi.append(leachc_metrics.get("dlbi", 0)) fig, ax = plt.subplots(figsize=(12, 6)) x_pos = range(len(scenarios)) width = 0.35 ax.bar([i - width/2 for i in x_pos], leach_dlbi, width, label='LEACH', color='#FF6B6B') ax.bar([i + width/2 for i in x_pos], leachc_dlbi, width, label='LEACH-C', color='#4ECDC4') ax.set_xlabel('Scénario', fontsize=12) ax.set_ylabel('DLBI (0 à 1)', fontsize=12) ax.set_title('Comparaison DLBI (Dynamic Load Balancing Index)', fontsize=14, fontweight='bold') ax.set_xticks(x_pos) ax.set_xticklabels(scenarios, rotation=45, ha='right') ax.set_ylim([0, 1.1]) ax.legend() ax.grid(axis='y', alpha=0.3) plt.tight_layout() plt.savefig(f"{output_dir}/03_DLBI_Comparison.png", dpi=300) plt.close() def _plot_rspi_comparison(self, output_dir): """Graphique tableau : Indice de Stabilité du Réseau avec explications.""" scenarios = list(self.results.keys()) leach_rspi = [] leachc_rspi = [] for scenario in scenarios: leach_metrics = self.results[scenario]["LEACH"]["metrics"] leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"] leach_rspi.append(leach_metrics.get("rspi", 0)) leachc_rspi.append(leachc_metrics.get("rspi", 0)) fig, ax = plt.subplots(figsize=(12, 8)) ax.axis('off') # Tableau affichant RSPI pour tous les scénarios data = [] for scenario, lrspi, crspi in zip(scenarios, leach_rspi, leachc_rspi): data.append([scenario, f"{lrspi:.6f}", f"{crspi:.6f}"]) data.insert(0, ['Scénario', 'LEACH RSPI', 'LEACH-C RSPI']) table = ax.table(cellText=data, cellLoc='center', loc='center', colWidths=[0.40, 0.30, 0.30]) table.auto_set_font_size(False) table.set_fontsize(11) table.scale(1, 3) # Style en-tête for i in range(3): table[(0, i)].set_facecolor('#4ECDC4') table[(0, i)].set_text_props(weight='bold', color='white', size=12) # Style lignes alternées for i in range(1, len(data)): for j in range(3): if i % 2 == 0: table[(i, j)].set_facecolor('#f0f0f0') else: table[(i, j)].set_facecolor('white') table[(i, j)].set_text_props(size=11) fig.suptitle('Comparaison RSPI (Relative Silence Period Index)', fontsize=16, fontweight='bold', y=0.98) explanation = ( 'RSPI = 0.0000 indique que le réseau reste STABLE pendant toute la simulation.\n' 'Les nœuds survivent jusqu\'à la fin, ce qui est un EXCELLENT résultat.\n\n' 'RSPI = 2 × [(1 - FR_muted/R_max) × (1 - LR_dead/R_max)] / [(1 - FR_muted/R_max) + (1 - LR_dead/R_max)]\n' 'Quand LR_dead = R_max (dernier nœud meurt à la fin), RSPI → 0' ) fig.text(0.5, 0.05, explanation, ha='center', fontsize=10, style='italic', bbox=dict(boxstyle='round', facecolor='lightyellow', alpha=0.7, pad=1)) plt.tight_layout(rect=[0, 0.14, 1, 0.96]) plt.savefig(f"{output_dir}/04_RSPI_Comparison.png", dpi=300, bbox_inches='tight') plt.close() def _plot_alive_nodes(self, output_dir): """Graphique récapitulatif : Tous les résultats (FDN, FMR, DLBI, RSPI) par scénario.""" scenarios = list(self.results.keys()) fig = plt.figure(figsize=(16, 12)) ax = fig.add_subplot(111) ax.axis('off') # Préparer les données : FDN, FMR, DLBI, RSPI pour chaque protocole data = [] for scenario in scenarios: scenario_short = scenario.replace('Scenario_', '').replace('_', ' ') row = [scenario_short] for protocol in ["LEACH", "LEACH-C"]: if protocol in self.results[scenario]: metrics = self.results[scenario][protocol]["metrics"] fdn = metrics['fdn'] if metrics['fdn'] is not None else "—" fmr = metrics['fmr'] if metrics['fmr'] is not None else "—" dlbi = f"{metrics['dlbi']:.2f}" rspi = f"{metrics['rspi']:.4f}" cell_text = f"FDN: {fdn}\nFMR: {fmr}\nDLBI: {dlbi}\nRSPI: {rspi}" row.append(cell_text) else: row.append("N/A") data.append(row) data.insert(0, ["Scénario", "LEACH", "LEACH-C"]) # Créer le tableau table = ax.table(cellText=data, cellLoc='center', loc='center', colWidths=[0.2, 0.4, 0.4]) table.auto_set_font_size(False) table.set_fontsize(11) table.scale(1, 3.5) # Style en-tête for i in range(3): cell = table[(0, i)] cell.set_facecolor('#4ECDC4') cell.set_text_props(weight='bold', color='white', size=13) cell.set_height(0.12) # Style lignes alternées et bordures for i in range(1, len(data)): for j in range(3): cell = table[(i, j)] if i % 2 == 0: cell.set_facecolor('#f5f5f5') else: cell.set_facecolor('white') cell.set_text_props(size=11, ha='center', va='center') cell.set_height(0.13) cell.set_linewidth(1.5) cell.set_edgecolor('#cccccc') for key, cell in table.get_celld().items(): cell.set_linewidth(2) cell.set_edgecolor('#333333') fig.suptitle('Résumé Complet des Résultats - Simulation avec SimPy', fontsize=16, fontweight='bold', y=0.98) legend_text = ( 'FDN: First Dead Node | FMR: First Muted Round | DLBI: Load Balancing (>0.7=excellent) | RSPI: Silence Index (0=excellent)' ) fig.text(0.5, 0.01, legend_text, ha='center', fontsize=10, style='italic', bbox=dict(boxstyle='round', facecolor='#ffffcc', alpha=0.8, pad=1), family='monospace') plt.tight_layout(rect=[0, 0.06, 1, 0.96]) plt.savefig(f"{output_dir}/05_Alive_Nodes_Over_Time.png", dpi=300, bbox_inches='tight') plt.close() def generate_summary_table(self, output_file): """Exporte les résultats dans un fichier CSV pour analyse externe.""" with open(output_file, 'w') as f: f.write("Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI\n") for scenario_name, scenario_data in self.results.items(): for protocol in ["LEACH", "LEACH-C"]: metrics = scenario_data[protocol]["metrics"] f.write(f"{scenario_name},{protocol},") f.write(f"{metrics.get('first_dead_node_round', 'N/A')},") f.write(f"{metrics.get('first_muted_round', 'N/A')},") f.write(f"{metrics.get('final_alive_nodes', 'N/A')},") f.write(f"{metrics.get('dlbi', 'N/A'):.4f},") f.write(f"{metrics.get('rspi', 'N/A'):.4f}\n") print(f"OK - Tableau récapitulatif: {output_file}") if __name__ == "__main__": import os script_dir = os.path.dirname(os.path.abspath(__file__)) project_dir = os.path.dirname(script_dir) results_dir = os.path.join(project_dir, "results") results_file = os.path.join(results_dir, "simulation_results.json") summary_file = os.path.join(results_dir, "summary.csv") analyzer = ResultsAnalyzer(results_file) analyzer.generate_comparison_graphs(results_dir) analyzer.generate_summary_table(summary_file)