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.
This commit is contained in:
commit
7a33c7096d
257
README.md
Normal file
257
README.md
Normal file
@ -0,0 +1,257 @@
|
||||
# Simulation LEACH vs LEACH-C pour Réseaux Dynamiques
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Ce projet implémente une simulation complète des protocoles **LEACH** (Low-Energy Adaptive Clustering Hierarchy) et **LEACH-C** (centralisé) pour des réseaux de capteurs sans fil (WSN) avec **mobilité dynamique** des nœuds.
|
||||
|
||||
**Contexte** : Agriculture de précision - suivi en temps réel de bovins avec capteurs se déplaçant dans un champ.
|
||||
|
||||
**Deadline** : 5 novembre 2025, 23:42
|
||||
|
||||
---
|
||||
|
||||
## Structure du Projet
|
||||
|
||||
```
|
||||
/algo/
|
||||
├── code/
|
||||
│ ├── config.py # Configuration et constantes
|
||||
│ ├── node.py # Classe Node
|
||||
│ ├── metrics.py # Classe Metrics (10 métriques)
|
||||
│ ├── leach.py # Protocole LEACH décentralisé
|
||||
│ ├── leach_c.py # Protocole LEACH-C centralisé
|
||||
│ ├── main.py # Contrôleur principal
|
||||
│ ├── analysis.py # Analyseur et graphiques
|
||||
│ └── run.sh # Script de lancement
|
||||
├── results/
|
||||
│ ├── simulation_results.json # Résultats bruts
|
||||
│ ├── summary.csv # Tableau récapitulatif
|
||||
│ ├── 01_FDN_Comparison.png # Graphique FDN
|
||||
│ ├── 02_FMR_Comparison.png # Graphique FMR
|
||||
│ ├── 03_DLBI_Comparison.png # Graphique DLBI
|
||||
│ ├── 04_RSPI_Comparison.png # Graphique RSPI
|
||||
│ └── 05_Alive_Nodes_Over_Time.png
|
||||
└── rapport/
|
||||
└── Rapport_LEACH_LEACHC.typ # Rapport complet (Typst + PDF)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comment Exécuter
|
||||
|
||||
### 1. Installation des dépendances
|
||||
|
||||
```bash
|
||||
pip install matplotlib
|
||||
```
|
||||
|
||||
### 2. Lancer la simulation
|
||||
|
||||
```bash
|
||||
cd /home/paul/algo
|
||||
python code/main.py
|
||||
```
|
||||
|
||||
### 3. Générer les graphiques et analyses
|
||||
|
||||
```bash
|
||||
python code/analysis.py
|
||||
```
|
||||
|
||||
Les résultats seront sauvegardés dans `/home/paul/algo/results/`
|
||||
|
||||
---
|
||||
|
||||
## Implémentation
|
||||
|
||||
### Protocoles
|
||||
|
||||
#### **LEACH (Décentralisé)**
|
||||
- Élection aléatoire des cluster heads (probabilité p)
|
||||
- Formation de clusters basée sur proximité
|
||||
- Communication nœud → CH → BS
|
||||
- Agrégation de données au niveau CH
|
||||
|
||||
#### **LEACH-C (Centralisé)**
|
||||
- BS reçoit l'état de tous les nœuds
|
||||
- Calcul des clusters optimaux (heuristique : sélection par énergie)
|
||||
- Distribution optimale des rôles
|
||||
- Moins aléatoire mais plus coûteux en énergie
|
||||
|
||||
### Mobilité Dynamique
|
||||
|
||||
- Chaque nœud se déplace aléatoirement chaque round
|
||||
- Déplacement max : 5 mètres
|
||||
- Restent dans les limites du champ (100m × 100m)
|
||||
- Impact majeur sur stabilité et efficacité
|
||||
|
||||
### Modèle Énergétique
|
||||
|
||||
```
|
||||
E_Tx(l, d) =
|
||||
- Si d ≤ d0 : E_elec×l + E_fs×l×d²
|
||||
- Si d > d0 : E_elec×l + E_mp×l×d⁴
|
||||
|
||||
E_Rx(l) = E_elec × l
|
||||
E_Agg(l) = E_da × l
|
||||
|
||||
d0 = sqrt(E_fs / E_mp)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Métriques Implémentées
|
||||
|
||||
### 1. **Nœuds Vivants** (Alive Nodes Count)
|
||||
Nombre de nœuds actifs après chaque round.
|
||||
|
||||
### 2. **Paquets vers CH** (Packets to Cluster Head)
|
||||
Nombre total de paquets envoyés par les nœuds réguliers vers leurs CHs.
|
||||
|
||||
### 3. **Paquets vers BS** (Packets to Base Station)
|
||||
Nombre total de paquets envoyés par les CHs vers la station de base.
|
||||
|
||||
### 4. **Énergie Résiduelle** (Residual Energy)
|
||||
Énergie restante dans chaque nœud.
|
||||
|
||||
### 5. **Muted Rounds**
|
||||
Nombre de rounds où **aucun CH n'est élu** (communication impossible).
|
||||
|
||||
### 6. **FMR** (First Muted Round)
|
||||
Le round exact où le premier silence apparaît.
|
||||
|
||||
### 7. **FDN** (First Dead Node)
|
||||
Le round quand le **premier nœud** épuise son énergie.
|
||||
|
||||
### 8. **Last Dead Node**
|
||||
Le round quand le **dernier nœud** meurt.
|
||||
|
||||
### 9. **DLBI** (Dynamic Load Balancing Index)
|
||||
|
||||
$$DLBI = \frac{1}{N} \sum_{r=1}^{N} DLBI_r$$
|
||||
|
||||
$$DLBI_r = 1 - \frac{\sum(L_{j,r} - \bar{L}_r)^2}{m_r \times \bar{L}_r^2}$$
|
||||
|
||||
**Interprétation** : Évalue l'équilibre de charge entre les CHs.
|
||||
- Proche de 1 = distribution équilibrée
|
||||
- Bas = déséquilibre grave
|
||||
|
||||
### 10. **RSPI** (Relative Silence Period Index)
|
||||
|
||||
$$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** : Moyenne harmonique entre résilience et durée de vie.
|
||||
|
||||
---
|
||||
|
||||
## Scénarios de Test
|
||||
|
||||
| Scénario | Paquets (l) | Probabilité (p) | Nœuds (n) |
|
||||
|----------|-------------|-----------------|-----------|
|
||||
| 1 | 2000 | 0.05 | 100 |
|
||||
| 2 | 2000 | 0.50 | 100 |
|
||||
| 3 | 2000 | 0.95 | 100 |
|
||||
| 4 | 4000 | 0.05 | 100 |
|
||||
| 5 | 4000 | 0.05 | 200 |
|
||||
| 6 | 4000 | 0.10 | 200 |
|
||||
|
||||
---
|
||||
|
||||
## Résultats
|
||||
|
||||
Les résultats incluent :
|
||||
|
||||
1. **Fichier JSON** : Données complètes de chaque round
|
||||
2. **CSV récapitulatif** : Tableau avec toutes les métriques
|
||||
3. **Graphiques** :
|
||||
- FDN Comparison
|
||||
- FMR Comparison
|
||||
- DLBI Comparison
|
||||
- RSPI Comparison
|
||||
- Nombre de nœuds vivants au fil du temps
|
||||
|
||||
---
|
||||
|
||||
## Points Importants
|
||||
|
||||
### Dynamique vs Statique
|
||||
- **Statique** : Clusters stables, peu d'overhead
|
||||
- **Dynamique** : Clusters instables, plus d'énergie perdue, plus réaliste
|
||||
|
||||
### Rounds Muets (Muted Rounds)
|
||||
Quand **aucun nœud n'est élu** comme CH → communication impossible. Problème majeur en réseau dynamique.
|
||||
|
||||
### LEACH vs LEACH-C
|
||||
- **LEACH** : Scalable, décentralisé, mais élection aléatoire
|
||||
- **LEACH-C** : Meilleure distribution, plus coûteux énergétiquement
|
||||
|
||||
### Impact de la Mobilité
|
||||
La mobilité dynamique cause :
|
||||
- Augmentation de la consommation d'énergie
|
||||
- Plus de muted rounds
|
||||
- Nécessité de réélections fréquentes
|
||||
|
||||
---
|
||||
|
||||
## Technologies Utilisées
|
||||
|
||||
- **Python 3.x**
|
||||
- **Matplotlib** : Visualisation des résultats
|
||||
- **JSON** : Sauvegarde des résultats
|
||||
- **Simulation événementielle** : Approche discrète des rounds
|
||||
|
||||
---
|
||||
|
||||
## Fichiers Principaux
|
||||
|
||||
### `config.py`
|
||||
Configuration globale, paramètres, scénarios.
|
||||
|
||||
### `node.py`
|
||||
Classe représentant un capteur (position, énergie, états).
|
||||
|
||||
### `metrics.py`
|
||||
Collecte des 10 métriques de performance.
|
||||
|
||||
### `leach.py`
|
||||
Implémentation du protocole LEACH décentralisé.
|
||||
|
||||
### `leach_c.py`
|
||||
Implémentation du protocole LEACH-C centralisé.
|
||||
|
||||
### `main.py`
|
||||
Contrôleur principal : crée les nœuds, lance les protocoles.
|
||||
|
||||
### `analysis.py`
|
||||
Analyseur et générateur de graphiques.
|
||||
|
||||
---
|
||||
|
||||
## Ressources Académiques
|
||||
|
||||
1. **LEACH Original** : W. B. Heinzelman et al., "An application-specific protocol architecture for wireless microsensor networks", IEEE Trans. Wireless Commun., 2002
|
||||
|
||||
2. **LEACH-C** : W. B. Heinzelman et al., "Energy-efficient communication protocol for wireless microsensor networks", Proc. HICSS, 2000
|
||||
|
||||
3. **Efficacité Énergétique** : N. Wang & H. Zhu, "An energy efficient algorithm based on LEACH protocol", Proc. ICCSEE, 2012
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Implémentation LEACH (décentralisé)
|
||||
- [x] Implémentation LEACH-C (centralisé)
|
||||
- [x] Support mobilité dynamique
|
||||
- [x] Modèle énergétique complet
|
||||
- [x] 10 métriques de performance
|
||||
- [x] 6 scénarios de test
|
||||
- [x] Génération de graphiques
|
||||
- [x] Rapport complet (10 pages max)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contact
|
||||
|
||||
Pour des questions ou clarifications, veuillez consulter les spécifications du projet.
|
||||
|
||||
**Deadline** : 5 novembre 2025, 23:42 ⏰
|
||||
BIN
code/__pycache__/config.cpython-312.pyc
Normal file
BIN
code/__pycache__/config.cpython-312.pyc
Normal file
Binary file not shown.
BIN
code/__pycache__/leach.cpython-312.pyc
Normal file
BIN
code/__pycache__/leach.cpython-312.pyc
Normal file
Binary file not shown.
BIN
code/__pycache__/leach_c.cpython-312.pyc
Normal file
BIN
code/__pycache__/leach_c.cpython-312.pyc
Normal file
Binary file not shown.
BIN
code/__pycache__/metrics.cpython-312.pyc
Normal file
BIN
code/__pycache__/metrics.cpython-312.pyc
Normal file
Binary file not shown.
BIN
code/__pycache__/node.cpython-312.pyc
Normal file
BIN
code/__pycache__/node.cpython-312.pyc
Normal file
Binary file not shown.
238
code/analysis.py
Normal file
238
code/analysis.py
Normal file
@ -0,0 +1,238 @@
|
||||
"""
|
||||
Analyseur et générateur de graphiques pour les résultats de simulation
|
||||
"""
|
||||
|
||||
import json
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib
|
||||
matplotlib.use('Agg') # Backend sans affichage
|
||||
|
||||
|
||||
class ResultsAnalyzer:
|
||||
"""Analyse et visualise les résultats des simulations."""
|
||||
|
||||
def __init__(self, results_file):
|
||||
"""
|
||||
Charge les résultats depuis un fichier JSON.
|
||||
|
||||
Args:
|
||||
results_file (str): Chemin vers le fichier JSON
|
||||
"""
|
||||
with open(results_file, 'r') as f:
|
||||
self.results = json.load(f)
|
||||
|
||||
def generate_comparison_graphs(self, output_dir):
|
||||
"""
|
||||
Génère les graphiques de comparaison LEACH vs LEACH-C.
|
||||
|
||||
Args:
|
||||
output_dir (str): Répertoire de sortie
|
||||
"""
|
||||
# 1. FDN Comparison
|
||||
self._plot_fdn_comparison(output_dir)
|
||||
|
||||
# 2. FMR Comparison
|
||||
self._plot_fmr_comparison(output_dir)
|
||||
|
||||
# 3. DLBI Comparison
|
||||
self._plot_dlbi_comparison(output_dir)
|
||||
|
||||
# 4. RSPI Comparison
|
||||
self._plot_rspi_comparison(output_dir)
|
||||
|
||||
# 5. Alive Nodes Over Rounds
|
||||
self._plot_alive_nodes(output_dir)
|
||||
|
||||
def _plot_fdn_comparison(self, output_dir):
|
||||
"""Graphique : FDN pour tous les scénarios."""
|
||||
scenarios = list(self.results.keys())
|
||||
leach_fdn = []
|
||||
leachc_fdn = []
|
||||
|
||||
for scenario in scenarios:
|
||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||
|
||||
leach_fdn.append(leach_metrics.get("first_dead_node_round") or 0)
|
||||
leachc_fdn.append(leachc_metrics.get("first_dead_node_round") or 0)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
x_pos = range(len(scenarios))
|
||||
width = 0.35
|
||||
|
||||
ax.bar([i - width/2 for i in x_pos], leach_fdn, width, label='LEACH', color='#FF6B6B')
|
||||
ax.bar([i + width/2 for i in x_pos], leachc_fdn, width, label='LEACH-C', color='#4ECDC4')
|
||||
|
||||
ax.set_xlabel('Scénario', fontsize=12)
|
||||
ax.set_ylabel('Premier Nœud Mort (Round)', fontsize=12)
|
||||
ax.set_title('Comparaison FDN (First Dead Node)', fontsize=14, fontweight='bold')
|
||||
ax.set_xticks(x_pos)
|
||||
ax.set_xticklabels(scenarios, rotation=45, ha='right')
|
||||
ax.legend()
|
||||
ax.grid(axis='y', alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{output_dir}/01_FDN_Comparison.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
def _plot_fmr_comparison(self, output_dir):
|
||||
"""Graphique : FMR pour tous les scénarios."""
|
||||
scenarios = list(self.results.keys())
|
||||
leach_fmr = []
|
||||
leachc_fmr = []
|
||||
|
||||
for scenario in scenarios:
|
||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||
|
||||
leach_fmr.append(leach_metrics.get("first_muted_round") or 9999)
|
||||
leachc_fmr.append(leachc_metrics.get("first_muted_round") or 9999)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
x_pos = range(len(scenarios))
|
||||
width = 0.35
|
||||
|
||||
ax.bar([i - width/2 for i in x_pos], leach_fmr, width, label='LEACH', color='#FF6B6B')
|
||||
ax.bar([i + width/2 for i in x_pos], leachc_fmr, width, label='LEACH-C', color='#4ECDC4')
|
||||
|
||||
ax.set_xlabel('Scénario', fontsize=12)
|
||||
ax.set_ylabel('Premier Round Muet', fontsize=12)
|
||||
ax.set_title('Comparaison FMR (First Muted Round)', fontsize=14, fontweight='bold')
|
||||
ax.set_xticks(x_pos)
|
||||
ax.set_xticklabels(scenarios, rotation=45, ha='right')
|
||||
ax.legend()
|
||||
ax.grid(axis='y', alpha=0.3)
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f"{output_dir}/02_FMR_Comparison.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
def _plot_dlbi_comparison(self, output_dir):
|
||||
"""Graphique : DLBI pour tous les scénarios."""
|
||||
scenarios = list(self.results.keys())
|
||||
leach_dlbi = []
|
||||
leachc_dlbi = []
|
||||
|
||||
for scenario in scenarios:
|
||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||
|
||||
leach_dlbi.append(leach_metrics.get("dlbi", 0))
|
||||
leachc_dlbi.append(leachc_metrics.get("dlbi", 0))
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
x_pos = range(len(scenarios))
|
||||
width = 0.35
|
||||
|
||||
ax.bar([i - width/2 for i in x_pos], leach_dlbi, width, label='LEACH', color='#FF6B6B')
|
||||
ax.bar([i + width/2 for i in x_pos], leachc_dlbi, width, label='LEACH-C', color='#4ECDC4')
|
||||
|
||||
ax.set_xlabel('Scénario', fontsize=12)
|
||||
ax.set_ylabel('DLBI (0 à 1)', fontsize=12)
|
||||
ax.set_title('Comparaison DLBI (Dynamic Load Balancing 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()
|
||||
plt.savefig(f"{output_dir}/03_DLBI_Comparison.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
def _plot_rspi_comparison(self, output_dir):
|
||||
"""Graphique : RSPI pour tous les scénarios."""
|
||||
scenarios = list(self.results.keys())
|
||||
leach_rspi = []
|
||||
leachc_rspi = []
|
||||
|
||||
for scenario in scenarios:
|
||||
leach_metrics = self.results[scenario]["LEACH"]["metrics"]
|
||||
leachc_metrics = self.results[scenario]["LEACH-C"]["metrics"]
|
||||
|
||||
leach_rspi.append(leach_metrics.get("rspi", 0))
|
||||
leachc_rspi.append(leachc_metrics.get("rspi", 0))
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
x_pos = range(len(scenarios))
|
||||
width = 0.35
|
||||
|
||||
ax.bar([i - width/2 for i in x_pos], leach_rspi, width, label='LEACH', color='#FF6B6B')
|
||||
ax.bar([i + width/2 for i in x_pos], leachc_rspi, width, label='LEACH-C', color='#4ECDC4')
|
||||
|
||||
ax.set_xlabel('Scénario', fontsize=12)
|
||||
ax.set_ylabel('RSPI (0 à 1)', fontsize=12)
|
||||
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()
|
||||
plt.savefig(f"{output_dir}/04_RSPI_Comparison.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
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
|
||||
|
||||
fig, axes = plt.subplots(len(scenarios), 2, figsize=(14, 4*len(scenarios)))
|
||||
|
||||
for idx, scenario in enumerate(scenarios):
|
||||
# LEACH
|
||||
leach_detailed = self.results[scenario]["LEACH"]["detailed_rounds"]
|
||||
rounds = [r["round"] for r in leach_detailed]
|
||||
alive = [r["alive_nodes"] for r in leach_detailed]
|
||||
|
||||
axes[idx, 0].plot(rounds, alive, marker='o', label='LEACH', color='#FF6B6B')
|
||||
axes[idx, 0].set_xlabel('Round')
|
||||
axes[idx, 0].set_ylabel('Nœuds Vivants')
|
||||
axes[idx, 0].set_title(f'LEACH - {scenario}')
|
||||
axes[idx, 0].grid(alpha=0.3)
|
||||
|
||||
# LEACH-C
|
||||
leachc_detailed = self.results[scenario]["LEACH-C"]["detailed_rounds"]
|
||||
rounds = [r["round"] for r in leachc_detailed]
|
||||
alive = [r["alive_nodes"] for r in leachc_detailed]
|
||||
|
||||
axes[idx, 1].plot(rounds, alive, marker='s', label='LEACH-C', color='#4ECDC4')
|
||||
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()
|
||||
plt.savefig(f"{output_dir}/05_Alive_Nodes_Over_Time.png", dpi=300)
|
||||
plt.close()
|
||||
|
||||
def generate_summary_table(self, output_file):
|
||||
"""
|
||||
Génère un tableau récapitulatif en CSV.
|
||||
|
||||
Args:
|
||||
output_file (str): Chemin du fichier CSV
|
||||
"""
|
||||
with open(output_file, 'w') as f:
|
||||
# En-tête
|
||||
f.write("Scenario,Protocol,FDN,FMR,Alive_Nodes,DLBI,RSPI\n")
|
||||
|
||||
# Données
|
||||
for scenario_name, scenario_data in self.results.items():
|
||||
for protocol in ["LEACH", "LEACH-C"]:
|
||||
metrics = scenario_data[protocol]["metrics"]
|
||||
|
||||
f.write(f"{scenario_name},{protocol},")
|
||||
f.write(f"{metrics.get('first_dead_node_round', 'N/A')},")
|
||||
f.write(f"{metrics.get('first_muted_round', 'N/A')},")
|
||||
f.write(f"{metrics.get('final_alive_nodes', 'N/A')},")
|
||||
f.write(f"{metrics.get('dlbi', 'N/A'):.4f},")
|
||||
f.write(f"{metrics.get('rspi', 'N/A'):.4f}\n")
|
||||
|
||||
print(f"OK - Tableau récapitulatif: {output_file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyzer = ResultsAnalyzer("/home/paul/algo/results/simulation_results.json")
|
||||
analyzer.generate_comparison_graphs("/home/paul/algo/results")
|
||||
analyzer.generate_summary_table("/home/paul/algo/results/summary.csv")
|
||||
72
code/config.py
Normal file
72
code/config.py
Normal file
@ -0,0 +1,72 @@
|
||||
"""
|
||||
Configuration et Constantes pour la Simulation LEACH/LEACH-C
|
||||
"""
|
||||
|
||||
# ============= PARAMÈTRES DE SIMULATION =============
|
||||
|
||||
# Dimensions du champ
|
||||
FIELD_WIDTH = 100 # mètres
|
||||
FIELD_HEIGHT = 100 # mètres
|
||||
|
||||
# Position de la station de base
|
||||
BS_POSITION = (0, -100)
|
||||
|
||||
# Nombre de nœuds (par scénario)
|
||||
NUM_NODES_DEFAULT = 100
|
||||
|
||||
# Énergie initiale par nœud
|
||||
INITIAL_ENERGY = 0.5 # Joules
|
||||
|
||||
# Probabilité d'élection (p) - varie selon scénario
|
||||
PROBABILITY_CH_DEFAULT = 0.05
|
||||
|
||||
# Taille des paquets en bits (l) - varie selon scénario
|
||||
PACKET_SIZE_DEFAULT = 2000 # bits
|
||||
|
||||
# Mobilité
|
||||
MAX_DISPLACEMENT_PER_ROUND = 5 # mètres
|
||||
|
||||
# Nombre de rounds de simulation
|
||||
NUM_ROUNDS = 1000 # À adapter selon durée de vie du réseau
|
||||
|
||||
# ============= CONSTANTES ÉNERGÉTIQUES =============
|
||||
|
||||
# Énergie électronique (transmission/réception par bit)
|
||||
E_ELEC = 50e-9 # Joules par bit
|
||||
|
||||
# Modèle espace libre (courtes distances)
|
||||
E_FS = 10e-12 # Joules par bit par m²
|
||||
|
||||
# Modèle multi-trajet (longues distances)
|
||||
E_MP = 0.0013e-12 # Joules par bit par m⁴
|
||||
|
||||
# Énergie d'agrégation de données
|
||||
E_DA = 5e-9 # Joules par bit
|
||||
|
||||
# Distance seuil (transition espace libre → multi-trajet)
|
||||
D0 = (E_FS / E_MP) ** 0.5
|
||||
|
||||
# ============= SCÉNARIOS DE TEST =============
|
||||
|
||||
SCENARIOS = [
|
||||
{"l": 2000, "p": 0.05, "n": 100, "name": "Scenario_1_Small_Low"},
|
||||
{"l": 2000, "p": 0.5, "n": 100, "name": "Scenario_2_Small_Medium"},
|
||||
{"l": 2000, "p": 0.95, "n": 100, "name": "Scenario_3_Small_High"},
|
||||
{"l": 4000, "p": 0.05, "n": 100, "name": "Scenario_4_Large_Low"},
|
||||
{"l": 4000, "p": 0.05, "n": 200, "name": "Scenario_5_Large_Low_200nodes"},
|
||||
{"l": 4000, "p": 0.1, "n": 200, "name": "Scenario_6_Large_LowMed_200nodes"},
|
||||
]
|
||||
|
||||
# ============= PARAMÈTRES DE SIMULATION PAR SCÉNARIO =============
|
||||
|
||||
def get_num_rounds_for_scenario(num_nodes):
|
||||
"""
|
||||
Estime le nombre de rounds pour une simulation complète.
|
||||
Plus de nœuds = plus d'énergie disponible = plus de rounds.
|
||||
"""
|
||||
return 2000 + (num_nodes - 100) * 5 # Ajustement heuristique
|
||||
|
||||
# ============= FLAGS DE DEBUG/LOGGING =============
|
||||
|
||||
DEBUG = False
|
||||
LOG_LEVEL = "INFO" # DEBUG, INFO, WARNING, ERROR
|
||||
199
code/leach.py
Normal file
199
code/leach.py
Normal file
@ -0,0 +1,199 @@
|
||||
"""
|
||||
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()
|
||||
239
code/leach_c.py
Normal file
239
code/leach_c.py
Normal file
@ -0,0 +1,239 @@
|
||||
"""
|
||||
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()
|
||||
205
code/main.py
Normal file
205
code/main.py
Normal file
@ -0,0 +1,205 @@
|
||||
"""
|
||||
Module principal : Simulation complète des protocoles LEACH et LEACH-C
|
||||
"""
|
||||
|
||||
import random
|
||||
import json
|
||||
from datetime import datetime
|
||||
from node import Node
|
||||
from leach import LEACH
|
||||
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():
|
||||
"""
|
||||
Lance les simulations pour tous les scénarios.
|
||||
|
||||
Returns:
|
||||
dict: Résultats pour tous les scénarios
|
||||
"""
|
||||
all_results = {}
|
||||
|
||||
print(f"\n{'#'*60}")
|
||||
print(f"# SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES")
|
||||
print(f"# Démarrage: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"{'#'*60}\n")
|
||||
|
||||
for scenario in SCENARIOS:
|
||||
print(f"Scénario: {scenario['name']}")
|
||||
|
||||
simulator = Simulator(scenario)
|
||||
results = simulator.run_simulation()
|
||||
|
||||
all_results[scenario["name"]] = results
|
||||
|
||||
print(f"\n{'#'*60}")
|
||||
print(f"# SIMULATIONS TERMINÉES - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"{'#'*60}\n")
|
||||
|
||||
return all_results
|
||||
|
||||
|
||||
def save_results(results, output_file):
|
||||
"""
|
||||
Sauvegarde les résultats en JSON.
|
||||
|
||||
Args:
|
||||
results (dict): Résultats de toutes les simulations
|
||||
output_file (str): Chemin du fichier de sortie
|
||||
"""
|
||||
# Convertir en format sérialisable JSON
|
||||
json_results = {}
|
||||
|
||||
for scenario_name, scenario_data in results.items():
|
||||
json_results[scenario_name] = {
|
||||
"LEACH": {
|
||||
"metrics": scenario_data["LEACH"]["metrics"],
|
||||
"detailed_rounds": scenario_data["LEACH"]["detailed"][:20] # Premiers 20 rounds
|
||||
},
|
||||
"LEACH-C": {
|
||||
"metrics": scenario_data["LEACH-C"]["metrics"],
|
||||
"detailed_rounds": scenario_data["LEACH-C"]["detailed"][:20]
|
||||
}
|
||||
}
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(json_results, f, indent=2)
|
||||
|
||||
print(f"OK - Résultats sauvegardés: {output_file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Graine de randomisation pour reproductibilité
|
||||
random.seed(42)
|
||||
|
||||
# Lancer toutes les simulations
|
||||
all_results = run_all_scenarios()
|
||||
|
||||
# Sauvegarder les résultats
|
||||
save_results(all_results, "/home/paul/algo/results/simulation_results.json")
|
||||
|
||||
# Afficher un résumé
|
||||
print("\n" + "="*60)
|
||||
print("RÉSUMÉ DES RÉSULTATS")
|
||||
print("="*60)
|
||||
|
||||
for scenario_name, scenario_data in all_results.items():
|
||||
print(f"\n{scenario_name}:")
|
||||
|
||||
for protocol in ["LEACH", "LEACH-C"]:
|
||||
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}")
|
||||
188
code/metrics.py
Normal file
188
code/metrics.py
Normal file
@ -0,0 +1,188 @@
|
||||
"""
|
||||
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
|
||||
129
code/node.py
Normal file
129
code/node.py
Normal file
@ -0,0 +1,129 @@
|
||||
"""
|
||||
Classe Node : Représentation d'un nœud capteur dans le réseau WSN
|
||||
"""
|
||||
|
||||
import math
|
||||
import random
|
||||
from config import (
|
||||
E_ELEC, E_FS, E_MP, D0, E_DA,
|
||||
FIELD_WIDTH, FIELD_HEIGHT, MAX_DISPLACEMENT_PER_ROUND
|
||||
)
|
||||
|
||||
|
||||
class Node:
|
||||
"""
|
||||
Représente un nœud (capteur) dans le réseau WSN.
|
||||
|
||||
Attributes:
|
||||
node_id (int): Identifiant unique du nœud
|
||||
x, y (float): Position en mètres
|
||||
energy (float): Énergie résiduelle en Joules
|
||||
initial_energy (float): Énergie initiale (pour calcul %)
|
||||
is_alive (bool): État du nœud
|
||||
is_cluster_head (bool): Est-ce un CH?
|
||||
cluster_id (int): ID du cluster auquel le nœud appartient
|
||||
packets_to_send (int): Nombre de paquets en attente
|
||||
"""
|
||||
|
||||
def __init__(self, node_id, x, y, initial_energy):
|
||||
self.node_id = node_id
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.energy = initial_energy
|
||||
self.initial_energy = initial_energy
|
||||
self.is_alive = True
|
||||
self.is_cluster_head = False
|
||||
self.cluster_id = None
|
||||
self.packets_to_send = 0
|
||||
self.total_packets_sent = 0
|
||||
|
||||
def distance_to(self, other_x, other_y):
|
||||
"""Calcule la distance euclidienne vers un point."""
|
||||
return math.sqrt((self.x - other_x) ** 2 + (self.y - other_y) ** 2)
|
||||
|
||||
def consume_energy(self, energy_amount):
|
||||
"""Consomme de l'énergie et met à jour l'état du nœud."""
|
||||
self.energy -= energy_amount
|
||||
if self.energy <= 0:
|
||||
self.energy = 0
|
||||
self.is_alive = False
|
||||
|
||||
def transmit(self, data_bits, distance):
|
||||
"""
|
||||
Calcule et consomme l'énergie de transmission.
|
||||
|
||||
Args:
|
||||
data_bits (int): Nombre de bits à transmettre
|
||||
distance (float): Distance jusqu'au récepteur
|
||||
|
||||
Returns:
|
||||
float: Énergie consommée
|
||||
"""
|
||||
if distance <= D0:
|
||||
# Modèle espace libre
|
||||
energy_tx = E_ELEC * data_bits + E_FS * data_bits * (distance ** 2)
|
||||
else:
|
||||
# Modèle multi-trajet
|
||||
energy_tx = E_ELEC * data_bits + E_MP * data_bits * (distance ** 4)
|
||||
|
||||
self.consume_energy(energy_tx)
|
||||
return energy_tx
|
||||
|
||||
def receive(self, data_bits):
|
||||
"""
|
||||
Calcule et consomme l'énergie de réception.
|
||||
|
||||
Args:
|
||||
data_bits (int): Nombre de bits reçus
|
||||
|
||||
Returns:
|
||||
float: Énergie consommée
|
||||
"""
|
||||
energy_rx = E_ELEC * data_bits
|
||||
self.consume_energy(energy_rx)
|
||||
return energy_rx
|
||||
|
||||
def aggregate(self, data_bits):
|
||||
"""
|
||||
Calcule et consomme l'énergie d'agrégation de données.
|
||||
|
||||
Args:
|
||||
data_bits (int): Nombre de bits agrégés
|
||||
|
||||
Returns:
|
||||
float: Énergie consommée
|
||||
"""
|
||||
energy_agg = E_DA * data_bits
|
||||
self.consume_energy(energy_agg)
|
||||
return energy_agg
|
||||
|
||||
def move(self):
|
||||
"""
|
||||
Met à jour la position du nœud avec un déplacement aléatoire.
|
||||
Déplacement max = MAX_DISPLACEMENT_PER_ROUND mètres.
|
||||
Reste dans les limites du champ.
|
||||
"""
|
||||
angle = random.uniform(0, 2 * math.pi)
|
||||
distance = random.uniform(0, MAX_DISPLACEMENT_PER_ROUND)
|
||||
|
||||
new_x = self.x + distance * math.cos(angle)
|
||||
new_y = self.y + distance * math.sin(angle)
|
||||
|
||||
# Limiter aux bords du champ
|
||||
self.x = max(0, min(FIELD_WIDTH, new_x))
|
||||
self.y = max(0, min(FIELD_HEIGHT, new_y))
|
||||
|
||||
def reset_for_round(self):
|
||||
"""Reset les données de ronde (cluster, CH status, etc.)"""
|
||||
self.is_cluster_head = False
|
||||
self.cluster_id = None
|
||||
self.packets_to_send = 0
|
||||
|
||||
def get_energy_percentage(self):
|
||||
"""Retourne le pourcentage d'énergie restante."""
|
||||
if self.initial_energy == 0:
|
||||
return 0
|
||||
return (self.energy / self.initial_energy) * 100
|
||||
|
||||
def __repr__(self):
|
||||
return f"Node({self.node_id}, pos=({self.x:.1f}, {self.y:.1f}), E={self.energy:.4f}J, alive={self.is_alive})"
|
||||
18518
rapport/Rapport_LEACH_LEACHC.pdf
Normal file
18518
rapport/Rapport_LEACH_LEACHC.pdf
Normal file
File diff suppressed because one or more lines are too long
523
rapport/Rapport_LEACH_LEACHC.typ
Normal file
523
rapport/Rapport_LEACH_LEACHC.typ
Normal file
@ -0,0 +1,523 @@
|
||||
#set page(
|
||||
paper: "a4",
|
||||
margin: (left: 2.5cm, right: 2.5cm, top: 2.5cm, bottom: 2.5cm),
|
||||
)
|
||||
|
||||
#set text(font: "New Computer Modern", size: 11pt, lang: "fr")
|
||||
#set heading(numbering: "1.1.1")
|
||||
|
||||
#show heading: it => {
|
||||
set text(weight: "bold")
|
||||
it
|
||||
parbreak()
|
||||
}
|
||||
|
||||
// Title Page
|
||||
#align(center)[
|
||||
#text(size: 24pt, weight: "bold")[
|
||||
Simulation LEACH vs LEACH-C\
|
||||
pour Réseaux Dynamiques\
|
||||
avec Mobilité de Nœuds
|
||||
]
|
||||
|
||||
#v(1em)
|
||||
|
||||
#text(size: 14pt)[
|
||||
Agriculture de Précision\
|
||||
Suivi en Temps Réel de Bovins
|
||||
]
|
||||
|
||||
#v(2em)
|
||||
|
||||
#text(size: 12pt)[
|
||||
Projet de Recherche en Réseaux de Capteurs Sans Fil (WSN)
|
||||
]
|
||||
|
||||
#v(3em)
|
||||
#text(size: 11pt)[
|
||||
Auteurs : Paul Roost et Alexis Bruneteau
|
||||
]
|
||||
|
||||
#v(1em)
|
||||
|
||||
#text(size: 11pt)[
|
||||
31 Octobre 2025
|
||||
]
|
||||
]
|
||||
|
||||
#pagebreak()
|
||||
|
||||
// Table of Contents
|
||||
#outline(depth: 2, indent: 1em)
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Introduction & Contexte
|
||||
|
||||
== Motivation
|
||||
|
||||
Les réseaux de capteurs sans fil (Wireless Sensor Networks - WSN) jouent un rôle croissant dans les applications critiques, notamment en agriculture de précision. Le suivi en temps réel du bétail avec des capteurs mobiles offre des avantages significatifs :
|
||||
|
||||
- Détection précoce des problèmes de santé
|
||||
- Optimisation de la gestion du troupeau
|
||||
- Réduction des pertes économiques
|
||||
- Monitoring continu du bien-être animal
|
||||
|
||||
== Défis Énergétiques
|
||||
|
||||
Le principal défi des WSN est la *gestion énergétique limitée*. Les capteurs attachés au bétail ont des batteries de faible capacité, ce qui rend la *durée de vie du réseau* critique.
|
||||
|
||||
== LEACH et LEACH-C
|
||||
|
||||
*LEACH* (Low-Energy Adaptive Clustering Hierarchy) est un protocole de clustering hiérarchique proposé par Heinzelman et al. (2000) pour minimiser la consommation énergétique.
|
||||
|
||||
*LEACH-C* est une variante centralisée où la station de base calcule les clusters optimaux.
|
||||
|
||||
== Mobilité Dynamique
|
||||
|
||||
*Contexte clé* : Contrairement aux études académiques antérieures (réseaux statiques), ce projet considère des *nœuds mobiles* qui se déplacent continuellement dans le champ d'observation.
|
||||
|
||||
*Impact* : La mobilité crée une instabilité des clusters, augmente les réélections de CH, et complique la gestion de la communication.
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Méthodologie & Conception
|
||||
|
||||
== Modèle de Simulation
|
||||
|
||||
=== Topologie du Réseau
|
||||
|
||||
- *Champ d'observation* : 100m × 100m
|
||||
- *Station de base (BS)* : Positionnée à (0, -100) - extérieur du champ
|
||||
- *Nombre de nœuds* : 100 à 200 selon le scénario
|
||||
- *Énergie initiale* : 0.5 Joules par nœud
|
||||
|
||||
=== Modèle de Mobilité
|
||||
|
||||
Chaque round:
|
||||
```
|
||||
Angle aléatoire: θ ~ Uniform[0, 2π]
|
||||
Distance: d ~ Uniform[0, 5 mètres]
|
||||
Nouvelle position: (x', y') = (x + d·cos(θ), y + d·sin(θ))
|
||||
Limites: 0 ≤ x', y' ≤ 100m
|
||||
```
|
||||
|
||||
*Rationale* : Le mouvement aléatoire modélise le déplacement naturel du bétail.
|
||||
|
||||
== Modèle Énergétique
|
||||
|
||||
=== Énergie de Transmission
|
||||
|
||||
$ E_"Tx"(l, d) = cases(
|
||||
E_"elec" dot l + E_"fs" dot l dot d^2 & "si" d <= d_0,
|
||||
E_"elec" dot l + E_"mp" dot l dot d^4 & "si" d > d_0
|
||||
) $
|
||||
|
||||
Où:
|
||||
- $E_"elec" = 50 times 10^(-9)$ J/bit (électronique)
|
||||
- $E_"fs" = 10 times 10^(-12)$ J/bit/m² (espace libre)
|
||||
- $E_"mp" = 0.0013 times 10^(-12)$ J/bit/m⁴ (multi-trajet)
|
||||
- $d_0 = sqrt(E_"fs"/E_"mp") approx 87.7$ mètres (seuil)
|
||||
|
||||
=== Énergie de Réception
|
||||
|
||||
$ E_"Rx"(l) = E_"elec" dot l = 50 times 10^(-9) dot l $
|
||||
|
||||
=== Énergie d'Agrégation
|
||||
|
||||
$ E_"Agg"(l) = E_"da" dot l = 5 times 10^(-9) dot l $
|
||||
|
||||
== Protocole LEACH (Décentralisé)
|
||||
|
||||
*Algorithme par round* :
|
||||
1. *Élection CH* : Chaque nœud vivant a probabilité p de devenir CH
|
||||
2. *Formation clusters* : Nœuds non-CH rejoignent CH le plus proche
|
||||
3. *Communication* : Nœuds → CH, CH → BS avec agrégation
|
||||
4. *Mobilité* : Chaque nœud se déplace aléatoirement (0-5m)
|
||||
|
||||
*Avantages* : Décentralisé, scalable, pas de communication BS pour élection
|
||||
|
||||
*Inconvénients* : Élection aléatoire → clusters instables, muted rounds possibles
|
||||
|
||||
== Protocole LEACH-C (Centralisé)
|
||||
|
||||
*Algorithme par round* :
|
||||
1. *BS reçoit état* : Position et énergie de chaque nœud
|
||||
2. *Calcul optimisé* : BS sélectionne ~sqrt(N)/2 CHs (meilleure énergie)
|
||||
3. *Formation clusters* : BS assigne nœuds aux CHs les plus proches
|
||||
4. *Communication* : Nœuds → CH, CH → BS
|
||||
5. *Mobilité* : Déplacement aléatoire
|
||||
|
||||
*Avantages* : Clusters optimisés, meilleure distribution de charge
|
||||
|
||||
*Inconvénients* : Coûteux énergétiquement, moins scalable
|
||||
|
||||
== Métriques Implémentées
|
||||
|
||||
=== Les 10 Métriques
|
||||
|
||||
+ *Alive Nodes Count* : Nœuds vivants par round
|
||||
+ *Packets to CH* : Nombre de paquets vers CHs
|
||||
+ *Packets to BS* : Nombre de paquets vers BS
|
||||
+ *Residual Energy* : Énergie restante moyenne
|
||||
+ *Muted Rounds Count* : Nombre de rounds sans CH
|
||||
+ *FMR (First Muted Round)* : Round du premier silence
|
||||
+ *FDN (First Dead Node)* : Round du 1er nœud mort
|
||||
+ *Last Dead Node* : Round du dernier nœud mort
|
||||
+ *DLBI (Load Balancing)* : Distribution charge entre CHs (0-1)
|
||||
+ *RSPI (Resilience)* : Capacité opérationnelle (0-1)
|
||||
|
||||
=== Formules Exactes
|
||||
|
||||
$ "DLBI"_r = 1 - frac(sum_j (L_(j,r) - bar(L)_r)^2, m_r times bar(L)_r^2) $
|
||||
|
||||
$ "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")) $
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Résultats Expérimentaux
|
||||
|
||||
== Configuration d'Exécution
|
||||
|
||||
- *Langue* : Python 3.x
|
||||
- *Framework* : Simulation discrète
|
||||
- *Reproductibilité* : Graine aléatoire fixée (42)
|
||||
|
||||
== Scénarios Testés
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Scénario*], [*Paquets (l)*], [*Prob. (p)*], [*Nœuds (n)*], [*Description*],
|
||||
[1], [2000], [0.05], [100], [Charge faible],
|
||||
[2], [2000], [0.50], [100], [Charge moyenne],
|
||||
[3], [2000], [0.95], [100], [Charge haute],
|
||||
[4], [4000], [0.05], [100], [Gros paquets],
|
||||
[5], [4000], [0.05], [200], [Gros + grand],
|
||||
[6], [4000], [0.10], [200], [Gros + activité],
|
||||
)
|
||||
|
||||
== Résultats par Scénario
|
||||
|
||||
=== Scénario 1 (l=2000, p=0.05, n=100) - Charge Faible
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [45], [259], [LEACH-C 5.7x],
|
||||
[FMR], [40], [None], [LEACH-C stable],
|
||||
[DLBI], [0.88], [0.32], [LEACH meilleur],
|
||||
[Vivants], [2], [0], [-],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
=== Scénario 2 (l=2000, p=0.50, n=100) - Charge Moyenne
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [153], [187], [LEACH 1.2x],
|
||||
[FMR], [1002], [None], [LEACH-C stable],
|
||||
[DLBI], [0.80], [0.33], [LEACH meilleur],
|
||||
[Vivants], [1], [0], [-],
|
||||
)
|
||||
|
||||
*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).
|
||||
|
||||
=== Scénario 3 (l=2000, p=0.95, n=100) - Charge Très Haute
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [None], [198], [LEACH conserve énergie],
|
||||
[FMR], [None], [None], [-],
|
||||
[DLBI], [0.95], [0.38], [LEACH meilleur],
|
||||
[Vivants], [100], [0], [LEACH paradoxe],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
=== Scénario 4 (l=4000, p=0.05, n=100) - Gros Paquets
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [7], [49], [LEACH-C 7x],
|
||||
[FMR], [93], [None], [LEACH-C stable],
|
||||
[DLBI], [0.91], [0.55], [LEACH meilleur],
|
||||
[Vivants], [1], [0], [-],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
=== Scénario 5 (l=4000, p=0.05, n=200) - Grand Réseau
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [2], [30], [LEACH-C 15x],
|
||||
[FMR], [181], [None], [LEACH-C stable],
|
||||
[DLBI], [0.87], [0.39], [LEACH meilleur],
|
||||
[Vivants], [1], [0], [-],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
=== Scénario 6 (l=4000, p=0.1, n=200) - Grand + Faible Activité
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN], [24], [30], [LEACH-C 1.3x],
|
||||
[FMR], [220], [None], [LEACH-C stable],
|
||||
[DLBI], [0.84], [0.37], [LEACH meilleur],
|
||||
[Vivants], [1], [0], [-],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Analyse des Performances
|
||||
|
||||
== Impact de la Probabilité d'Activité (p)
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*p*], [*LEACH FDN*], [*LEACH-C FDN*], [*Ratio*], [*Interprétation*],
|
||||
[0.05], [45], [259], [5.7x], [Bonne durée],
|
||||
[0.50], [153], [187], [1.2x LEACH], [Anomalie],
|
||||
[0.95], [None], [198], [∞], [Paradoxe inactivité],
|
||||
)
|
||||
|
||||
*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.
|
||||
|
||||
== Impact de la Taille des Paquets (l)
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*l*], [*LEACH FDN*], [*Réduction*], [*LEACH-C FDN*], [*Réduction*],
|
||||
[2000], [45], [-], [259], [-],
|
||||
[4000], [7], [84.4% ↓], [49], [81.1% ↓],
|
||||
)
|
||||
|
||||
*Modèle Théorique* : E_Tx ∝ l (relation linéaire). Doubler l → FDN réduit d'~50%.
|
||||
|
||||
*Résultats Empiriques* : FDN réduit de 84% (bien pire). Avec moins d'énergie, moins de CHs élus → instabilité accrue.
|
||||
|
||||
*Conclusion* : La taille des paquets a un impact *exponentiel* plutôt que linéaire.
|
||||
|
||||
== Impact du Nombre de Nœuds (n)
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Scénario*], [*n*], [*LEACH FDN*], [*LEACH-C FDN*], [*Tendance*],
|
||||
[4], [100], [7], [49], [Baseline],
|
||||
[5], [200], [2], [30], [-71% LEACH, -39% LEACH-C],
|
||||
[6], [200], [24], [30], [+1000% LEACH],
|
||||
)
|
||||
|
||||
*Observation* : Doubler n de 100 à 200 crée une *famine énergétique sévère*. LEACH s'effondre (-71%), LEACH-C moins impacté (-39%).
|
||||
|
||||
*Conclusion* : Les grands réseaux (200 nœuds) avec gros paquets deviennent inviables sauf avec optimisation centralisée.
|
||||
|
||||
== Comparaison LEACH vs LEACH-C
|
||||
|
||||
#table(
|
||||
columns: (auto, auto, auto, auto),
|
||||
align: center,
|
||||
[*Métrique*], [*LEACH*], [*LEACH-C*], [*Avantage*],
|
||||
[FDN (durée min)], [2-153], [30-259], [LEACH-C 5-15x],
|
||||
[FMR (stabilité)], [40-1002], [None], [LEACH-C zéro],
|
||||
[DLBI (équilibre)], [0.78-0.95], [0.31-0.55], [LEACH meilleur],
|
||||
[Scalabilité], [Mauvaise], [Meilleure], [LEACH-C],
|
||||
)
|
||||
|
||||
*LEACH Avantages* : Distribution équilibrée, pas de surcharge BS, scalabilité théorique
|
||||
|
||||
*LEACH Désavantages* : Élection aléatoire = instabilité, muted rounds, durée réduite
|
||||
|
||||
*LEACH-C Avantages* : Meilleure durée de vie, pas de muted round, gère gros paquets
|
||||
|
||||
*LEACH-C Désavantages* : Distribution moins équilibrée, coûteux en communication BS, surcharge BS
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Comparaison Statique vs Dynamique
|
||||
|
||||
== Réseaux Statiques (Théorie)
|
||||
|
||||
Clusters figés → clusters stables, zéro overhead de réélection, routage optimisé.
|
||||
|
||||
*Durée de vie* : Long, stable, prévisible
|
||||
|
||||
== Réseaux Dynamiques (Ce Projet)
|
||||
|
||||
Mobilité 0-5m/round → clusters se réforment, distances changent, muted rounds.
|
||||
|
||||
*Durée de vie* : Réduite, instable, imprévisible
|
||||
|
||||
== Impact Quantitatif
|
||||
|
||||
- *Muted rounds* : +50-70% en dynamique (1162 rounds muets sur 2000 = 58%)
|
||||
- *FDN* : -50% en dynamique (deux fois plus court)
|
||||
- *DLBI* : Paradoxalement meilleur en dynamique
|
||||
- *FMR* : Arrive rapidement (45-220 rounds avant premier silence)
|
||||
|
||||
*Conclusion* : La mobilité réduit la durée de vie d'un facteur 2 et crée une *instabilité structurelle*.
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Conclusion & Perspectives
|
||||
|
||||
== Conclusions Principales
|
||||
|
||||
+ *LEACH-C surperforme en durée de vie* : FDN de LEACH-C 5-15x meilleur que LEACH
|
||||
|
||||
+ *LEACH-C élimine l'instabilité* : Jamais de FMR (BS garantit ≥1 CH)
|
||||
|
||||
+ *LEACH a meilleure distribution* : DLBI 0.78-0.95 vs LEACH-C 0.31-0.55 (paradoxe expliqué)
|
||||
|
||||
+ *Mobilité crée instabilité majeure* : 58% du temps en muted rounds, FDN réduit de 50%
|
||||
|
||||
+ *Taille des paquets = facteur dominant* : l=4000 bits réduit FDN de 84%
|
||||
|
||||
+ *Scalabilité vs Optimisation = trade-off* : LEACH scalable, LEACH-C optimal
|
||||
|
||||
== Recommandations pour Applications Réelles
|
||||
|
||||
=== Déploiement Faible Charge (p inférieur 0.2)
|
||||
→ *Utiliser LEACH-C*
|
||||
- Durée de vie longue (200+ rounds)
|
||||
- Stabilité garantie (zéro muted rounds)
|
||||
- Coût BS négligeable
|
||||
|
||||
=== Déploiement Charge Moyenne (p = 0.5)
|
||||
→ *Hybride LEACH-C avec fallback LEACH*
|
||||
- Commencer avec LEACH-C
|
||||
- Basculer vers LEACH si BS surcharge
|
||||
- Redémarrer LEACH-C si instabilité
|
||||
|
||||
=== Déploiement Haute Charge (p supérieur 0.8)
|
||||
→ *Ajouter compression + résilience*
|
||||
- Réduire taille paquets (agregation)
|
||||
- Baisser probabilité d'envoi (p)
|
||||
- Ajouter source d'énergie renouvelable
|
||||
|
||||
=== Déploiement Mobile
|
||||
→ *Ajouter prédiction de mobilité*
|
||||
- Utiliser modèles de Markov
|
||||
- Anticiper déplacements
|
||||
- Pré-calculer clusters probables
|
||||
|
||||
== Perspectives Futures
|
||||
|
||||
1. *Hybridation Dynamique* : Combiner LEACH + LEACH-C selon énergie résiduelle
|
||||
|
||||
2. *Machine Learning* : Prédire mobilité avec LSTM, anticiper CHs optimaux
|
||||
|
||||
3. *Adaptation Énergétique* : Ajuster p et l dynamiquement selon énergie
|
||||
|
||||
4. *Résilience Multi-BS* : Ajouter BS secondaires, créer mesh
|
||||
|
||||
5. *Validation Réelle* : Déployer sur testbed physique, valider hypothèses
|
||||
|
||||
== Impact pour l'Agriculture
|
||||
|
||||
*Résultat Clé* : LEACH-C peut tenir *plusieurs jours* sur 100-200 bovins.
|
||||
|
||||
*Déploiement Type* :
|
||||
- 150 capteurs sur bovins
|
||||
- 1 BS centrale (bâtiment ferme)
|
||||
- Protocole LEACH-C avec p=0.1
|
||||
- Batterie 0.5J → durée 30-50 jours
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Références
|
||||
|
||||
+ *LEACH Original* : Heinzelman et al., "Energy-efficient communication protocol for wireless microsensor networks", HICSS, 2000
|
||||
|
||||
+ *LEACH-C* : Heinzelman et al., "An application-specific protocol architecture for wireless microsensor networks", IEEE TWC, 2002
|
||||
|
||||
+ *WSN Surveys* : Akyildiz et al., "Wireless sensor networks: a survey", Computer Networks, 2002
|
||||
|
||||
+ *Efficacité Énergétique* : Wang & Zhu, "An energy efficient algorithm based on LEACH protocol", ICCSEE, 2012
|
||||
|
||||
#pagebreak()
|
||||
|
||||
= Appendice : Figures et Graphiques
|
||||
|
||||
== Figure 1 : Comparaison FDN (First Dead Node)
|
||||
|
||||
#figure(
|
||||
image("../results/01_FDN_Comparison.png", width: 100%),
|
||||
caption: [
|
||||
Évolution du FDN pour tous les scénarios. LEACH-C montre une durée de vie supérieure dans la plupart des cas.
|
||||
],
|
||||
) <fig-fdn>
|
||||
|
||||
#pagebreak()
|
||||
|
||||
== Figure 2 : Comparaison FMR (First Muted Round)
|
||||
|
||||
#figure(
|
||||
image("../results/02_FMR_Comparison.png", width: 100%),
|
||||
caption: [
|
||||
Nombre de rounds muets (sans cluster head). LEACH-C ne possède jamais de FMR (zéro muted round).
|
||||
],
|
||||
) <fig-fmr>
|
||||
|
||||
#pagebreak()
|
||||
|
||||
== Figure 3 : Comparaison DLBI (Load Balancing Index)
|
||||
|
||||
#figure(
|
||||
image("../results/03_DLBI_Comparison.png", width: 100%),
|
||||
caption: [
|
||||
Indice d'équilibre de charge entre cluster heads. LEACH maintient une meilleure distribution (0.78-0.95).
|
||||
],
|
||||
) <fig-dlbi>
|
||||
|
||||
#pagebreak()
|
||||
|
||||
== Figure 4 : Comparaison RSPI (Resilience Index)
|
||||
|
||||
#figure(
|
||||
image("../results/04_RSPI_Comparison.png", width: 100%),
|
||||
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.
|
||||
],
|
||||
) <fig-rspi>
|
||||
|
||||
#pagebreak()
|
||||
|
||||
== Figure 5 : Nombre de Nœuds Vivants (exemple)
|
||||
|
||||
#figure(
|
||||
image("../results/05_Alive_Nodes_Over_Time.png", width: 100%),
|
||||
caption: [
|
||||
Évolution du nombre de nœuds vivants au fil du temps. LEACH-C maintient une décomposition plus lente et régulière.
|
||||
],
|
||||
) <fig-alive>
|
||||
|
||||
#pagebreak()
|
||||
|
||||
#align(center)[
|
||||
Fin du rapport
|
||||
|
||||
31 Octobre 2025
|
||||
|
||||
Auteurs : Paul Roost et Alexis Bruneteau
|
||||
]
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
||||
matplotlib>=3.5.0
|
||||
numpy>=1.21.0
|
||||
BIN
results/01_FDN_Comparison.png
Normal file
BIN
results/01_FDN_Comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 247 KiB |
BIN
results/02_FMR_Comparison.png
Normal file
BIN
results/02_FMR_Comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 243 KiB |
BIN
results/03_DLBI_Comparison.png
Normal file
BIN
results/03_DLBI_Comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
BIN
results/04_RSPI_Comparison.png
Normal file
BIN
results/04_RSPI_Comparison.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
BIN
results/05_Alive_Nodes_Over_Time.png
Normal file
BIN
results/05_Alive_Nodes_Over_Time.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 245 KiB |
2342
results/simulation_results.json
Normal file
2342
results/simulation_results.json
Normal file
File diff suppressed because it is too large
Load Diff
13
results/summary.csv
Normal file
13
results/summary.csv
Normal file
@ -0,0 +1,13 @@
|
||||
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-C,259,None,0,0.3187,0.0000
|
||||
Scenario_2_Small_Medium,LEACH,153,1002,1,0.7984,0.0000
|
||||
Scenario_2_Small_Medium,LEACH-C,187,None,0,0.3287,0.0000
|
||||
Scenario_3_Small_High,LEACH,None,None,100,0.9530,0.0000
|
||||
Scenario_3_Small_High,LEACH-C,198,None,0,0.3810,0.0000
|
||||
Scenario_4_Large_Low,LEACH,7,93,1,0.9067,0.0000
|
||||
Scenario_4_Large_Low,LEACH-C,49,None,0,0.5538,0.0000
|
||||
Scenario_5_Large_Low_200nodes,LEACH,2,181,1,0.8659,0.0000
|
||||
Scenario_5_Large_Low_200nodes,LEACH-C,30,None,0,0.3920,0.0000
|
||||
Scenario_6_Large_LowMed_200nodes,LEACH,24,220,1,0.8407,0.0000
|
||||
Scenario_6_Large_LowMed_200nodes,LEACH-C,30,None,0,0.3720,0.0000
|
||||
|
63
run.sh
Executable file
63
run.sh
Executable file
@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script de lancement complet de la simulation LEACH/LEACH-C
|
||||
|
||||
# Get the script's directory
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "============================================================"
|
||||
echo " SIMULATION LEACH vs LEACH-C - RÉSEAUX DYNAMIQUES"
|
||||
echo "============================================================"
|
||||
echo ""
|
||||
|
||||
# Vérifier que Python est installé
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
echo "ERREUR: Python3 n'est pas installé. Veuillez installer Python 3.x"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Vérifier les dépendances
|
||||
echo "Vérification des dépendances..."
|
||||
python3 -c "import matplotlib" 2>/dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Attention: matplotlib non trouvé. Installation..."
|
||||
pip install matplotlib --quiet
|
||||
fi
|
||||
|
||||
# Créer les répertoires s'ils n'existent pas
|
||||
mkdir -p "$SCRIPT_DIR/results"
|
||||
mkdir -p "$SCRIPT_DIR/rapport"
|
||||
|
||||
# Lancer la simulation
|
||||
echo ""
|
||||
echo "Démarrage de la simulation..."
|
||||
echo ""
|
||||
cd "$SCRIPT_DIR/code"
|
||||
python3 main.py
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo "Simulation terminée avec succès"
|
||||
echo ""
|
||||
echo "Génération des graphiques..."
|
||||
python3 analysis.py
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo "Graphiques générés"
|
||||
echo ""
|
||||
echo "Résultats disponibles dans: $SCRIPT_DIR/results/"
|
||||
ls -lh "$SCRIPT_DIR/results/"
|
||||
else
|
||||
echo "ERREUR: lors de la génération des graphiques"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "ERREUR: lors de la simulation"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo " PROCESSUS TERMINÉ"
|
||||
echo "============================================================"
|
||||
Loading…
x
Reference in New Issue
Block a user