AlgoRep/code/leach_c.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

240 lines
7.9 KiB
Python

"""
Implémentation du protocole LEACH-C (Centralisé)
"""
import random
import math
from node import Node
from metrics import Metrics
from config import BS_POSITION
class LEACHC:
"""
Implémentation de LEACH-C en version centralisée.
La BS reçoit l'état de tous les nœuds et calcule les clusters optimaux.
"""
def __init__(self, nodes, probability_ch, packet_size):
"""
Initialise le protocole LEACH-C.
Args:
nodes (list): Liste des nœuds
probability_ch (float): Probabilité d'activité des nœuds (non utilisée pour élection)
packet_size (int): Taille des paquets en bits
"""
self.nodes = nodes
self.probability_ch = probability_ch # Utilisé pour activité des nœuds
self.packet_size = packet_size
self.metrics = Metrics()
self.ch_nodes = []
self.round_num = 0
def calculate_optimal_clusters(self):
"""
Calcule une partition optimale des clusters.
Heuristique : sélectionner les nœuds avec le plus d'énergie comme CHs.
Nombre de CHs ≈ sqrt(N)/2 est une bonne heuristique.
Returns:
list: Liste des CHs calculés par la BS
"""
alive_nodes = [n for n in self.nodes if n.is_alive]
if not alive_nodes:
return []
# Nombre optimal de CHs (heuristique classique)
num_ch_optimal = max(1, int(math.sqrt(len(alive_nodes)) / 2))
# Trier les nœuds par énergie décroissante
sorted_nodes = sorted(alive_nodes, key=lambda n: n.energy, reverse=True)
# Sélectionner les CHs
ch_candidates = sorted_nodes[:num_ch_optimal]
return ch_candidates
def form_clusters(self, ch_nodes):
"""
La BS assigne les nœuds à leurs CHs respectifs.
Heuristique : chaque nœud rejoint le CH le plus proche.
Args:
ch_nodes (list): Liste des CHs
"""
# Reset cluster affiliation
for node in self.nodes:
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 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 send_ch_info_to_bs(self, ch_nodes):
"""
Les CHs envoient leurs info à la BS.
Coût énergétique : transmission du CH vers la BS.
Args:
ch_nodes (list): Liste des CHs
"""
for ch in ch_nodes:
if ch.is_alive:
# Transmettre l'info (simplification : peu d'octets)
info_bits = 32 # Position et énergie
distance_to_bs = ch.distance_to(BS_POSITION[0], BS_POSITION[1])
ch.transmit(info_bits, distance_to_bs)
def communication_phase(self, ch_nodes):
"""
Phase de communication :
- Les nœuds envoient les données à leur CH
- Les CHs agrègent et envoient à la BS
Args:
ch_nodes (list): Liste des CHs
Returns:
tuple: (packets_to_ch, packets_to_bs)
"""
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:
closest_ch = None
min_distance = float('inf')
for ch in 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 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-C.
Returns:
bool: True si la ronde a réussi, False si aucun CH disponible
"""
self.round_num += 1
# Reset pour la nouvelle ronde
for node in self.nodes:
node.reset_for_round()
# Phase 1 : BS calcule les CHs optimaux
ch_candidates = self.calculate_optimal_clusters()
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)
return False
# Marquer les CHs
self.ch_nodes = []
for ch in ch_candidates:
ch.is_cluster_head = True
self.ch_nodes.append(ch)
# Phase 2 : Formation des clusters par la BS
self.form_clusters(self.ch_nodes)
# Phase 3 : Envoi de l'info des CHs à la BS
self.send_ch_info_to_bs(self.ch_nodes)
# Phase 4 : Communication
packets_to_ch, packets_to_bs = self.communication_phase(self.ch_nodes)
# Phase 5 : 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()