AlgoRep/code/analysis.py
paul.roost fff067809f Refactor report formatting and improve clarity in observations
- 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.
2025-11-03 14:12:51 +01:00

301 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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)