- 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.
200 lines
6.5 KiB
Python
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()
|