SimPy implementation with 3000 rounds - complete refactor
- Migrate from simple loop to SimPy discrete event simulator - Implement node_mobility_process() as parallel SimPy processes - LEACH: distributed CH election with probability p - LEACH-C: centralized BS-optimized CH selection - 6 test scenarios with comprehensive results - 3000 rounds per scenario for long-term viability testing - All metrics calculated: FDN, FMR, DLBI, RSPI - 5 PNG graphs generated with analysis - Full rapport updated with 3000-round results - Code cleaned: no .log or .md files, no __pycache__
193
code/analysis.py
@ -53,8 +53,9 @@ class ResultsAnalyzer:
|
|||||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||||
|
|
||||||
leach_fdn.append(leach_metrics.get("first_dead_node_round") or 0)
|
# Utiliser fdn du JSON (peut être null)
|
||||||
leachc_fdn.append(leachc_metrics.get("first_dead_node_round") or 0)
|
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))
|
fig, ax = plt.subplots(figsize=(12, 6))
|
||||||
x_pos = range(len(scenarios))
|
x_pos = range(len(scenarios))
|
||||||
@ -71,6 +72,12 @@ class ResultsAnalyzer:
|
|||||||
ax.legend()
|
ax.legend()
|
||||||
ax.grid(axis='y', alpha=0.3)
|
ax.grid(axis='y', alpha=0.3)
|
||||||
|
|
||||||
|
# Ajouter note si pas de FDN (nœuds toujours vivants)
|
||||||
|
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.tight_layout()
|
||||||
plt.savefig(f"{output_dir}/01_FDN_Comparison.png", dpi=300)
|
plt.savefig(f"{output_dir}/01_FDN_Comparison.png", dpi=300)
|
||||||
plt.close()
|
plt.close()
|
||||||
@ -85,8 +92,9 @@ class ResultsAnalyzer:
|
|||||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||||
|
|
||||||
leach_fmr.append(leach_metrics.get("first_muted_round") or 9999)
|
# Utiliser fmr du JSON (peut être null)
|
||||||
leachc_fmr.append(leachc_metrics.get("first_muted_round") or 9999)
|
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))
|
fig, ax = plt.subplots(figsize=(12, 6))
|
||||||
x_pos = range(len(scenarios))
|
x_pos = range(len(scenarios))
|
||||||
@ -153,57 +161,140 @@ class ResultsAnalyzer:
|
|||||||
leach_rspi.append(leach_metrics.get("rspi", 0))
|
leach_rspi.append(leach_metrics.get("rspi", 0))
|
||||||
leachc_rspi.append(leachc_metrics.get("rspi", 0))
|
leachc_rspi.append(leachc_metrics.get("rspi", 0))
|
||||||
|
|
||||||
fig, ax = plt.subplots(figsize=(12, 6))
|
# Graphique : Tableau de texte uniquement (plus lisible)
|
||||||
x_pos = range(len(scenarios))
|
fig, ax = plt.subplots(figsize=(12, 8))
|
||||||
width = 0.35
|
ax.axis('off')
|
||||||
|
|
||||||
ax.bar([i - width/2 for i in x_pos], leach_rspi, width, label='LEACH', color='#FF6B6B')
|
# Créer un tableau affichant les valeurs
|
||||||
ax.bar([i + width/2 for i in x_pos], leachc_rspi, width, label='LEACH-C', color='#4ECDC4')
|
data = []
|
||||||
|
for scenario, lrspi, crspi in zip(scenarios, leach_rspi, leachc_rspi):
|
||||||
|
data.append([scenario, f"{lrspi:.6f}", f"{crspi:.6f}"])
|
||||||
|
|
||||||
ax.set_xlabel('Scénario', fontsize=12)
|
# Ajouter en-tête
|
||||||
ax.set_ylabel('RSPI (0 à 1)', fontsize=12)
|
data.insert(0, ['Scénario', 'LEACH RSPI', 'LEACH-C RSPI'])
|
||||||
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)
|
|
||||||
|
|
||||||
plt.tight_layout()
|
table = ax.table(cellText=data, cellLoc='center', loc='center',
|
||||||
plt.savefig(f"{output_dir}/04_RSPI_Comparison.png", dpi=300)
|
colWidths=[0.40, 0.30, 0.30])
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(11)
|
||||||
|
table.scale(1, 3)
|
||||||
|
|
||||||
|
# Colorer l'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)
|
||||||
|
|
||||||
|
# Colorer les lignes alternées et ajouter bordures
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Titre
|
||||||
|
fig.suptitle('Comparaison RSPI (Relative Silence Period Index)',
|
||||||
|
fontsize=16, fontweight='bold', y=0.98)
|
||||||
|
|
||||||
|
# Ajouter explications
|
||||||
|
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()
|
plt.close()
|
||||||
|
|
||||||
def _plot_alive_nodes(self, output_dir):
|
def _plot_alive_nodes(self, output_dir):
|
||||||
"""Graphique : Nombre de nœuds vivants au fil du temps."""
|
"""
|
||||||
scenarios = list(self.results.keys())[:3] # Premiers 3 scénarios
|
Graphique : Résumé complet des résultats en format très lisible.
|
||||||
|
"""
|
||||||
|
scenarios = list(self.results.keys())
|
||||||
|
|
||||||
fig, axes = plt.subplots(len(scenarios), 2, figsize=(14, 4*len(scenarios)))
|
fig = plt.figure(figsize=(16, 12))
|
||||||
|
ax = fig.add_subplot(111)
|
||||||
|
ax.axis('off')
|
||||||
|
|
||||||
for idx, scenario in enumerate(scenarios):
|
# Préparer les données
|
||||||
# LEACH
|
data = []
|
||||||
leach_detailed = self.results[scenario]["LEACH"]["detailed_rounds"]
|
for scenario in scenarios:
|
||||||
rounds = [r["round"] for r in leach_detailed]
|
scenario_short = scenario.replace('Scenario_', '').replace('_', ' ')
|
||||||
alive = [r["alive_nodes"] for r in leach_detailed]
|
row = [scenario_short]
|
||||||
|
|
||||||
axes[idx, 0].plot(rounds, alive, marker='o', label='LEACH', color='#FF6B6B')
|
for protocol in ["LEACH", "LEACH-C"]:
|
||||||
axes[idx, 0].set_xlabel('Round')
|
if protocol in self.results[scenario]:
|
||||||
axes[idx, 0].set_ylabel('Nœuds Vivants')
|
metrics = self.results[scenario][protocol]["metrics"]
|
||||||
axes[idx, 0].set_title(f'LEACH - {scenario}')
|
fdn = metrics['fdn'] if metrics['fdn'] is not None else "—"
|
||||||
axes[idx, 0].grid(alpha=0.3)
|
fmr = metrics['fmr'] if metrics['fmr'] is not None else "—"
|
||||||
|
dlbi = f"{metrics['dlbi']:.2f}"
|
||||||
|
rspi = f"{metrics['rspi']:.4f}"
|
||||||
|
|
||||||
# LEACH-C
|
cell_text = f"FDN: {fdn}\nFMR: {fmr}\nDLBI: {dlbi}\nRSPI: {rspi}"
|
||||||
leachc_detailed = self.results[scenario]["LEACH-C"]["detailed_rounds"]
|
row.append(cell_text)
|
||||||
rounds = [r["round"] for r in leachc_detailed]
|
else:
|
||||||
alive = [r["alive_nodes"] for r in leachc_detailed]
|
row.append("N/A")
|
||||||
|
|
||||||
axes[idx, 1].plot(rounds, alive, marker='s', label='LEACH-C', color='#4ECDC4')
|
data.append(row)
|
||||||
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)
|
|
||||||
|
|
||||||
plt.tight_layout()
|
# Ajouter en-tête
|
||||||
plt.savefig(f"{output_dir}/05_Alive_Nodes_Over_Time.png", dpi=300)
|
header = ["Scénario", "LEACH", "LEACH-C"]
|
||||||
|
data.insert(0, header)
|
||||||
|
|
||||||
|
# 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 l'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 les lignes alternées
|
||||||
|
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')
|
||||||
|
|
||||||
|
# Ajouter bordures plus visibles
|
||||||
|
for key, cell in table.get_celld().items():
|
||||||
|
cell.set_linewidth(2)
|
||||||
|
cell.set_edgecolor('#333333')
|
||||||
|
|
||||||
|
# Titre
|
||||||
|
fig.suptitle('Résumé Complet des Résultats - Simulation avec SimPy',
|
||||||
|
fontsize=16, fontweight='bold', y=0.98)
|
||||||
|
|
||||||
|
# Légende
|
||||||
|
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()
|
plt.close()
|
||||||
|
|
||||||
def generate_summary_table(self, output_file):
|
def generate_summary_table(self, output_file):
|
||||||
@ -233,6 +324,16 @@ class ResultsAnalyzer:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
analyzer = ResultsAnalyzer("/home/paul/algo/results/simulation_results.json")
|
import os
|
||||||
analyzer.generate_comparison_graphs("/home/paul/algo/results")
|
|
||||||
analyzer.generate_summary_table("/home/paul/algo/results/summary.csv")
|
# Déterminer les chemins dynamiquement
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__)) # /home/paul/AlgoRep/code
|
||||||
|
project_dir = os.path.dirname(script_dir) # /home/paul/AlgoRep
|
||||||
|
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)
|
||||||
|
|||||||
@ -63,8 +63,10 @@ def get_num_rounds_for_scenario(num_nodes):
|
|||||||
"""
|
"""
|
||||||
Estime le nombre de rounds pour une simulation complète.
|
Estime le nombre de rounds pour une simulation complète.
|
||||||
Plus de nœuds = plus d'énergie disponible = plus de rounds.
|
Plus de nœuds = plus d'énergie disponible = plus de rounds.
|
||||||
|
|
||||||
|
Avec SimPy, visant ~3000 rounds par scénario.
|
||||||
"""
|
"""
|
||||||
return 2000 + (num_nodes - 100) * 5 # Ajustement heuristique
|
return 3000 + (num_nodes - 100) * 10 # Augmenté pour plus de données
|
||||||
|
|
||||||
# ============= FLAGS DE DEBUG/LOGGING =============
|
# ============= FLAGS DE DEBUG/LOGGING =============
|
||||||
|
|
||||||
|
|||||||
@ -154,7 +154,7 @@ class LEACH:
|
|||||||
if not ch_elected:
|
if not ch_elected:
|
||||||
# Muted round - pas de CH
|
# Muted round - pas de CH
|
||||||
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
|
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
|
||||||
self.metrics.update_dead_nodes(self.nodes)
|
self.metrics.update_dead_nodes(self.nodes, self.round_num)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Phase 2 : Formation des clusters
|
# Phase 2 : Formation des clusters
|
||||||
@ -171,7 +171,7 @@ class LEACH:
|
|||||||
# Enregistrement des métriques
|
# Enregistrement des métriques
|
||||||
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
|
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
|
||||||
packets_to_ch, packets_to_bs, muted=False)
|
packets_to_ch, packets_to_bs, muted=False)
|
||||||
self.metrics.update_dead_nodes(self.nodes)
|
self.metrics.update_dead_nodes(self.nodes, self.round_num)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@ -185,7 +185,7 @@ class LEACHC:
|
|||||||
if not ch_candidates:
|
if not ch_candidates:
|
||||||
# Aucun nœud vivant
|
# Aucun nœud vivant
|
||||||
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
|
self.metrics.record_round(self.round_num, self.nodes, [], 0, 0, muted=True)
|
||||||
self.metrics.update_dead_nodes(self.nodes)
|
self.metrics.update_dead_nodes(self.nodes, self.round_num)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Marquer les CHs
|
# Marquer les CHs
|
||||||
@ -211,7 +211,7 @@ class LEACHC:
|
|||||||
# Enregistrement des métriques
|
# Enregistrement des métriques
|
||||||
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
|
self.metrics.record_round(self.round_num, self.nodes, self.ch_nodes,
|
||||||
packets_to_ch, packets_to_bs, muted=False)
|
packets_to_ch, packets_to_bs, muted=False)
|
||||||
self.metrics.update_dead_nodes(self.nodes)
|
self.metrics.update_dead_nodes(self.nodes, self.round_num)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
231
code/main.py
@ -1,150 +1,71 @@
|
|||||||
"""
|
"""
|
||||||
Module principal : Simulation complète des protocoles LEACH et LEACH-C
|
Module principal : Simulation complète des protocoles LEACH et LEACH-C avec SimPy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from node import Node
|
from simulator_simpy import SimulatorSimPy
|
||||||
from leach import LEACH
|
from config import SCENARIOS, DEBUG
|
||||||
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():
|
def run_all_scenarios():
|
||||||
"""
|
"""
|
||||||
Lance les simulations pour tous les scénarios.
|
Lance les simulations pour tous les scénarios.
|
||||||
|
Utilise SimPy pour simulation à événements discrets.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Résultats pour tous les scénarios
|
dict: Résultats pour tous les scénarios
|
||||||
"""
|
"""
|
||||||
all_results = {}
|
all_results = {}
|
||||||
|
|
||||||
print(f"\n{'#'*60}")
|
print(f"\n{'#'*70}")
|
||||||
print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES")
|
print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES (SimPy)")
|
||||||
print(f"# Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
print(f"# Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
print(f"{'#'*60}\n")
|
print(f"{'#'*70}\n")
|
||||||
|
|
||||||
for scenario in SCENARIOS:
|
for scenario in SCENARIOS:
|
||||||
print(f"Scénario: {scenario['name']}")
|
scenario_name = scenario["name"]
|
||||||
|
print(f"\n{'='*70}")
|
||||||
|
print(f"SCÉNARIO: {scenario_name}")
|
||||||
|
print(f"{'='*70}")
|
||||||
|
|
||||||
simulator = Simulator(scenario)
|
scenario_results = {}
|
||||||
results = simulator.run_simulation()
|
|
||||||
|
|
||||||
all_results[scenario["name"]] = results
|
# 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"]
|
||||||
|
}
|
||||||
|
|
||||||
print(f"\n{'#'*60}")
|
# 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"# SIMULATIONS TERMINÉES - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
print(f"# SIMULATIONS TERMINÉES - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||||
print(f"{'#'*60}\n")
|
print(f"{'#'*70}\n")
|
||||||
|
|
||||||
return all_results
|
return all_results
|
||||||
|
|
||||||
@ -161,16 +82,13 @@ def save_results(results, output_file):
|
|||||||
json_results = {}
|
json_results = {}
|
||||||
|
|
||||||
for scenario_name, scenario_data in results.items():
|
for scenario_name, scenario_data in results.items():
|
||||||
json_results[scenario_name] = {
|
json_results[scenario_name] = {}
|
||||||
"LEACH": {
|
|
||||||
"metrics": scenario_data["LEACH"]["metrics"],
|
for protocol in ["LEACH", "LEACH-C"]:
|
||||||
"detailed_rounds": scenario_data["LEACH"]["detailed"][:20] # Premiers 20 rounds
|
if protocol in scenario_data:
|
||||||
},
|
json_results[scenario_name][protocol] = {
|
||||||
"LEACH-C": {
|
"metrics": scenario_data[protocol]["metrics"]
|
||||||
"metrics": scenario_data["LEACH-C"]["metrics"],
|
}
|
||||||
"detailed_rounds": scenario_data["LEACH-C"]["detailed"][:20]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(output_file, 'w') as f:
|
with open(output_file, 'w') as f:
|
||||||
json.dump(json_results, f, indent=2)
|
json.dump(json_results, f, indent=2)
|
||||||
@ -178,28 +96,45 @@ def save_results(results, output_file):
|
|||||||
print(f"OK - Résultats sauvegardés: {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__":
|
if __name__ == "__main__":
|
||||||
# Graine de randomisation pour reproductibilité
|
# Graine de randomisation pour reproductibilité
|
||||||
random.seed(42)
|
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
|
# Lancer toutes les simulations
|
||||||
all_results = run_all_scenarios()
|
all_results = run_all_scenarios()
|
||||||
|
|
||||||
# Sauvegarder les résultats
|
|
||||||
save_results(all_results, "/home/paul/algo/results/simulation_results.json")
|
|
||||||
|
|
||||||
# Afficher un résumé
|
# Afficher un résumé
|
||||||
print("\n" + "="*60)
|
print_summary(all_results)
|
||||||
print("RÉSUMÉ DES RÉSULTATS")
|
|
||||||
print("="*60)
|
|
||||||
|
|
||||||
for scenario_name, scenario_data in all_results.items():
|
# Sauvegarder les résultats
|
||||||
print(f"\n{scenario_name}:")
|
output_file = os.path.join(results_dir, "simulation_results.json")
|
||||||
|
save_results(all_results, output_file)
|
||||||
|
|
||||||
for protocol in ["LEACH", "LEACH-C"]:
|
print(f"\nRésultats disponibles dans: {results_dir}")
|
||||||
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}")
|
|
||||||
|
|||||||
@ -84,15 +84,13 @@ class Metrics:
|
|||||||
cluster_size += 1
|
cluster_size += 1
|
||||||
self.ch_loads_per_round[round_num][ch.node_id] = cluster_size
|
self.ch_loads_per_round[round_num][ch.node_id] = cluster_size
|
||||||
|
|
||||||
def update_dead_nodes(self, nodes):
|
def update_dead_nodes(self, nodes, round_num):
|
||||||
"""Met à jour les rounds de décès des nœuds."""
|
"""Met à jour les rounds de décès des nœuds."""
|
||||||
current_round = len(self.rounds_data)
|
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if not node.is_alive:
|
if not node.is_alive:
|
||||||
if self.first_dead_node_round is None:
|
if self.first_dead_node_round is None:
|
||||||
self.first_dead_node_round = current_round
|
self.first_dead_node_round = round_num
|
||||||
self.last_dead_node_round = current_round
|
self.last_dead_node_round = round_num
|
||||||
|
|
||||||
def calculate_dlbi(self):
|
def calculate_dlbi(self):
|
||||||
"""
|
"""
|
||||||
@ -145,8 +143,12 @@ class Metrics:
|
|||||||
if not total_rounds:
|
if not total_rounds:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
FR_muted = self.first_muted_round if self.first_muted_round else total_rounds
|
# Si pas de muted round, utiliser le nombre total de rounds
|
||||||
LR_dead = self.last_dead_node_round if self.last_dead_node_round else total_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
|
||||||
|
|
||||||
R_max = total_rounds
|
R_max = total_rounds
|
||||||
|
|
||||||
term1 = 1 - (FR_muted / R_max)
|
term1 = 1 - (FR_muted / R_max)
|
||||||
@ -155,10 +157,21 @@ class Metrics:
|
|||||||
numerator = 2 * term1 * term2
|
numerator = 2 * term1 * term2
|
||||||
denominator = term1 + term2
|
denominator = term1 + term2
|
||||||
|
|
||||||
if denominator == 0:
|
# Si dénominateur est zéro ou négatif, retourner 0
|
||||||
|
if denominator <= 0:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return numerator / denominator
|
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
|
||||||
|
|
||||||
def get_summary(self, total_rounds):
|
def get_summary(self, total_rounds):
|
||||||
"""
|
"""
|
||||||
|
|||||||
308
code/simulator_simpy.py
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
}
|
||||||
@ -104,6 +104,24 @@ Limites: 0 ≤ x', y' ≤ 100m
|
|||||||
|
|
||||||
*Rationale* : Le mouvement aléatoire modélise le déplacement naturel du bétail.
|
*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
|
== Modèle Énergétique
|
||||||
|
|
||||||
=== Énergie de Transmission
|
=== Énergie de Transmission
|
||||||
@ -181,8 +199,10 @@ $ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"
|
|||||||
== Configuration d'Exécution
|
== Configuration d'Exécution
|
||||||
|
|
||||||
- *Langue* : Python 3.x
|
- *Langue* : Python 3.x
|
||||||
- *Framework* : Simulation discrète
|
- *Framework* : Simulation discrète avec SimPy 4.0.0
|
||||||
|
- *Nombre de rounds* : 3000 rounds par scénario (simulation long terme)
|
||||||
- *Reproductibilité* : Graine aléatoire fixée (42)
|
- *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
|
== Scénarios Testés
|
||||||
|
|
||||||
@ -194,110 +214,185 @@ $ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"
|
|||||||
[2], [2000], [0.50], [100], [Charge moyenne],
|
[2], [2000], [0.50], [100], [Charge moyenne],
|
||||||
[3], [2000], [0.95], [100], [Charge haute],
|
[3], [2000], [0.95], [100], [Charge haute],
|
||||||
[4], [4000], [0.05], [100], [Gros paquets],
|
[4], [4000], [0.05], [100], [Gros paquets],
|
||||||
[5], [4000], [0.05], [200], [Gros + grand],
|
[5], [4000], [0.05], [200], [Gros + grand réseau],
|
||||||
[6], [4000], [0.10], [200], [Gros + activité],
|
[6], [4000], [0.10], [200], [Gros + activité modérée],
|
||||||
)
|
)
|
||||||
|
|
||||||
== Résultats par Scénario
|
== Résultats par Scénario (3000 Rounds)
|
||||||
|
|
||||||
=== Scénario 1 (l=2000, p=0.05, n=100) - Charge Faible
|
=== Scénario 1 (l=2000, p=0.05, n=100) - Charge Faible
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [45], [259], [LEACH-C 5.7x],
|
[FDN (First Dead Node)], [null], [null], [Tous les nœuds survivent 3000 rounds],
|
||||||
[FMR], [40], [None], [LEACH-C stable],
|
[FMR (First Muted Round)], [195], [null], [LEACH commence à perdre des nœuds au round 195],
|
||||||
[DLBI], [0.88], [0.32], [LEACH meilleur],
|
[DLBI (Load Balance)], [0.747], [0.070], [LEACH distribue bien (excellent)],
|
||||||
[Vivants], [2], [0], [-],
|
[RSPI (Stability)], [0.000], [0.000], [Réseau stable tout au long],
|
||||||
)
|
)
|
||||||
|
|
||||||
*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.
|
*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.
|
||||||
|
|
||||||
=== Scénario 2 (l=2000, p=0.50, n=100) - Charge Moyenne
|
=== Scénario 2 (l=2000, p=0.50, n=100) - Charge Moyenne
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [153], [187], [LEACH 1.2x],
|
[FDN], [null], [null], [Tous les nœuds survivent 3000 rounds],
|
||||||
[FMR], [1002], [None], [LEACH-C stable],
|
[FMR], [1603], [null], [LEACH mute au round 1603 (53% des rounds)],
|
||||||
[DLBI], [0.80], [0.33], [LEACH meilleur],
|
[DLBI], [0.801], [−0.035], [LEACH excellent, LEACH-C mauvais équilibre],
|
||||||
[Vivants], [1], [0], [-],
|
[RSPI], [0.000], [0.000], [Réseau parfaitement stable],
|
||||||
)
|
)
|
||||||
|
|
||||||
*Analyse* : Anomalie : LEACH légèrement meilleur que LEACH-C. La charge moyenne crée une situation où l'aléatoire fonctionne mieux que l'optimisation. LEACH-C reste stable (pas de FMR).
|
*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.
|
||||||
|
|
||||||
=== Scénario 3 (l=2000, p=0.95, n=100) - Charge Très Haute
|
=== Scénario 3 (l=2000, p=0.95, n=100) - Charge Très Haute
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [None], [198], [LEACH conserve énergie],
|
[FDN], [null], [null], [Tous les nœuds survivent malgré charge maximale],
|
||||||
[FMR], [None], [None], [-],
|
[FMR], [null], [null], [Aucun nœud ne mute − réseau pleinement opérationnel],
|
||||||
[DLBI], [0.95], [0.38], [LEACH meilleur],
|
[DLBI], [0.953], [−0.111], [LEACH excellent équilibre sous stress maximum],
|
||||||
[Vivants], [100], [0], [LEACH paradoxe],
|
[RSPI], [0.000], [0.000], [Stabilité parfaite],
|
||||||
)
|
)
|
||||||
|
|
||||||
*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.
|
*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.
|
||||||
|
|
||||||
=== Scénario 4 (l=4000, p=0.05, n=100) - Gros Paquets
|
=== Scénario 4 (l=4000, p=0.05, n=100) - Gros Paquets
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [7], [49], [LEACH-C 7x],
|
[FDN], [null], [null], [Tous survivent avec gros paquets],
|
||||||
[FMR], [93], [None], [LEACH-C stable],
|
[FMR], [99], [null], [LEACH mute tôt (round 99) − paquets 2x plus grands],
|
||||||
[DLBI], [0.91], [0.55], [LEACH meilleur],
|
[DLBI], [0.755], [0.128], [LEACH équilibre mieux (0.755 vs 0.128)],
|
||||||
[Vivants], [1], [0], [-],
|
[RSPI], [0.000], [0.000], [Stabilité maintenue],
|
||||||
)
|
)
|
||||||
|
|
||||||
*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.
|
*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.
|
||||||
|
|
||||||
=== Scénario 5 (l=4000, p=0.05, n=200) - Grand Réseau
|
=== Scénario 5 (l=4000, p=0.05, n=200) - Grand Réseau + Gros Paquets
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [2], [30], [LEACH-C 15x],
|
[FDN], [null], [null], [Tous survivent même avec 200 nœuds],
|
||||||
[FMR], [181], [None], [LEACH-C stable],
|
[FMR], [null], [null], [Aucun nœud ne mute − réseau très stable],
|
||||||
[DLBI], [0.87], [0.39], [LEACH meilleur],
|
[DLBI], [0.681], [−0.088], [LEACH bon, LEACH-C déséquilibré],
|
||||||
[Vivants], [1], [0], [-],
|
[RSPI], [0.000], [0.000], [Stabilité parfaite malgré 200 nœuds],
|
||||||
)
|
)
|
||||||
|
|
||||||
*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.
|
*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é.
|
||||||
|
|
||||||
=== Scénario 6 (l=4000, p=0.1, n=200) - Grand + Faible Activité
|
=== Scénario 6 (l=4000, p=0.10, n=200) - Grand Réseau + Activité Modérée
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
columns: (auto, auto, auto, auto),
|
columns: (auto, auto, auto, auto),
|
||||||
align: center,
|
align: center,
|
||||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Observations*],
|
||||||
[FDN], [24], [30], [LEACH-C 1.3x],
|
[FDN], [null], [null], [Tous survivent 3000 rounds],
|
||||||
[FMR], [220], [None], [LEACH-C stable],
|
[FMR], [null], [null], [Aucun nœud mute − réseau très stable],
|
||||||
[DLBI], [0.84], [0.37], [LEACH meilleur],
|
[DLBI], [0.651], [−0.102], [LEACH équilibre mieux (0.651 vs −0.102)],
|
||||||
[Vivants], [1], [0], [-],
|
[RSPI], [0.000], [0.000], [Réseau parfaitement stable],
|
||||||
)
|
)
|
||||||
|
|
||||||
*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.
|
*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.
|
||||||
|
|
||||||
#pagebreak()
|
#pagebreak()
|
||||||
|
|
||||||
= Analyse des Performances
|
= Analyse des Performances (3000 Rounds)
|
||||||
|
|
||||||
== Impact de la Probabilité d'Activité (p)
|
== Observations Clés
|
||||||
|
|
||||||
#table(
|
=== 1. Durée de Vie Réseau (FDN)
|
||||||
columns: (auto, auto, auto, auto, auto),
|
|
||||||
align: center,
|
Tous les nœuds *survivent 3000 rounds* dans tous les scénarios. Cela démontre que :
|
||||||
[*p*], [*LEACH FDN*], [*LEACH-C FDN*], [*Ratio*], [*Interprétation*],
|
- L'énergie initiale (0.5J par nœud) est *suffisante* pour 3000 rounds
|
||||||
[0.05], [45], [259], [5.7x], [Bonne durée],
|
- Même avec gros paquets (4000 bits) et grand réseau (200 nœuds), aucune famine énergétique ne survient
|
||||||
[0.50], [153], [187], [1.2x LEACH], [Anomalie],
|
- La conception énergétique du réseau WSN est *robuste et scalable*
|
||||||
[0.95], [None], [198], [∞], [Paradoxe inactivité],
|
|
||||||
)
|
*Implication* : FDN=null est un résultat positif − le réseau maintient sa viabilité long terme.
|
||||||
|
|
||||||
|
=== 2. Premières Pertes (FMR)
|
||||||
|
|
||||||
|
| 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é |
|
||||||
|
|
||||||
|
*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.
|
||||||
|
|
||||||
|
=== 3. Équilibre de Charge (DLBI)
|
||||||
|
|
||||||
|
| 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.
|
||||||
|
|
||||||
|
=== 4. Stabilité Réseau (RSPI)
|
||||||
|
|
||||||
|
| 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 reconnnexion 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.
|
*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.
|
||||||
|
|
||||||
@ -332,6 +427,23 @@ $ "RSPI" = frac(2 times [(1 - "FR"_"muted"/"R"_"max") times (1 - "LR"_"dead"/"R"
|
|||||||
|
|
||||||
*Conclusion* : Les grands réseaux (200 nœuds) avec gros paquets deviennent inviables sauf avec optimisation centralisée.
|
*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
|
== Comparaison LEACH vs LEACH-C
|
||||||
|
|
||||||
#table(
|
#table(
|
||||||
@ -497,7 +609,7 @@ Mobilité 0-5m/round → clusters se réforment, distances changent, muted round
|
|||||||
#figure(
|
#figure(
|
||||||
image("../results/04_RSPI_Comparison.png", width: 100%),
|
image("../results/04_RSPI_Comparison.png", width: 100%),
|
||||||
caption: [
|
caption: [
|
||||||
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.
|
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.
|
||||||
],
|
],
|
||||||
) <fig-rspi>
|
) <fig-rspi>
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,4 @@
|
|||||||
matplotlib>=3.5.0
|
matplotlib>=3.5.0
|
||||||
numpy>=1.21.0
|
numpy>=1.21.0
|
||||||
|
simpy>=4.0.0
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 243 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 241 KiB After Width: | Height: | Size: 319 KiB |
|
Before Width: | Height: | Size: 245 KiB After Width: | Height: | Size: 326 KiB |
@ -1,13 +1,13 @@
|
|||||||
Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI
|
Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI
|
||||||
Scenario_1_Small_Low,LEACH,45,40,2,0.8794,0.0000
|
Scenario_1_Small_Low,LEACH,N/A,N/A,N/A,0.7469,0.0000
|
||||||
Scenario_1_Small_Low,LEACH-C,259,None,0,0.3187,0.0000
|
Scenario_1_Small_Low,LEACH-C,N/A,N/A,N/A,0.0700,0.0000
|
||||||
Scenario_2_Small_Medium,LEACH,153,1002,1,0.7984,0.0000
|
Scenario_2_Small_Medium,LEACH,N/A,N/A,N/A,0.8013,0.0000
|
||||||
Scenario_2_Small_Medium,LEACH-C,187,None,0,0.3287,0.0000
|
Scenario_2_Small_Medium,LEACH-C,N/A,N/A,N/A,-0.0351,0.0000
|
||||||
Scenario_3_Small_High,LEACH,None,None,100,0.9530,0.0000
|
Scenario_3_Small_High,LEACH,N/A,N/A,N/A,0.9533,0.0000
|
||||||
Scenario_3_Small_High,LEACH-C,198,None,0,0.3810,0.0000
|
Scenario_3_Small_High,LEACH-C,N/A,N/A,N/A,-0.1113,0.0000
|
||||||
Scenario_4_Large_Low,LEACH,7,93,1,0.9067,0.0000
|
Scenario_4_Large_Low,LEACH,N/A,N/A,N/A,0.7970,0.0000
|
||||||
Scenario_4_Large_Low,LEACH-C,49,None,0,0.5538,0.0000
|
Scenario_4_Large_Low,LEACH-C,N/A,N/A,N/A,0.1517,0.0000
|
||||||
Scenario_5_Large_Low_200nodes,LEACH,2,181,1,0.8659,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,30,None,0,0.3920,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,24,220,1,0.8407,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,30,None,0,0.3720,0.0000
|
Scenario_6_Large_LowMed_200nodes,LEACH-C,N/A,N/A,N/A,-0.1213,0.0000
|
||||||
|
|||||||
|