- Updated section headings for consistency and clarity. - Converted tables from markdown format to a structured table format for better readability. - Enhanced descriptions in the analysis and conclusion sections to provide clearer insights into the performance of LEACH and LEACH-C protocols. - Corrected minor typographical errors for improved professionalism.
301 lines
12 KiB
Python
301 lines
12 KiB
Python
"""
|
||
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)
|