AlgoRep/code/leach.py
paul.roost 7a33c7096d Add simulation results and launch script for LEACH/LEACH-C
- Created summary.csv to store simulation results for various scenarios, protocols, and metrics.
- Developed run.sh script to automate the simulation process, including dependency checks, simulation execution, and result analysis.
- Ensured proper directory structure for results and reports.
- Added error handling for Python and matplotlib dependencies.
2025-11-02 13:55:51 +01:00

200 lines
6.5 KiB
Python

"""
Implémentation du protocole LEACH (Low-Energy Adaptive Clustering Hierarchy)
"""
import random
import math
from node import Node
from metrics import Metrics
from config import BS_POSITION
class LEACH:
"""
Implémentation de LEACH en version décentralisée.
Chaque nœud a une probabilité p d'être élu cluster head.
"""
def __init__(self, nodes, probability_ch, packet_size):
"""
Initialise le protocole LEACH.
Args:
nodes (list): Liste des nœuds
probability_ch (float): Probabilité d'élection comme CH
packet_size (int): Taille des paquets en bits
"""
self.nodes = nodes
self.probability_ch = probability_ch
self.packet_size = packet_size
self.metrics = Metrics()
self.ch_nodes = []
self.round_num = 0
def elect_cluster_heads(self):
"""
Élection aléatoire des cluster heads.
Chaque nœud vivant a probabilité p de devenir CH.
"""
self.ch_nodes = []
for node in self.nodes:
if node.is_alive:
if random.random() < self.probability_ch:
node.is_cluster_head = True
self.ch_nodes.append(node)
return len(self.ch_nodes) > 0
def form_clusters(self):
"""
Formation des clusters : chaque nœud non-CH rejoigne le CH le plus proche.
"""
# Reset cluster affiliation
for node in self.nodes:
if not node.is_cluster_head:
node.cluster_id = None
# Assignation des nœuds aux CHs
for node in self.nodes:
if not node.is_alive or node.is_cluster_head:
continue
# Trouver le CH le plus proche
closest_ch = None
min_distance = float('inf')
for ch in self.ch_nodes:
if ch.is_alive:
dist = node.distance_to(ch.x, ch.y)
if dist < min_distance:
min_distance = dist
closest_ch = ch
if closest_ch:
node.cluster_id = closest_ch.node_id
def communication_phase(self):
"""
Phase de communication :
- Les nœuds envoient les données à leur CH
- Les CHs agrègent et envoient à la BS
Returns:
tuple: (packets_to_ch, packets_to_bs, total_energy_consumed)
"""
packets_to_ch = 0
packets_to_bs = 0
# Phase 1 : Nœuds → CHs
for node in self.nodes:
if not node.is_alive or node.is_cluster_head:
continue
# Déterminer si le nœud doit envoyer (probabilité p)
if random.random() < self.probability_ch: # Utilisé comme probabilité d'activité
closest_ch = None
min_distance = float('inf')
for ch in self.ch_nodes:
if ch.is_alive:
dist = node.distance_to(ch.x, ch.y)
if dist < min_distance:
min_distance = dist
closest_ch = ch
if closest_ch:
# Transmission du nœud au CH
energy_tx = node.transmit(self.packet_size, min_distance)
# Réception au CH
energy_rx = closest_ch.receive(self.packet_size)
packets_to_ch += 1
# Phase 2 : CHs → BS (après agrégation)
for ch in self.ch_nodes:
if not ch.is_alive:
continue
# Compter les nœuds dans le cluster
cluster_members = sum(1 for n in self.nodes
if n.cluster_id == ch.node_id and n.is_alive)
if cluster_members > 0:
# Agrégation des données reçues
aggregated_bits = cluster_members * self.packet_size
energy_agg = ch.aggregate(aggregated_bits)
# Transmission à la BS
distance_to_bs = ch.distance_to(BS_POSITION[0], BS_POSITION[1])
energy_tx_bs = ch.transmit(aggregated_bits, distance_to_bs)
packets_to_bs += 1
return packets_to_ch, packets_to_bs
def run_round(self):
"""
Exécute une ronde complète du protocole LEACH.
Returns:
bool: True si la ronde a réussi, False si muted (pas de CH)
"""
self.round_num += 1
# Reset pour la nouvelle ronde
for node in self.nodes:
node.reset_for_round()
# Phase 1 : Élection des CHs
ch_elected = self.elect_cluster_heads()
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)
return False
# Phase 2 : Formation des clusters
self.form_clusters()
# Phase 3 : Communication
packets_to_ch, packets_to_bs = self.communication_phase()
# Phase 4 : Mobilité - déplacement aléatoire des nœuds
for node in self.nodes:
if node.is_alive:
node.move()
# 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)
return True
def run_simulation(self, num_rounds):
"""
Lance la simulation complète.
Args:
num_rounds (int): Nombre de rounds à simuler
"""
for _ in range(num_rounds):
# Vérifier s'il y a encore des nœuds vivants
alive_count = sum(1 for n in self.nodes if n.is_alive)
if alive_count == 0:
break
self.run_round()
def get_metrics(self, total_rounds):
"""Retourne les métriques de la simulation."""
return self.metrics.get_summary(total_rounds)
def get_detailed_metrics(self):
"""Retourne les données détaillées de chaque round."""
return self.metrics.get_rounds_data()