- 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.
189 lines
6.2 KiB
Python
189 lines
6.2 KiB
Python
"""
|
|
Classe Metrics : Collecte et calcul des métriques de performance
|
|
"""
|
|
|
|
import math
|
|
from collections import defaultdict
|
|
|
|
|
|
class Metrics:
|
|
"""
|
|
Collecte les métriques de performance pour chaque round.
|
|
|
|
Métriques suivies:
|
|
1. Alive nodes count
|
|
2. Packets to cluster head
|
|
3. Packets to base station
|
|
4. Residual energy
|
|
5. Muted rounds (no CH elected)
|
|
6. First muted round (FMR)
|
|
7. First dead node (FDN)
|
|
8. Last dead node
|
|
9. Dynamic Load Balancing Index (DLBI)
|
|
10. Relative Silence Period Index (RSPI)
|
|
"""
|
|
|
|
def __init__(self):
|
|
# Données par round
|
|
self.rounds_data = []
|
|
|
|
# Statistiques globales
|
|
self.first_dead_node_round = None
|
|
self.last_dead_node_round = None
|
|
self.first_muted_round = None
|
|
self.total_muted_rounds = 0
|
|
self.muted_rounds_list = []
|
|
|
|
# Pour calcul DLBI
|
|
self.ch_loads_per_round = {} # round -> {ch_id -> load}
|
|
|
|
def record_round(self, round_num, nodes, ch_nodes, packets_to_ch, packets_to_bs,
|
|
muted=False):
|
|
"""
|
|
Enregistre les données d'une ronde.
|
|
|
|
Args:
|
|
round_num (int): Numéro du round
|
|
nodes (list): Liste de tous les nœuds
|
|
ch_nodes (list): Liste des cluster heads de ce round
|
|
packets_to_ch (int): Nombre total de paquets vers CHs
|
|
packets_to_bs (int): Nombre total de paquets vers BS
|
|
muted (bool): Si true, aucun CH n'a été élu ce round
|
|
"""
|
|
alive_count = sum(1 for n in nodes if n.is_alive)
|
|
residual_energy = [n.energy for n in nodes if n.is_alive]
|
|
avg_residual_energy = sum(residual_energy) / len(residual_energy) if residual_energy else 0
|
|
|
|
round_data = {
|
|
"round": round_num,
|
|
"alive_nodes": alive_count,
|
|
"packets_to_ch": packets_to_ch,
|
|
"packets_to_bs": packets_to_bs,
|
|
"avg_residual_energy": avg_residual_energy,
|
|
"ch_count": len(ch_nodes),
|
|
"muted": muted,
|
|
}
|
|
|
|
self.rounds_data.append(round_data)
|
|
|
|
# Suivi des muted rounds
|
|
if muted:
|
|
self.muted_rounds_list.append(round_num)
|
|
self.total_muted_rounds += 1
|
|
if self.first_muted_round is None:
|
|
self.first_muted_round = round_num
|
|
|
|
# Enregistrer les charges des CHs pour DLBI
|
|
if ch_nodes:
|
|
self.ch_loads_per_round[round_num] = {}
|
|
for ch in ch_nodes:
|
|
# La charge = nombre de nœuds dans le cluster
|
|
cluster_size = 1 # Le CH lui-même
|
|
for node in nodes:
|
|
if node.cluster_id == ch.node_id and not node.is_cluster_head:
|
|
cluster_size += 1
|
|
self.ch_loads_per_round[round_num][ch.node_id] = cluster_size
|
|
|
|
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 = current_round
|
|
self.last_dead_node_round = current_round
|
|
|
|
def calculate_dlbi(self):
|
|
"""
|
|
Calcule le Dynamic Load Balancing Index.
|
|
|
|
DLBI = (1/N) * Σ(DLBI_r) pour r=1 à N
|
|
DLBI_r = 1 - [Σ(L_j,r - L̄_r)² / (m_r * L̄_r²)]
|
|
|
|
Returns:
|
|
float: DLBI (0 à 1, plus élevé = mieux)
|
|
"""
|
|
if not self.ch_loads_per_round:
|
|
return 0
|
|
|
|
dlbi_values = []
|
|
|
|
for round_num, loads in self.ch_loads_per_round.items():
|
|
if not loads or len(loads) == 0:
|
|
continue
|
|
|
|
loads_list = list(loads.values())
|
|
m_r = len(loads_list) # Nombre de CHs
|
|
L_bar_r = sum(loads_list) / m_r # Charge moyenne
|
|
|
|
if L_bar_r == 0:
|
|
dlbi_r = 0
|
|
else:
|
|
variance = sum((load - L_bar_r) ** 2 for load in loads_list)
|
|
dlbi_r = 1 - (variance / (m_r * (L_bar_r ** 2)))
|
|
|
|
dlbi_values.append(dlbi_r)
|
|
|
|
if dlbi_values:
|
|
return sum(dlbi_values) / len(dlbi_values)
|
|
return 0
|
|
|
|
def calculate_rspi(self, total_rounds):
|
|
"""
|
|
Calcule le Relative Silence Period Index.
|
|
|
|
RSPI = 2 * [(1 - FR_muted/R_max) * (1 - LR_dead/R_max)]
|
|
/ [(1 - FR_muted/R_max) + (1 - LR_dead/R_max)]
|
|
|
|
Args:
|
|
total_rounds (int): Nombre total de rounds
|
|
|
|
Returns:
|
|
float: RSPI (0 à 1, plus élevé = mieux)
|
|
"""
|
|
if not total_rounds:
|
|
return 0
|
|
|
|
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)
|
|
term2 = 1 - (LR_dead / R_max)
|
|
|
|
numerator = 2 * term1 * term2
|
|
denominator = term1 + term2
|
|
|
|
if denominator == 0:
|
|
return 0
|
|
|
|
return numerator / denominator
|
|
|
|
def get_summary(self, total_rounds):
|
|
"""
|
|
Retourne un résumé complet des métriques.
|
|
|
|
Returns:
|
|
dict: Dictionnaire avec toutes les métriques
|
|
"""
|
|
if not self.rounds_data:
|
|
return {}
|
|
|
|
final_round = self.rounds_data[-1]
|
|
|
|
return {
|
|
"total_rounds_completed": len(self.rounds_data),
|
|
"final_alive_nodes": final_round["alive_nodes"],
|
|
"first_dead_node_round": self.first_dead_node_round,
|
|
"last_dead_node_round": self.last_dead_node_round,
|
|
"first_muted_round": self.first_muted_round,
|
|
"total_muted_rounds": self.total_muted_rounds,
|
|
"dlbi": self.calculate_dlbi(),
|
|
"rspi": self.calculate_rspi(total_rounds),
|
|
}
|
|
|
|
def get_rounds_data(self):
|
|
"""Retourne les données de tous les rounds."""
|
|
return self.rounds_data
|