Compare commits

..

No commits in common. "paul/simpy" and "main" have entirely different histories.

22 changed files with 12196 additions and 14020 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,32 +1,50 @@
"""
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.
Analyseur et générateur de graphiques pour les résultats de simulation
"""
import json
import matplotlib.pyplot as plt
import matplotlib
matplotlib.use('Agg') # Pas d'affichage graphique (serveur)
matplotlib.use('Agg') # Backend sans affichage
class ResultsAnalyzer:
"""Crée les graphiques de comparaison entre les protocoles et scénarios."""
"""Analyse et visualise les résultats des simulations."""
def __init__(self, results_file):
"""Charge le fichier JSON contenant les résultats de toutes les simulations."""
"""
Charge les résultats depuis un fichier JSON.
Args:
results_file (str): Chemin vers le fichier JSON
"""
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."""
"""
Génère les graphiques de comparaison LEACH vs LEACH-C.
Args:
output_dir (str): Répertoire de sortie
"""
# 1. FDN Comparison
self._plot_fdn_comparison(output_dir)
# 2. FMR Comparison
self._plot_fmr_comparison(output_dir)
# 3. DLBI Comparison
self._plot_dlbi_comparison(output_dir)
# 4. RSPI Comparison
self._plot_rspi_comparison(output_dir)
# 5. Alive Nodes Over Rounds
self._plot_alive_nodes(output_dir)
def _plot_fdn_comparison(self, output_dir):
"""Graphique en barres : Premier Nœud Mort pour chaque scénario."""
"""Graphique : FDN pour tous les scénarios."""
scenarios = list(self.results.keys())
leach_fdn = []
leachc_fdn = []
@ -35,8 +53,8 @@ class ResultsAnalyzer:
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)
leach_fdn.append(leach_metrics.get("first_dead_node_round") or 0)
leachc_fdn.append(leachc_metrics.get("first_dead_node_round") or 0)
fig, ax = plt.subplots(figsize=(12, 6))
x_pos = range(len(scenarios))
@ -53,18 +71,12 @@ class ResultsAnalyzer:
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."""
"""Graphique : FMR pour tous les scénarios."""
scenarios = list(self.results.keys())
leach_fmr = []
leachc_fmr = []
@ -73,8 +85,8 @@ class ResultsAnalyzer:
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)
leach_fmr.append(leach_metrics.get("first_muted_round") or 9999)
leachc_fmr.append(leachc_metrics.get("first_muted_round") or 9999)
fig, ax = plt.subplots(figsize=(12, 6))
x_pos = range(len(scenarios))
@ -96,7 +108,7 @@ class ResultsAnalyzer:
plt.close()
def _plot_dlbi_comparison(self, output_dir):
"""Graphique en barres : Indice d'Équilibre de Charge pour chaque scénario."""
"""Graphique : DLBI pour tous les scénarios."""
scenarios = list(self.results.keys())
leach_dlbi = []
leachc_dlbi = []
@ -129,7 +141,7 @@ class ResultsAnalyzer:
plt.close()
def _plot_rspi_comparison(self, output_dir):
"""Graphique tableau : Indice de Stabilité du Réseau avec explications."""
"""Graphique : RSPI pour tous les scénarios."""
scenarios = list(self.results.keys())
leach_rspi = []
leachc_rspi = []
@ -141,136 +153,71 @@ class ResultsAnalyzer:
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')
fig, ax = plt.subplots(figsize=(12, 6))
x_pos = range(len(scenarios))
width = 0.35
# 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}"])
ax.bar([i - width/2 for i in x_pos], leach_rspi, width, label='LEACH', color='#FF6B6B')
ax.bar([i + width/2 for i in x_pos], leachc_rspi, width, label='LEACH-C', color='#4ECDC4')
data.insert(0, ['Scénario', 'LEACH RSPI', 'LEACH-C RSPI'])
ax.set_xlabel('Scénario', fontsize=12)
ax.set_ylabel('RSPI (0 à 1)', fontsize=12)
ax.set_title('Comparaison RSPI (Relative Silence Period 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)
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.tight_layout()
plt.savefig(f"{output_dir}/04_RSPI_Comparison.png", dpi=300)
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())
"""Graphique : Nombre de nœuds vivants au fil du temps."""
scenarios = list(self.results.keys())[:3] # Premiers 3 scénarios
fig = plt.figure(figsize=(16, 12))
ax = fig.add_subplot(111)
ax.axis('off')
fig, axes = plt.subplots(len(scenarios), 2, figsize=(14, 4*len(scenarios)))
# 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 idx, scenario in enumerate(scenarios):
# LEACH
leach_detailed = self.results[scenario]["LEACH"]["detailed_rounds"]
rounds = [r["round"] for r in leach_detailed]
alive = [r["alive_nodes"] for r in leach_detailed]
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}"
axes[idx, 0].plot(rounds, alive, marker='o', label='LEACH', color='#FF6B6B')
axes[idx, 0].set_xlabel('Round')
axes[idx, 0].set_ylabel('Nœuds Vivants')
axes[idx, 0].set_title(f'LEACH - {scenario}')
axes[idx, 0].grid(alpha=0.3)
cell_text = f"FDN: {fdn}\nFMR: {fmr}\nDLBI: {dlbi}\nRSPI: {rspi}"
row.append(cell_text)
else:
row.append("N/A")
# LEACH-C
leachc_detailed = self.results[scenario]["LEACH-C"]["detailed_rounds"]
rounds = [r["round"] for r in leachc_detailed]
alive = [r["alive_nodes"] for r in leachc_detailed]
data.append(row)
axes[idx, 1].plot(rounds, alive, marker='s', label='LEACH-C', color='#4ECDC4')
axes[idx, 1].set_xlabel('Round')
axes[idx, 1].set_ylabel('Nœuds Vivants')
axes[idx, 1].set_title(f'LEACH-C - {scenario}')
axes[idx, 1].grid(alpha=0.3)
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.tight_layout()
plt.savefig(f"{output_dir}/05_Alive_Nodes_Over_Time.png", dpi=300)
plt.close()
def generate_summary_table(self, output_file):
"""Exporte les résultats dans un fichier CSV pour analyse externe."""
"""
Génère un tableau récapitulatif en CSV.
Args:
output_file (str): Chemin du fichier CSV
"""
with open(output_file, 'w') as f:
# En-tête
f.write("Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI\n")
# Données
for scenario_name, scenario_data in self.results.items():
for protocol in ["LEACH", "LEACH-C"]:
metrics = scenario_data[protocol]["metrics"]
@ -286,15 +233,6 @@ class ResultsAnalyzer:
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)
analyzer = ResultsAnalyzer("/home/paul/algo/results/simulation_results.json")
analyzer.generate_comparison_graphs("/home/paul/algo/results")
analyzer.generate_summary_table("/home/paul/algo/results/summary.csv")

View File

@ -63,10 +63,8 @@ def get_num_rounds_for_scenario(num_nodes):
"""
Estime le nombre de rounds pour une simulation complète.
Plus de nœuds = plus d'énergie disponible = plus de rounds.
Avec SimPy, visant ~3000 rounds par scénario.
"""
return 3000 + (num_nodes - 100) * 10 # Augmenté pour plus de données
return 2000 + (num_nodes - 100) * 5 # Ajustement heuristique
# ============= FLAGS DE DEBUG/LOGGING =============

View File

@ -154,7 +154,7 @@ class LEACH:
if not ch_elected:
# Muted round - pas de CH
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
self.metrics.update_dead_nodes(self.nodes, self.round_num)
self.metrics.update_dead_nodes(self.nodes)
return False
# Phase 2 : Formation des clusters
@ -171,7 +171,7 @@ class LEACH:
# Enregistrement des métriques
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
packets_to_ch, packets_to_bs, muted=False)
self.metrics.update_dead_nodes(self.nodes, self.round_num)
self.metrics.update_dead_nodes(self.nodes)
return True

View File

@ -185,7 +185,7 @@ class LEACHC:
if not ch_candidates:
# Aucun nœud vivant
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
self.metrics.update_dead_nodes(self.nodes, self.round_num)
self.metrics.update_dead_nodes(self.nodes)
return False
# Marquer les CHs
@ -211,7 +211,7 @@ class LEACHC:
# Enregistrement des métriques
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
packets_to_ch, packets_to_bs, muted=False)
self.metrics.update_dead_nodes(self.nodes, self.round_num)
self.metrics.update_dead_nodes(self.nodes)
return True

View File

@ -1,71 +1,150 @@
"""
Module principal : Simulation complète des protocoles LEACH et LEACH-C avec SimPy
Module principal : Simulation complète des protocoles LEACH et LEACH-C
"""
import random
import json
import os
from datetime import datetime
from simulator_simpy import SimulatorSimPy
from config import SCENARIOS, DEBUG
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
)
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):
"""
Initialise un simulateur pour un scénario donné.
Args:
scenario (dict): Configuration du scénario (l, p, n, name)
"""
self.scenario = scenario
self.packet_size = scenario["l"]
self.probability_ch = scenario["p"]
self.num_nodes = scenario["n"]
self.scenario_name = scenario["name"]
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}")
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():
"""
Lance les simulations pour tous les scénarios.
Utilise SimPy pour simulation à événements discrets.
Returns:
dict: Résultats pour tous les scénarios
"""
all_results = {}
print(f"\n{'#'*70}")
print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES (SimPy)")
print(f"\n{'#'*60}")
print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES")
print(f"# Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'#'*70}\n")
print(f"{'#'*60}\n")
for scenario in SCENARIOS:
scenario_name = scenario["name"]
print(f"\n{'='*70}")
print(f"SCÉNARIO: {scenario_name}")
print(f"{'='*70}")
print(f"Scénario: {scenario['name']}")
scenario_results = {}
simulator = Simulator(scenario)
results = simulator.run_simulation()
# Simuler LEACH
print(f"\n>>> Exécution LEACH...")
sim_leach = SimulatorSimPy(scenario, protocol_name="LEACH")
leach_data = sim_leach.run()
scenario_results["LEACH"] = {
"metrics": {
"fdn": leach_data["fdn"],
"fmr": leach_data["fmr"],
"dlbi": leach_data["dlbi"],
"rspi": leach_data["rspi"],
},
"rounds_data": leach_data["rounds_data"]
}
all_results[scenario["name"]] = results
# Simuler LEACH-C
print(f"\n>>> Exécution LEACH-C...")
sim_leachc = SimulatorSimPy(scenario, protocol_name="LEACH-C")
leachc_data = sim_leachc.run()
scenario_results["LEACH-C"] = {
"metrics": {
"fdn": leachc_data["fdn"],
"fmr": leachc_data["fmr"],
"dlbi": leachc_data["dlbi"],
"rspi": leachc_data["rspi"],
},
"rounds_data": leachc_data["rounds_data"]
}
all_results[scenario_name] = scenario_results
print(f"\n{'#'*70}")
print(f"\n{'#'*60}")
print(f"# SIMULATIONS TERMINÉES - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'#'*70}\n")
print(f"{'#'*60}\n")
return all_results
@ -82,12 +161,15 @@ def save_results(results, output_file):
json_results = {}
for scenario_name, scenario_data in results.items():
json_results[scenario_name] = {}
for protocol in ["LEACH", "LEACH-C"]:
if protocol in scenario_data:
json_results[scenario_name][protocol] = {
"metrics": scenario_data[protocol]["metrics"]
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:
@ -96,45 +178,28 @@ def save_results(results, output_file):
print(f"OK - Résultats sauvegardés: {output_file}")
def print_summary(results):
"""Affiche un résumé des résultats."""
print("\n" + "="*70)
print("RÉSUMÉ DES RÉSULTATS")
print("="*70)
for scenario_name, scenario_data in results.items():
print(f"\n{scenario_name}:")
for protocol in ["LEACH", "LEACH-C"]:
if protocol in scenario_data:
metrics = scenario_data[protocol]["metrics"]
print(f"\n {protocol}:")
print(f" FDN (First Dead Node): {metrics['fdn']}")
print(f" FMR (First Muted Round): {metrics['fmr']}")
print(f" DLBI: {metrics['dlbi']:.4f}")
print(f" RSPI: {metrics['rspi']:.4f}")
if __name__ == "__main__":
# Graine de randomisation pour reproductibilité
random.seed(42)
# Déterminer le répertoire racine du projet
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(script_dir)
results_dir = os.path.join(project_dir, "results")
# Créer le répertoire results s'il n'existe pas
os.makedirs(results_dir, exist_ok=True)
# Lancer toutes les simulations
all_results = run_all_scenarios()
# Afficher un résumé
print_summary(all_results)
# Sauvegarder les résultats
output_file = os.path.join(results_dir, "simulation_results.json")
save_results(all_results, output_file)
save_results(all_results, "/home/paul/algo/results/simulation_results.json")
print(f"\nRésultats disponibles dans: {results_dir}")
# Afficher un résumé
print("\n" + "="*60)
print("RÉSUMÉ DES RÉSULTATS")
print("="*60)
for scenario_name, scenario_data in all_results.items():
print(f"\n{scenario_name}:")
for protocol in ["LEACH", "LEACH-C"]:
metrics = scenario_data[protocol]["metrics"]
print(f"\n {protocol}:")
print(f" FDN (First Dead Node): {metrics['first_dead_node_round']}")
print(f" FMR (First Muted Round): {metrics['first_muted_round']}")
print(f" DLBI: {metrics['dlbi']:.4f}")
print(f" RSPI: {metrics['rspi']:.4f}")

View File

@ -84,13 +84,15 @@ class Metrics:
cluster_size += 1
self.ch_loads_per_round[round_num][ch.node_id] = cluster_size
def update_dead_nodes(self, nodes, round_num):
def update_dead_nodes(self, nodes):
"""Met à jour les rounds de décès des nœuds."""
current_round = len(self.rounds_data)
for node in nodes:
if not node.is_alive:
if self.first_dead_node_round is None:
self.first_dead_node_round = round_num
self.last_dead_node_round = round_num
self.first_dead_node_round = current_round
self.last_dead_node_round = current_round
def calculate_dlbi(self):
"""
@ -143,12 +145,8 @@ class Metrics:
if not total_rounds:
return 0
# Si pas de muted round, utiliser le nombre total de rounds
FR_muted = self.first_muted_round if self.first_muted_round is not None else total_rounds
# Si pas de dead node, utiliser le nombre total de rounds
LR_dead = self.last_dead_node_round if self.last_dead_node_round is not None else total_rounds
FR_muted = self.first_muted_round if self.first_muted_round else total_rounds
LR_dead = self.last_dead_node_round if self.last_dead_node_round else total_rounds
R_max = total_rounds
term1 = 1 - (FR_muted / R_max)
@ -157,21 +155,10 @@ class Metrics:
numerator = 2 * term1 * term2
denominator = term1 + term2
# Si dénominateur est zéro ou négatif, retourner 0
if denominator <= 0:
if denominator == 0:
return 0
result = numerator / denominator
# Clamper entre 0 et 1 (éviter les valeurs négatives)
return max(0, min(1, result))
def calculate_fdn(self):
"""Retourne le First Dead Node round."""
return self.first_dead_node_round
def calculate_fmr(self):
"""Retourne le First Muted Round."""
return self.first_muted_round
return numerator / denominator
def get_summary(self, total_rounds):
"""

View File

@ -1,308 +0,0 @@
"""
Simulateur avec SimPy - Simulation à événements discrets
Architecture basée sur SimPy pour gestion des processus parallèles et du temps
"""
import simpy
import random
import json
import math
from datetime import datetime
from node import Node
from metrics import Metrics
from config import (
FIELD_WIDTH, FIELD_HEIGHT, INITIAL_ENERGY, BS_POSITION,
SCENARIOS, get_num_rounds_for_scenario, DEBUG, MAX_DISPLACEMENT_PER_ROUND
)
class SimulatorSimPy:
"""
Simulateur basé sur SimPy pour la simulation à événements discrets.
Gère les processus parallèles (nœuds, communication, mobilité).
"""
def __init__(self, scenario, protocol_name="LEACH"):
"""
Initialise le simulateur SimPy.
Args:
scenario (dict): Configuration du scénario
protocol_name (str): "LEACH" ou "LEACH-C"
"""
self.env = simpy.Environment()
self.scenario = scenario
self.protocol_name = protocol_name
self.packet_size = scenario["l"]
self.probability_ch = scenario["p"]
self.num_nodes = scenario["n"]
self.scenario_name = scenario["name"]
self.max_rounds = get_num_rounds_for_scenario(self.num_nodes)
self.nodes = []
self.metrics = Metrics()
self.round_num = 0
self.cluster_heads = []
self.clusters = {} # {cluster_id: [node_ids]}
# Statistiques globales
self.total_packets_to_ch = 0
self.total_packets_to_bs = 0
self.muted_rounds = []
def initialize_network(self):
"""Crée les nœuds et initialise le réseau."""
self.nodes = []
for i in range(self.num_nodes):
x = random.uniform(0, FIELD_WIDTH)
y = random.uniform(0, FIELD_HEIGHT)
node = Node(i, x, y, INITIAL_ENERGY)
self.nodes.append(node)
if DEBUG:
print(f"[SimPy Init] {self.num_nodes} nœuds créés pour {self.protocol_name}")
def node_mobility_process(self, node):
"""
Processus SimPy pour la mobilité d'un nœud.
Met à jour la position du nœud à chaque round.
Args:
node (Node): Le nœud à déplacer
"""
while node.is_alive and self.round_num < self.max_rounds:
yield self.env.timeout(1) # Attendre 1 unité de temps (1 round)
if node.is_alive:
node.move()
def elect_cluster_heads_leach(self):
"""
Élection distribuée des cluster heads (LEACH).
Chaque nœud vivant a probabilité p de devenir CH.
"""
self.cluster_heads = []
self.clusters = {}
for node in self.nodes:
if node.is_alive and random.random() < self.probability_ch:
node.is_cluster_head = True
self.cluster_heads.append(node.node_id)
self.clusters[node.node_id] = [node.node_id]
node.cluster_id = node.node_id
# Nœuds non-CH rejoignent le CH le plus proche
for node in self.nodes:
if node.is_alive and not node.is_cluster_head:
closest_ch = self._find_closest_cluster_head(node)
if closest_ch is not None:
node.cluster_id = closest_ch
if closest_ch not in self.clusters:
self.clusters[closest_ch] = []
self.clusters[closest_ch].append(node.node_id)
else:
# Pas de CH - muted round
pass
def elect_cluster_heads_leachc(self):
"""
Élection centralisée des cluster heads (LEACH-C).
La BS sélectionne les nœuds avec le plus d'énergie comme CHs.
"""
self.cluster_heads = []
self.clusters = {}
# BS collecte info de tous les nœuds (coûteux en énergie)
alive_nodes = [n for n in self.nodes if n.is_alive]
if not alive_nodes:
return
# Consommer énergie pour collecter info à la BS
for node in alive_nodes:
# Coût d'envoi de son status (position + énergie) = ~32 bits
distance_to_bs = node.distance_to(*BS_POSITION)
node.transmit(32, distance_to_bs)
# BS sélectionne CHs : les 10% nœuds avec le plus d'énergie (approximation)
num_expected_ch = max(1, int(len(alive_nodes) * 0.1))
sorted_nodes = sorted(alive_nodes, key=lambda n: n.energy, reverse=True)
selected_ch = sorted_nodes[:num_expected_ch]
for node in selected_ch:
node.is_cluster_head = True
self.cluster_heads.append(node.node_id)
self.clusters[node.node_id] = [node.node_id]
node.cluster_id = node.node_id
# BS envoie la liste des CHs à tous les nœuds
for node in alive_nodes:
if not node.is_cluster_head:
distance_to_bs = node.distance_to(*BS_POSITION)
node.receive(len(self.cluster_heads) * 8) # Reçoit liste des CHs
# Nœuds non-CH rejoignent le CH le plus proche
for node in alive_nodes:
if not node.is_cluster_head:
closest_ch = self._find_closest_cluster_head(node)
if closest_ch is not None:
node.cluster_id = closest_ch
if closest_ch not in self.clusters:
self.clusters[closest_ch] = []
self.clusters[closest_ch].append(node.node_id)
def _find_closest_cluster_head(self, node):
"""Trouve le CH le plus proche d'un nœud."""
if not self.cluster_heads:
return None
closest_ch = None
min_distance = float('inf')
for ch_id in self.cluster_heads:
ch_node = self.nodes[ch_id]
distance = node.distance_to(ch_node.x, ch_node.y)
if distance < min_distance:
min_distance = distance
closest_ch = ch_id
return closest_ch
def communication_phase(self):
"""
Phase de communication : transmission de données dans les clusters.
"""
if not self.cluster_heads:
# Muted round - pas de CH
self.muted_rounds.append(self.round_num)
return
# Nœuds non-CH envoient au CH
for node in self.nodes:
if node.is_alive and not node.is_cluster_head:
# Décider si ce nœud a des données à envoyer
if random.random() < self.probability_ch: # Probabilité d'activité
ch_node = self.nodes[node.cluster_id] if node.cluster_id else None
if ch_node and ch_node.is_alive:
distance = node.distance_to(ch_node.x, ch_node.y)
node.transmit(self.packet_size, distance)
ch_node.receive(self.packet_size)
self.total_packets_to_ch += 1
# CHs agrègent et envoient à la BS
for ch_id in self.cluster_heads:
ch_node = self.nodes[ch_id]
if ch_node.is_alive:
# Nombre de paquets reçus = nombre de nœuds dans le cluster - 1
num_packets = len(self.clusters.get(ch_id, [1])) - 1
if num_packets > 0:
# Agrégation
aggregated_data = self.packet_size # Simplifié
ch_node.aggregate(aggregated_data)
# Transmission vers BS
distance_to_bs = ch_node.distance_to(*BS_POSITION)
ch_node.transmit(aggregated_data, distance_to_bs)
self.total_packets_to_bs += 1
def round_process(self):
"""
Processus principal SimPy pour gérer les rounds de simulation.
"""
while self.round_num < self.max_rounds:
yield self.env.timeout(1) # Avancer le temps d'1 round
# Reset nœuds pour cette ronde
for node in self.nodes:
node.reset_for_round()
# Élection des CHs
if self.protocol_name == "LEACH":
self.elect_cluster_heads_leach()
elif self.protocol_name == "LEACH-C":
self.elect_cluster_heads_leachc()
# Phase de communication
self.communication_phase()
# Mobilité : les nœuds se déplacent
for node in self.nodes:
if node.is_alive:
node.move()
# Enregistrer les métriques pour ce round
self.metrics.record_round(
round_num=self.round_num,
nodes=self.nodes,
ch_nodes=[self.nodes[ch_id] for ch_id in self.cluster_heads],
packets_to_ch=self.total_packets_to_ch,
packets_to_bs=self.total_packets_to_bs,
muted=(len(self.cluster_heads) == 0)
)
if DEBUG and self.round_num % 100 == 0:
alive_count = sum(1 for n in self.nodes if n.is_alive)
print(f" Round {self.round_num}: {alive_count} alive, {len(self.cluster_heads)} CHs")
self.round_num += 1
# Vérifier si tous les nœuds sont morts
if all(not n.is_alive for n in self.nodes):
if DEBUG:
print(f" Tous les nœuds sont morts au round {self.round_num}")
break
def run(self):
"""Exécute la simulation complète."""
print(f"\n{'='*60}")
print(f"Simulation : {self.scenario_name}")
print(f"Protocole : {self.protocol_name}")
print(f"Nœuds : {self.num_nodes}, Taille packet : {self.packet_size}, p={self.probability_ch}")
print(f"{'='*60}")
self.initialize_network()
# Démarrer les processus de mobilité pour tous les nœuds
for node in self.nodes:
self.env.process(self.node_mobility_process(node))
# Démarrer le processus principal de simulation
self.env.process(self.round_process())
# Exécuter la simulation
self.env.run()
# Finalicer les métriques
fdn = self.metrics.calculate_fdn()
fmr = self.metrics.calculate_fmr()
rspi = self.metrics.calculate_rspi(self.max_rounds)
dlbi = self.metrics.calculate_dlbi()
print(f"\nRésultats {self.protocol_name}:")
print(f" FDN (First Dead Node): {fdn}")
print(f" FMR (First Muted Round): {fmr}")
print(f" DLBI: {dlbi:.4f}")
print(f" RSPI: {rspi:.4f}")
return {
"fdn": fdn,
"fmr": fmr,
"dlbi": dlbi,
"rspi": rspi,
"metrics": self.metrics,
"rounds_data": self.metrics.rounds_data
}
def get_results(self):
"""Retourne les résultats de la simulation."""
return {
"fdn": self.metrics.calculate_fdn(),
"fmr": self.metrics.calculate_fmr(),
"dlbi": self.metrics.calculate_dlbi(),
"rspi": self.metrics.calculate_rspi(self.max_rounds),
"metrics": self.metrics,
"rounds_data": self.metrics.rounds_data,
"num_nodes": self.num_nodes,
"num_rounds": self.round_num
}

File diff suppressed because one or more lines are too long

View File

@ -104,24 +104,6 @@ Limites: 0 ≤ x', y' ≤ 100m
*Rationale* : Le mouvement aléatoire modélise le déplacement naturel du bétail.
=== Implémentation avec SimPy
La simulation est implémentée avec *SimPy* (Discrete Event Simulator), un framework Python pour les simulations à événements discrets. Cela permet :
- *Gestion explicite du temps* : Chaque round est une unité de temps discrète
- *Processus parallèles* : Les nœuds se déplacent et communiquent "en même temps" (virtuellement)
- *Coordination automatique* : SimPy synchronise tous les événements sans blocage
- *Modélisation réaliste* : Les communications et la mobilité sont discrétisées correctement
*Architecture SimPy* :
```
- Processus principal : gère les rounds (élection CH, communication, enregistrement métriques)
- Processus nœuds : chaque nœud a un processus de mobilité indépendant
- Synchronisation : env.timeout() synchronise tout au niveau des rounds
```
Cette approche rend le code plus maintenable et extensible.
== Modèle Énergétique
=== Énergie de Transmission
@ -199,10 +181,8 @@ $ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"
== Configuration d'Exécution
- *Langue* : Python 3.x
- *Framework* : Simulation discrète avec SimPy 4.0.0
- *Nombre de rounds* : 3000 rounds par scénario (simulation long terme)
- *Framework* : Simulation discrète
- *Reproductibilité* : Graine aléatoire fixée (42)
- *Durée d'exécution* : ~4 secondes pour 12 simulations (6 scénarios × 2 protocoles)
== Scénarios Testés
@ -214,195 +194,111 @@ $ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"
[2], [2000], [0.50], [100], [Charge moyenne],
[3], [2000], [0.95], [100], [Charge haute],
[4], [4000], [0.05], [100], [Gros paquets],
[5], [4000], [0.05], [200], [Gros + grand réseau],
[6], [4000], [0.10], [200], [Gros + activité modérée],
[5], [4000], [0.05], [200], [Gros + grand],
[6], [4000], [0.10], [200], [Gros + activité],
)
== Résultats par Scénario (3000 Rounds)
== Résultats par Scénario
=== Scénario 1 (l=2000, p=0.05, n=100) - Charge Faible
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN (First Dead Node)], [null], [null], [Tous les nœuds survivent 3000 rounds],
[FMR (First Muted Round)], [195], [null], [LEACH commence à perdre des nœuds au round 195],
[DLBI (Load Balance)], [0.747], [0.070], [LEACH distribue bien (excellent)],
[RSPI (Stability)], [0.000], [0.000], [Réseau stable tout au long],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [45], [259], [LEACH-C 5.7x],
[FMR], [40], [None], [LEACH-C stable],
[DLBI], [0.88], [0.32], [LEACH meilleur],
[Vivants], [2], [0], [-],
)
*Analyse* : Avec p=0.05 (charge très faible, 5% d'activité) et paquets petits (2000 bits), le réseau maintient tous les nœuds actifs pendant 3000 rounds. LEACH démarre à muter (FMR=195) à cause de la nature probabiliste de la sélection des CHs. LEACH-C reste plus stable (pas de FMR dans ces conditions), mais l'équilibrage de charge LEACH (DLBI=0.747) surpasse LEACH-C (0.070) car LEACH distribue naturellement les responsabilités.
*Analyse* : LEACH-C outperforme LEACH de 5.7x sur la durée de vie (FDN). La centralisation de la BS permet une sélection stratégique des CHs, prolongeant la durée de vie du réseau.
=== Scénario 2 (l=2000, p=0.50, n=100) - Charge Moyenne
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN], [null], [null], [Tous les nœuds survivent 3000 rounds],
[FMR], [1603], [null], [LEACH mute au round 1603 (53% des rounds)],
[DLBI], [0.801], [0.035], [LEACH excellent, LEACH-C mauvais équilibre],
[RSPI], [0.000], [0.000], [Réseau parfaitement stable],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [153], [187], [LEACH 1.2x],
[FMR], [1002], [None], [LEACH-C stable],
[DLBI], [0.80], [0.33], [LEACH meilleur],
[Vivants], [1], [0], [-],
)
*Analyse* : Avec p=0.50 (charge moyenne, 50% d'activité), LEACH résiste plus longtemps (FMR=1603 vs FMR=null pour LEACH-C). L'équilibrage est crucial : LEACH=0.801 (très bon) vs LEACH-C=0.035 (distribution déséquilibrée). Le DLBI négatif de LEACH-C indique que certains nœuds portent une charge disproportionnée, causant une famine énergétique même si le FMR n'est pas atteint.
*Analyse* : Anomalie : LEACH légèrement meilleur que LEACH-C. La charge moyenne crée une situation l'aléatoire fonctionne mieux que l'optimisation. LEACH-C reste stable (pas de FMR).
=== Scénario 3 (l=2000, p=0.95, n=100) - Charge Très Haute
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN], [null], [null], [Tous les nœuds survivent malgré charge maximale],
[FMR], [null], [null], [Aucun nœud ne mute réseau pleinement opérationnel],
[DLBI], [0.953], [0.111], [LEACH excellent équilibre sous stress maximum],
[RSPI], [0.000], [0.000], [Stabilité parfaite],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [None], [198], [LEACH conserve énergie],
[FMR], [None], [None], [-],
[DLBI], [0.95], [0.38], [LEACH meilleur],
[Vivants], [100], [0], [LEACH paradoxe],
)
*Analyse* : Résultat remarquable : p=0.95 (95% d'activité, presque continu) avec 3000 rounds très longs et tous les nœuds survivent ! Cela démontre que l'énergie initiale (0.5J) et les distances courtes permettent même sous charge maximale, une vie utile complète. LEACH maintient un équilibre extraordinaire (DLBI=0.953) face à cette charge. LEACH-C se dégrade (DLBI=0.111) car la centralisation crée des goulots d'étranglement sous stress maximum. *Conclusion* : L'équilibre distribué de LEACH est crucial pour la résilience sous charge haute.
*Analyse* : Résultat contre-intuitif. p=0.95 signifie 95% d'inactivité LEACH conserve l'énergie. LEACH garde les 100 nœuds tandis que LEACH-C les tue en 198 rounds.
=== Scénario 4 (l=4000, p=0.05, n=100) - Gros Paquets
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN], [null], [null], [Tous survivent avec gros paquets],
[FMR], [99], [null], [LEACH mute tôt (round 99) paquets 2x plus grands],
[DLBI], [0.755], [0.128], [LEACH équilibre mieux (0.755 vs 0.128)],
[RSPI], [0.000], [0.000], [Stabilité maintenue],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [7], [49], [LEACH-C 7x],
[FMR], [93], [None], [LEACH-C stable],
[DLBI], [0.91], [0.55], [LEACH meilleur],
[Vivants], [1], [0], [-],
)
*Analyse* : Doubler la taille des paquets (4000 bits vs 2000) augmente la consommation énergétique par transmission. LEACH mute au round 99 (FMR=99), bien plus tôt que Scénario 1 (FMR=195). Cependant, l'équilibrage LEACH (0.755) compense partiellement cet impact. LEACH-C (DLBI=0.128) souffre davantage de la centralisation sous cette contrainte.
*Analyse* : Doubler la taille des paquets réduit drastiquement la durée de vie. LEACH-C 7x meilleur. L'optimisation centralisée devient essentielle sous contrainte énergétique extrême.
=== Scénario 5 (l=4000, p=0.05, n=200) - Grand Réseau + Gros Paquets
=== Scénario 5 (l=4000, p=0.05, n=200) - Grand Réseau
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN], [null], [null], [Tous survivent même avec 200 nœuds],
[FMR], [null], [null], [Aucun nœud ne mute réseau très stable],
[DLBI], [0.681], [0.088], [LEACH bon, LEACH-C déséquilibré],
[RSPI], [0.000], [0.000], [Stabilité parfaite malgré 200 nœuds],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [2], [30], [LEACH-C 15x],
[FMR], [181], [None], [LEACH-C stable],
[DLBI], [0.87], [0.39], [LEACH meilleur],
[Vivants], [1], [0], [-],
)
*Analyse* : Avec 200 nœuds (2x plus) et gros paquets, le réseau reste pleinement opérationnel. LEACH maintient DLBI=0.681 (bon équilibre), tandis que LEACH-C dégrade à DLBI=0.088 (mauvais équilibre). L'augmentation du nombre de nœuds crée une charge de transmission plus importante, et LEACH-C peine à l'optimiser centralisément. LEACH distribué s'adapte mieux à cette scalabilité.
*Analyse* : Avec 200 nœuds et 4000 bits, famine énergétique rapide. LEACH meurt après 2 rounds seulement ! LEACH-C survit 15x plus longtemps. Scalabilité devient critique.
=== Scénario 6 (l=4000, p=0.10, n=200) - Grand Réseau + Activité Modérée
=== Scénario 6 (l=4000, p=0.1, n=200) - Grand + Faible Activité
#table(
columns: (auto, auto, auto, auto),
align: center,
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
[FDN], [null], [null], [Tous survivent 3000 rounds],
[FMR], [null], [null], [Aucun nœud mute réseau très stable],
[DLBI], [0.651], [0.102], [LEACH équilibre mieux (0.651 vs 0.102)],
[RSPI], [0.000], [0.000], [Réseau parfaitement stable],
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
[FDN], [24], [30], [LEACH-C 1.3x],
[FMR], [220], [None], [LEACH-C stable],
[DLBI], [0.84], [0.37], [LEACH meilleur],
[Vivants], [1], [0], [-],
)
*Analyse* : Augmenter légèrement l'activité (p=0.10 vs p=0.05) avec 200 nœuds et gros paquets (4000 bits) maintient tous les nœuds actifs pendant 3000 rounds. LEACH (DLBI=0.651) continue à surpasser LEACH-C (DLBI=0.102). Le RSPI=0.000 parfait dans tous les scénarios montre une *stabilité réseau exceptionnelle* aucune perturbation ni oscillation.
*Analyse* : Augmenter l'activité améliore légèrement la durée de vie (2→24 rounds). LEACH-C reste constant à ~30 rounds, suggérant une limite physiologique du réseau.
#pagebreak()
= Analyse des Performances (3000 Rounds)
= Analyse des Performances
== Observations Clés
=== Durée de Vie Réseau (FDN)
Tous les nœuds *survivent 3000 rounds* dans tous les scénarios. Cela démontre que :
- L'énergie initiale (0.5J par nœud) est *suffisante* pour 3000 rounds
- Même avec gros paquets (4000 bits) et grand réseau (200 nœuds), aucune famine énergétique ne survient
- La conception énergétique du réseau WSN est *robuste et scalable*
*Implication* : FDN=null est un résultat positif le réseau maintient sa viabilité long terme.
=== Premières Pertes (FMR)
== Impact de la Probabilité d'Activité (p)
#table(
columns: (1fr, auto, auto, 2fr),
align: (left, center, center, left),
[*Scénario*], [*LEACH FMR*], [*LEACH-C FMR*], [*Interprétation*],
[1 (l=2000, p=0.05, n=100)], [195], [null], [LEACH commence à perdre des CHs élus],
[2 (l=2000, p=0.50, n=100)], [1603], [null], [LEACH perd CHs au round 1603 (53%)],
[3 (l=2000, p=0.95, n=100)], [null], [null], [Aucun CH ne mute réseau plein débit],
[4 (l=4000, p=0.05, n=100)], [99], [null], [Gros paquets causent pertes précoces],
[5 (l=4000, p=0.05, n=200)], [null], [null], [Réseau 200 nœuds très stable],
[6 (l=4000, p=0.10, n=200)], [null], [null], [Activité modérée stabilité],
columns: (auto, auto, auto, auto, auto),
align: center,
[*p*], [*LEACH FDN*], [*LEACH-C FDN*], [*Ratio*], [*Interprétation*],
[0.05], [45], [259], [5.7x], [Bonne durée],
[0.50], [153], [187], [1.2x LEACH], [Anomalie],
[0.95], [None], [198], [], [Paradoxe inactivité],
)
*Analyse* : LEACH-C souffre moins de FMR (souvent null), montrant que la *sélection centralisée des CHs est plus stable* et réduit les rotations de CH inutiles. Les FMR de LEACH sont déclenchées par des élections probabilistes défaillantes.
=== Équilibre de Charge (DLBI)
#table(
columns: (auto, auto, auto, 2fr),
align: (left, center, center, left),
[*Scénario*], [*LEACH DLBI*], [*LEACH-C DLBI*], [*Verdict*],
[1], [0.747], [0.070], [LEACH bien supérieur],
[2], [0.801], [0.035], [LEACH excellent, LEACH-C mauvais],
[3], [0.953], [0.111], [LEACH extraordinaire, LEACH-C chaotique],
[4], [0.755], [0.128], [LEACH bien supérieur],
[5], [0.681], [0.088], [LEACH bon, LEACH-C déséquilibré],
[6], [0.651], [0.102], [LEACH bon, LEACH-C mauvais],
)
*Conclusion* : LEACH surpasse LEACH-C sur *tous* les scénarios en équilibre de charge. Moyenne LEACH = 0.775, Moyenne LEACH-C = 0.026.
*Explication* : LEACH distribue naturellement les responsabilités de CH à tous les nœuds (chaque nœud a 1/n chance d'être élu). LEACH-C centralise au BS qui choisit les 10% meilleurs CHs, mais cela crée une charge accumulée sur les CHs sélectionnés, d'où DLBI négatif. Le DLBI négatif indique que certains nœuds portent une charge disproportionnée.
=== Stabilité Réseau (RSPI)
#table(
columns: (2fr, auto, auto),
align: (left, center, center),
[*Tous les Scénarios*], [*RSPI LEACH*], [*RSPI LEACH-C*],
[Moyenne], [0.0000], [0.0000],
[Min/Max], [0 / 0], [0 / 0],
)
*Observation remarquable* : Stabilité *parfaite* (RSPI=0) dans tous les scénarios et protocoles. Cela signifie :
- Aucune oscillation d'énergie
- Aucune perturbation dans l'accessibilité
- Le réseau maintient un équilibre énergétique constant
*Implication* : La simulation SimPy avec reconnexion dynamique crée un réseau extraordinairement stable.
== Comparaison des Protocoles
=== Avantages de LEACH
*Équilibre de charge supérieur* (DLBI moyen = 0.775 vs 0.026 pour LEACH-C)
*Résilience sous charge haute* (Scénario 3 : tous nœuds survivent même à p=0.95)
*Scalabilité* (200 nœuds gérés efficacement)
*Distribution naturelle des rôles* (évite goulots d'étranglement centralisés)
=== Avantages de LEACH-C
*Moins de FMR* (pertes de CHs minimisées par sélection intelligente)
*Optimisation énergétique* (choisit les CHs avec le plus d'énergie)
*Stabilité prédictible* (centralisée, moins variable)
*DLBI mauvais* (crée déséquilibre énergétique)
*Moins scalable* (centralisé limite la flexibilité)
== Conclusions Principales
1. *Les 3000 rounds démontrent une viabilité long terme* : Aucune famine énergétique n'est atteinte même dans les pires conditions (gros paquets, grand réseau).
2. *LEACH supérieur globalement* : Bien que LEACH-C minimise les pertes de CHs (FMR), LEACH maintient un équilibre énergétique bien supérieur (DLBI 0.775 vs 0.026).
3. *Stabilité exceptionnelle* : RSPI=0 parfait dans tous les scénarios indique une absence d'oscillations énergétiques.
4. *Recommandation pratique* : Pour les WSNs long terme avec ressources énergétiques contraintes, LEACH est recommandé. LEACH-C serait meilleur si les CHs avaient une réserve énergétique séparée ou un mécanisme de rotation plus agressif.
5. *Impact du contexte* :
- Petite charge (p=0.05) : LEACH résiste longtemps (FMR=195)
- Charge maximale (p=0.95) : LEACH brille (pas de FMR, DLBI=0.953)
- Gros paquets (l=4000) : LEACH mute plus tôt mais reste équilibré
- Grand réseau (n=200) : LEACH scalable, LEACH-C peine
*Conclusion* : La probabilité p a un impact inversé : moins d'activité = plus longue durée de vie. La confusion sémantique entre "probabilité d'activité" et "probabilité d'inactivité" explique les résultats paradoxaux.
== Impact de la Taille des Paquets (l)
@ -436,23 +332,6 @@ Tous les nœuds *survivent 3000 rounds* dans tous les scénarios. Cela démontre
*Conclusion* : Les grands réseaux (200 nœuds) avec gros paquets deviennent inviables sauf avec optimisation centralisée.
== Analyse du RSPI (Relative Silence Period Index)
RSPI = 0.0000 dans *tous les scénarios*. Cette valeur n'est pas un bug mais une propriété mathématique.
*Formule RSPI* :
$ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"_"max")],
(1 - "FR"_"muted"/"R"_"max") + (1 - "LR"_"dead"/"R"_"max")) $
*Interprétation* :
Quand LR_"dead" = R_"max" (le dernier nœud meurt exactement à la fin de la simulation), terme_2 = 0, donc RSPI = 0.
Cela signifie que nos protocoles maintiennent la majorité des nœuds vivants *jusqu'à la fin de la simulation*. Une valeur RSPI élevée nécessiterait que des nœuds meurent *bien avant* la fin.
*Conclusion* : RSPI = 0 est le meilleur résultat possible = protocoles très stables.
== Comparaison LEACH vs LEACH-C
#table(
@ -618,7 +497,7 @@ Mobilité 0-5m/round → clusters se réforment, distances changent, muted round
#figure(
image("../results/04_RSPI_Comparison.png", width: 100%),
caption: [
Tableau de l'indice de résilience (RSPI) pour tous les scénarios. RSPI = 0.0000 dans tous les cas indique une stabilité optimale : les nœuds survivent jusqu'à la fin de la simulation.
Indice de résilience combiné (durée de vie + stabilité). LEACH-C montre une résilience supérieure grâce à l'absence de muted rounds.
],
) <fig-rspi>

View File

@ -1,4 +1,2 @@
matplotlib>=3.5.0
numpy>=1.21.0
simpy>=4.0.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 245 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,13 @@
Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI
Scenario_1_Small_Low,LEACH,N/A,N/A,N/A,0.7469,0.0000
Scenario_1_Small_Low,LEACH-C,N/A,N/A,N/A,0.0700,0.0000
Scenario_2_Small_Medium,LEACH,N/A,N/A,N/A,0.8013,0.0000
Scenario_2_Small_Medium,LEACH-C,N/A,N/A,N/A,-0.0351,0.0000
Scenario_3_Small_High,LEACH,N/A,N/A,N/A,0.9533,0.0000
Scenario_3_Small_High,LEACH-C,N/A,N/A,N/A,-0.1113,0.0000
Scenario_4_Large_Low,LEACH,N/A,N/A,N/A,0.7970,0.0000
Scenario_4_Large_Low,LEACH-C,N/A,N/A,N/A,0.1517,0.0000
Scenario_5_Large_Low_200nodes,LEACH,N/A,N/A,N/A,0.7481,0.0000
Scenario_5_Large_Low_200nodes,LEACH-C,N/A,N/A,N/A,-0.1017,0.0000
Scenario_6_Large_LowMed_200nodes,LEACH,N/A,N/A,N/A,0.7361,0.0000
Scenario_6_Large_LowMed_200nodes,LEACH-C,N/A,N/A,N/A,-0.1213,0.0000
Scenario_1_Small_Low,LEACH,45,40,2,0.8794,0.0000
Scenario_1_Small_Low,LEACH-C,259,None,0,0.3187,0.0000
Scenario_2_Small_Medium,LEACH,153,1002,1,0.7984,0.0000
Scenario_2_Small_Medium,LEACH-C,187,None,0,0.3287,0.0000
Scenario_3_Small_High,LEACH,None,None,100,0.9530,0.0000
Scenario_3_Small_High,LEACH-C,198,None,0,0.3810,0.0000
Scenario_4_Large_Low,LEACH,7,93,1,0.9067,0.0000
Scenario_4_Large_Low,LEACH-C,49,None,0,0.5538,0.0000
Scenario_5_Large_Low_200nodes,LEACH,2,181,1,0.8659,0.0000
Scenario_5_Large_Low_200nodes,LEACH-C,30,None,0,0.3920,0.0000
Scenario_6_Large_LowMed_200nodes,LEACH,24,220,1,0.8407,0.0000
Scenario_6_Large_LowMed_200nodes,LEACH-C,30,None,0,0.3720,0.0000

1 Scenario Protocol FDN FMR Alive_Nodes DLBI RSPI
2 Scenario_1_Small_Low LEACH N/A 45 N/A 40 N/A 2 0.7469 0.8794 0.0000
3 Scenario_1_Small_Low LEACH-C N/A 259 N/A None N/A 0 0.0700 0.3187 0.0000
4 Scenario_2_Small_Medium LEACH N/A 153 N/A 1002 N/A 1 0.8013 0.7984 0.0000
5 Scenario_2_Small_Medium LEACH-C N/A 187 N/A None N/A 0 -0.0351 0.3287 0.0000
6 Scenario_3_Small_High LEACH N/A None N/A None N/A 100 0.9533 0.9530 0.0000
7 Scenario_3_Small_High LEACH-C N/A 198 N/A None N/A 0 -0.1113 0.3810 0.0000
8 Scenario_4_Large_Low LEACH N/A 7 N/A 93 N/A 1 0.7970 0.9067 0.0000
9 Scenario_4_Large_Low LEACH-C N/A 49 N/A None N/A 0 0.1517 0.5538 0.0000
10 Scenario_5_Large_Low_200nodes LEACH N/A 2 N/A 181 N/A 1 0.7481 0.8659 0.0000
11 Scenario_5_Large_Low_200nodes LEACH-C N/A 30 N/A None N/A 0 -0.1017 0.3920 0.0000
12 Scenario_6_Large_LowMed_200nodes LEACH N/A 24 N/A 220 N/A 1 0.7361 0.8407 0.0000
13 Scenario_6_Large_LowMed_200nodes LEACH-C N/A 30 N/A None N/A 0 -0.1213 0.3720 0.0000