Compare commits

...

17 Commits

Author SHA1 Message Date
2dadd20da8 Update Report 2026-01-19 17:53:47 +01:00
12c11b0634 Supprimer Rapport_MOPSO_Surrogate.pdf 2026-01-19 17:53:22 +01:00
29b613753f Téléverser les fichiers vers "/" 2026-01-19 17:26:56 +01:00
a8323a2633 Téléverser les fichiers vers "/" 2026-01-19 17:24:57 +01:00
ce3b7cf527 Update Report 2026-01-19 16:05:18 +01:00
0347ef9fd1 Supprimer Rapport_MOPSO_Surrogate.pdf 2026-01-19 16:04:28 +01:00
KuMiShi
20d17eb69f Adding slides 2026-01-18 18:25:33 +01:00
ca254e97ac updating main.py 2026-01-18 17:29:46 +01:00
ac5cbbc690 final version of demo notebook 2026-01-18 17:23:22 +01:00
2d7841dc82 Rapport 2026-01-18 15:56:00 +01:00
4e97f4d1a1 adding plot of pareto 2026-01-18 15:44:21 +01:00
874813e29e updating mopso demo 2026-01-18 14:51:35 +01:00
0f0a4e540d updating particle.py 2026-01-18 14:50:48 +01:00
0dd6770457 updating mopso.py 2026-01-18 14:50:26 +01:00
KuMiShi
76cd66c00d Correction README 2026-01-18 14:20:58 +01:00
KuMiShi
9c72e8cdd5 Update README.md 2026-01-18 14:10:34 +01:00
KuMiShi
d1c2475d1b Adding Power constraints to simulation 2026-01-18 13:43:44 +01:00
8 changed files with 602 additions and 143 deletions

View File

@@ -1,17 +1,18 @@
# Mini Projet - Optimisation Métaheuristique # Mini Projet - Optimisation Métaheuristique
Ceci est le répertoire Git du projet d'optimisation métaheuristique du groupe 9 dont les membres sont AIT MOUSSA Amine, DAANOUNI Siham et DELAMOTTE Clément. Ceci est le répertoire Git du projet d'optimisation métaheuristique du groupe 9 dont les membres sont **AIT MOUSSA Amine, DAANOUNI Siham et DELAMOTTE Clément**.
Le sujet choisi est **l'optimisation du chargement des véhicules électriques** et l'algorithme mis en place est **Multiple Objectives Particle Swarm Optimization (MOPSO) + Surrogate**. La modélisation du problème se trouvera dans le rapport. Le sujet choisi est **l'optimisation du chargement des véhicules électriques** et l'algorithme mis en place est **Multiple Objectives Particle Swarm Optimization (MOPSO) + Surrogate**. La modélisation du problème se trouvera dans le rapport et les slides de présentation.
Pour les datasets, nous avons pris diverses sources pour concevoir notre propre jeu de données: Pour les datasets, nous avons pris diverses sources réalistes pour concevoir nos propres jeux de données afin de pouvoir récupérer des paramètres cruciaux:
- data/vehicle_capacity.csv: [Car Dataset (2025)](https://www.kaggle.com/datasets/abdulmalik1518/cars-datasets-2025/data) - data/vehicle_capacity.csv: [Car Dataset (2025)](https://www.kaggle.com/datasets/abdulmalik1518/cars-datasets-2025/data).
- data/elec_prices.csv: [RTE France (éco2mix)](https://www.rte-france.com/donnees-publications/eco2mix-donnees-temps-reel/donnees-marche), les données ont été récupérées manuellement sur l'hivers 2025 (S2-S5) et l'été 2025 (S29-S32) - data/elec_prices.csv: [RTE France (éco2mix)](https://www.rte-france.com/donnees-publications/eco2mix-donnees-temps-reel/donnees-marche), les données ont été récupérées manuellement sur l'hivers 2025 (S2-S5) et l'été 2025 (S29-S32).
- data/grid_capacity.txt: [RTE France (éco2mix)](https://www.rte-france.com/donnees-publications/eco2mix-donnees-temps-reel/donnees-marche), même procédé qu'au dessus.
## Installation ## Installation
Le projet a été concu à l'aide du *Python packet manager* ***UV***, il est préférable d'utiliser celui-ci pour ca facilité d'utilisation. **UV** peut être installé via le [site internet officiel](https://docs.astral.sh/uv/getting-started/installation/#installing-uv). Pour télécharger le projet vous pouvez simplement utiliser la commande `git clone https://gitea.galaxynoliro.fr/KuMiShi/Optim_Metaheuristique.git` ou récupérer le fichier `.zip` du projet et l'extraire.
POur télécharger le projet vous pouvez simplement utiliser la commande `git clone https://gitea.galaxynoliro.fr/KuMiShi/Optim_Metaheuristique.git` ou récupérer le fichier `.zip` du projet et l'extraire. Le projet a été concu à l'aide du ***Python packet manager UV***, il est préférable d'utiliser celui-ci pour sa facilité d'utilisation **sauf si vous vous contentez de regarder les résultats de notre notebook**. **UV** peut être installé via le [site internet officiel](https://docs.astral.sh/uv/getting-started/installation/#installing-uv) sur tout système d'exploitation.
**Linux:** **Linux:**
```bash ```bash
@@ -29,6 +30,11 @@ winget install --id=astral-sh.uv -e
``` ```
## Utilisation ## Utilisation
Vous pouvez utiliser le projet de deux manières:
1. Récupérer le notebook et suivre les cellules une à une avec les résultats pré-compiler dans le fichier.
2. Exécuter le projet complet à l'aide du code source et de **UV**
Pour charger le projet et l'executer sans problème, il faut d'abord configurer notre environnement d'execution de la manière suivante: Pour charger le projet et l'executer sans problème, il faut d'abord configurer notre environnement d'execution de la manière suivante:
```bash ```bash
@@ -38,8 +44,8 @@ uv venv
# Téléchargement des requirements du projet # Téléchargement des requirements du projet
uv pip sync uv.lock uv pip sync uv.lock
# Si uv.lock n'existe pas, vous pouvez le générer avec la commande suivante: # Si uv.lock ne fonctionne pas correctement ou n'existe pas, vous pouvez le générer avec la commande suivante à partir du .toml:
uv pip compile --upgrade pyproject.toml -o uv.lock uv pip compile --upgrade pyproject.toml -o uv.lock
``` ```
Enfin, vous pouvez executer n'importe quel script avec la commande `uv run main.py` (main.py pouvant etre remplacé par n'importe quel autre script python executable). Enfin, vous pouvez executer n'importe quel script avec la commande `uv run main.py`, sachant que `main.py` peut être remplacé par n'importe quel autre script python executable.

BIN
Rapport_MOPSO_Surrogate.pdf Normal file

Binary file not shown.

BIN
Slides_presentation.pdf Normal file

Binary file not shown.

9
data/grid_capacity.txt Normal file
View File

@@ -0,0 +1,9 @@
| Maximum | Minimum
---------------------------------------------------
Consumption (Winter)| 87 028 Mwh | 46 847 Mwh
(Summer)| 52 374 Mwh | 29 819 Mwh
---------------------------------------------------
Production (Winter)| 91 341 Mwh | 72 926 Mwh
(Summer)| 86 579 Mwh | 49 127 Mwh
Winter correspond to S2-S5 and Summer correspond to S29-S32 (same as prices)

237
main.py
View File

@@ -1,12 +1,11 @@
import time import time
import pandas as pd
import numpy as np import numpy as np
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import copy import copy
from mopso import MOPSO from mopso import MOPSO
from surrogate_handler import SurrogateHandler from surrogate_handler import SurrogateHandler
import pandas as pd
# --- EXTENDED CLASS (Inheritance) ---
class SmartMOPSO(MOPSO): class SmartMOPSO(MOPSO):
def __init__(self, model_type=None, **kwargs): def __init__(self, model_type=None, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -20,18 +19,7 @@ class SmartMOPSO(MOPSO):
for p in self.particles: for p in self.particles:
self.surrogate_handler.add_data(p.x, p.f_current[1]) self.surrogate_handler.add_data(p.x, p.f_current[1])
def iterate(self): def iterate(self, prediction_freq:int=10):
train_freq = 10 # Retrain every 10 iterations
# Check if retraining is needed
if self.use_surrogate and (self.t % train_freq == 0):
self.surrogate_handler.train()
# Determine if AI prediction should be used
use_ai = (self.use_surrogate and
self.surrogate_handler.is_trained and
self.t % train_freq != 0)
# Main loop (overriding original logic to manage control flow) # Main loop (overriding original logic to manage control flow)
for t in range(self.t): for t in range(self.t):
self.select_leader() self.select_leader()
@@ -42,32 +30,52 @@ class SmartMOPSO(MOPSO):
self.particles[i].update_position() self.particles[i].update_position()
self.particles[i].keep_boudaries(self.A_max) self.particles[i].keep_boudaries(self.A_max)
if use_ai: if (t % (prediction_freq) != 0) and self.use_surrogate:
# 1. Fast exact calculation (f1, f3) # Fast exact calculation (f1, f3)
f1 = self.particles[i].f1(self.prices) f1 = self.particles[i].f1(self.prices)
f3 = self.particles[i].f3() f3 = self.particles[i].f3()
# 2. Slow prediction (f2) via AI # Slow prediction (f2) by using Surrogate
f2_pred = self.surrogate_handler.predict(self.particles[i].x) f2_pred = self.surrogate_handler.predict(self.particles[i].x)
# 3. Inject scores without running the expensive 'updating_socs' # Inject scores without running the expensive 'updating_socs'
self.particles[i].f_current = [f1, f2_pred, f3] self.particles[i].f_current = [f1, f2_pred, f3]
else: else:
# Standard Calculation (Slow & Exact) # Standard Calculation (Slow and Exact)
self.particles[i].updating_socs(self.socs, self.capacities) self.particles[i].updating_socs(self.socs, self.capacities)
self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times) self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times)
# Capture data for AI training
if self.use_surrogate:
self.surrogate_handler.add_data(self.particles[i].x, self.particles[i].f_current[1])
self.particles[i].update_best() self.particles[i].update_best()
self.update_archive() self.update_archive()
# Run Classic MOPSO, collect data and run training for the model
def train_surrogate_model(self):
# Generation of data
for t in range(self.t):
self.select_leader()
for i in range(self.n):
# Movement
self.particles[i].update_velocity(self.leader.x, self.c1, self.c2, self.w)
self.particles[i].update_position()
self.particles[i].keep_boudaries(self.A_max)
# Standard Calculation (Slow and Exact)
self.particles[i].updating_socs(self.socs, self.capacities)
self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times)
# Capture data for AI training
self.surrogate_handler.add_data(self.particles[i].x, self.particles[i].f_current[1])
# End of dataset generation (based on classic MOPSO)
self.surrogate_handler.train()
def calculate_elec_prices(csv_file:str, sep:str=';'): def calculate_elec_prices(csv_file:str, sep:str=';'):
elec_df = pd.read_csv(filepath_or_buffer=csv_file, sep=sep) elec_df = pd.read_csv(filepath_or_buffer=csv_file, sep=sep, skipinitialspace=True)
# Mean of Winter and Summer of 2025 electric prices (Euros/MWh) # Mean of Winter and Summer of 2025 electric prices (Euros/MWh)
elec_mean = (elec_df['Winter 2025'].mean() + elec_df['Summer 2025'].mean())/2 elec_mean = (elec_df['Winter 2025'].mean() + elec_df['Summer 2025'].mean())/2
@@ -75,6 +83,10 @@ def calculate_elec_prices(csv_file:str, sep:str=';'):
# Standard variation of Winter and Summer of 2025 electric prices (Euros/MWh) # Standard variation of Winter and Summer of 2025 electric prices (Euros/MWh)
elec_std = (elec_df['Winter 2025'].std() + elec_df['Summer 2025'].std())/2 elec_std = (elec_df['Winter 2025'].std() + elec_df['Summer 2025'].std())/2
elec_mean = elec_mean / 1000
elec_std = elec_std / 1000
print(f'Electricity prices:\n - Mean: ${elec_mean}€/Mwh\n - Std: ${elec_std}€/Mwh') print(f'Electricity prices:\n - Mean: ${elec_mean}€/Mwh\n - Std: ${elec_std}€/Mwh')
return elec_mean, elec_std return elec_mean, elec_std
@@ -88,46 +100,41 @@ def generate_capacities(csv_file:str, nb_vehicles:int, seed:int=42, sep:str=';')
capacities = pd.Series(all_capacities).sample(n=nb_vehicles, random_state=seed) capacities = pd.Series(all_capacities).sample(n=nb_vehicles, random_state=seed)
print(f'Capacities of vehicles (kwh): ${capacities}') print(f'Capacities of vehicles (kwh): ${capacities}')
return capacities return capacities.tolist()
# --- EXECUTION FUNCTION --- def get_power_constants(nb_vehicles:int, nb_consumers:int=67000000):
def run_scenario(scenario_name, model_type=None): mean_consumption = (87028 + 46847 + 52374 + 29819)/4 # Mean of consumption in France in 2025 (estimate according to data/grid_capacity.txt)
elec_price_csv = 'data/elec_prices.csv' sim_ratio = nb_vehicles / nb_consumers # Ratio to reduce A_max of simulation to realistic restrictions
capacity_csv = 'data/vehicle_capacity.csv'
# Simulation parameters a_max = sim_ratio * mean_consumption
N = 20 # Number of vehicles x_max = a_max / nb_vehicles # For init, uniform charging/discharging for every vehicle
T = 30 # Number of iterations (for the particles) x_min = -x_max
W = 0.4 # Inertia (for exploration) return a_max, x_max, x_min
C1 = 0.3 # Individual trust
C2 = 0.2 # Social trust
ARC_SIZE = 10 # Archive size
P_MEAN, P_STD = calculate_elec_prices(elec_price_csv)
CAPACITIES = generate_capacities(capacity_csv, N)
NB_TICKS = 48
DELTA = 60
A_MAX = 0 def run_scenario(scenario_name, capacities:list, price_mean:float, price_std:float, model_type=None, n:int=20, t:int=30, w:float=0.4, c1:float=0.3, c2:float=0.2, archive_size:int=10, nb_vehicles:int=10, delta_t:int=60, nb_of_ticks:int=48):
X_MAX = 0 A_MAX, X_MAX, X_MIN = get_power_constants(nb_vehicles=nb_vehicles)
X_MIN = 0
print(f"\n--- Launching Scenario: {scenario_name} ---") print(f"\n--- Launching Scenario: {scenario_name} ---")
start_time = time.time()
# Simulation parameters # Simulation parameters
params = { params = {
'A_max': 500, 'price_mean': 0.15, 'price_std': 0.05, 'A_max': A_MAX, 'price_mean': price_mean, 'price_std': price_std,
'capacities': [50]*10, 'n': 20, 't': 50, 'capacities': capacities, 'n': n, 't': t,
'w': 0.4, 'c1': 2.0, 'c2': 2.0, 'w': w, 'c1': c1, 'c2': c2,
'nb_vehicles': 10, 'delta_t': 60, 'nb_of_ticks': 72 'nb_vehicles': nb_vehicles, 'delta_t': delta_t, 'nb_of_ticks': nb_of_ticks,
'x_min':X_MIN, 'x_max':X_MAX
} }
# Instantiate extended class # Instantiate extended class
optimizer = SmartMOPSO(model_type=model_type, **params) optimizer = SmartMOPSO(model_type=model_type, **params)
if(model_type is not None):
optimizer.train_surrogate_model()
start_time = time.time()
# Run simulation # Run simulation
optimizer.iterate() optimizer.iterate()
@@ -140,27 +147,137 @@ def run_scenario(scenario_name, model_type=None):
print(f"Finished in {duration:.2f} seconds.") print(f"Finished in {duration:.2f} seconds.")
print(f"Best f2 found: {best_f2:.4f}") print(f"Best f2 found: {best_f2:.4f}")
return duration, best_f2 return duration, best_f2, optimizer.archive
# --- MAIN ---
if __name__ == "__main__":
results = {}
# CSV files
elec_price_csv = 'data/elec_prices.csv'
capacity_csv = 'data/vehicle_capacity.csv'
# Global Simulation parameters
T = 30 # Number of iterations (for the particles)
W = 0.4 # Inertia (for exploration)
C1 = 0.3 # Individual trust
C2 = 0.2 # Social trust
ARC_SIZE = 10 # Archive size
nb_vehicle = 20
P_MEAN, P_STD = calculate_elec_prices(elec_price_csv)
CAPACITIES = generate_capacities(capacity_csv, nb_vehicles=nb_vehicle)
NB_TICKS = 48
DELTA = 60
results = {
'MOPSO':[],
'MLP': [],
'RF': []
}
nb_particles = [20,50,100,500]
for k in range(len(nb_particles)):
# 1. Without Surrogate (Baseline) # 1. Without Surrogate (Baseline)
d1, f1_score = run_scenario("No AI", model_type=None) d1, f1_score, _ = run_scenario(
results['No-AI'] = (d1, f1_score) "Only MOPSO",
capacities=CAPACITIES,
price_mean=P_MEAN,
price_std=P_STD,
nb_vehicles=nb_vehicle, # Important pour la cohérence
model_type=None,
n=nb_particles[k]
)
results['MOPSO'].append((d1, f1_score))
# 2. With MLP # 2. With MLP
d2, f2_score = run_scenario("With MLP", model_type='mlp') d2, f2_score, _ = run_scenario(
results['MLP'] = (d2, f2_score) "With MLP",
capacities=CAPACITIES,
price_mean=P_MEAN,
price_std=P_STD,
nb_vehicles=nb_vehicle,
model_type='mlp',
n=nb_particles[k]
)
results['MLP'].append((d2, f2_score))
# 3. With Random Forest # 3. With Random Forest
d3, f3_score = run_scenario("With Random Forest", model_type='rf') d3, f3_score, _ = run_scenario(
results['RF'] = (d3, f3_score) "With Random Forest",
capacities=CAPACITIES,
price_mean=P_MEAN,
price_std=P_STD,
nb_vehicles=nb_vehicle,
model_type='rf',
n=nb_particles[k]
)
results['RF'].append((d3, f3_score))
# --- DISPLAY RESULTS --- # --- DISPLAY RESULTS ---
print("\n=== SUMMARY ===") print("\n=== SUMMARY ===")
print(f"{'Mode':<15} | {'Time (s)':<10} | {'Best f2':<10}") print(f"{'Mode':<15} | {'Time (s)':<10} | {'Best f2':<10}")
print("-" * 45) print("-" * 45)
for k, v in results.items(): for k, v in results.items():
print(f"{k:<15} | {v[0]:<10.2f} | {v[1]:<10.4f}") for i in range(len(nb_particles)):
print(f"{k:<15}_{nb_particles[i]:<15} | {v[i][0]:<10.2f} | {v[i][1]:<10.4f}")
import matplotlib.pyplot as plt
import numpy as np
def plot_time_benchmark(nb_particles_list, results_dict):
t_mopso = [item[0] for item in results_dict['MOPSO']]
t_mlp = [item[0] for item in results_dict['MLP']]
t_rf = [item[0] for item in results_dict['RF']]
plt.figure(figsize=(10, 6))
plt.plot(nb_particles_list, t_mopso, 'o-', label='Sans IA (MOPSO)', color='#1f77b4', linewidth=2)
plt.plot(nb_particles_list, t_mlp, 's--', label='Avec MLP', color='#ff7f0e', linewidth=2)
plt.plot(nb_particles_list, t_rf, '^-.', label='Avec Random Forest', color='#2ca02c', linewidth=2)
plt.title("Temps d'exécution selon le nombre de particules", fontsize=14, fontweight='bold')
plt.xlabel("Nombre de Particules", fontsize=12)
plt.ylabel("Temps (s)", fontsize=12)
plt.grid(True, linestyle=':', alpha=0.7)
plt.legend(fontsize=11)
plt.tight_layout()
plt.show()
plot_time_benchmark(nb_particles, results)
import matplotlib.pyplot as plt
def plot_f2_benchmark(nb_particles_list, results_dict):
s_mopso = [item[1] for item in results_dict['MOPSO']]
s_mlp = [item[1] for item in results_dict['MLP']]
s_rf = [item[1] for item in results_dict['RF']]
plt.figure(figsize=(10, 6))
plt.plot(nb_particles_list, s_mopso, 'o-', label='Sans IA (MOPSO)', color='#1f77b4', linewidth=2)
plt.plot(nb_particles_list, s_mlp, 's--', label='Avec MLP', color='#ff7f0e', linewidth=2)
plt.plot(nb_particles_list, s_rf, '^-.', label='Avec Random Forest', color='#2ca02c', linewidth=2)
plt.title("Meilleur Score F2 (Convergence) selon le nombre de particules", fontsize=14, fontweight='bold')
plt.xlabel("Nombre de Particules (log scale)", fontsize=12)
plt.ylabel("Meilleur F2 Score", fontsize=12)
plt.grid(True, linestyle=':', alpha=0.7)
plt.legend(fontsize=11)
plt.xscale('log')
plt.tight_layout()
plt.show()
plot_f2_benchmark(nb_particles, results)

View File

@@ -1,5 +1,5 @@
import random as rd import random as rd
from .particle import Particle from particle import Particle
import copy import copy
class MOPSO(): class MOPSO():
@@ -17,7 +17,7 @@ class MOPSO():
# Initialisation of particle's global parameters # Initialisation of particle's global parameters
self.A_max = A_max # Network's power limit self.A_max = A_max # Network's power limit
self.socs, self.socs_req = self.generate_state_of_charges(nb_vehicles,nb_of_ticks) self.socs, self.socs_req = self.generate_state_of_charges(nb_vehicles,nb_of_ticks)
self.times = self.generate_times(nb_vehicles, nb_of_ticks, delta_t) self.times = self.generate_times(nb_vehicles, nb_of_ticks)
self.prices = self.generates_prices(nb_of_ticks,price_mean,price_std) #TODO: Use RTE France prices for random prices generation according to number of ticks self.prices = self.generates_prices(nb_of_ticks,price_mean,price_std) #TODO: Use RTE France prices for random prices generation according to number of ticks
self.capacities = capacities self.capacities = capacities
@@ -83,7 +83,7 @@ class MOPSO():
def generates_prices(self,nb_of_ticks:int, mean:float, std:float): def generates_prices(self,nb_of_ticks:int, mean:float, std:float):
prices = [] prices = []
for _ in range(nb_of_ticks): for _ in range(nb_of_ticks):
variation = rd.randrange(-(std*10), (std * 10) +1, 1) / 10 # Random float variation variation = rd.uniform(-std, std) # Random float variation
prices.append(mean + variation) prices.append(mean + variation)
return prices return prices

File diff suppressed because one or more lines are too long

View File

@@ -65,14 +65,12 @@ class Particle():
self.x[tick][i] = self.x[tick][i] * 0.9 self.x[tick][i] = self.x[tick][i] * 0.9
current_power = self.get_current_grid_stress(tick) current_power = self.get_current_grid_stress(tick)
def generate_position(self): def generate_position(self):
pos = [] pos = []
for _ in range(self.nb_of_ticks): for _ in range(self.nb_of_ticks):
x_tick = [] x_tick = []
for _ in range(self.nb_vehicles): for _ in range(self.nb_vehicles):
x_tick.append(rd.randrange(self.x_min, self.x_max +1, 1)) x_tick.append(rd.uniform(self.x_min, self.x_max))
pos.append(x_tick) pos.append(x_tick)
return pos return pos
@@ -83,7 +81,8 @@ class Particle():
for _ in range(self.nb_of_ticks): for _ in range(self.nb_of_ticks):
v_tick = [] v_tick = []
for _ in range(self.nb_vehicles): for _ in range(self.nb_vehicles):
v_tick.append(rd.randrange(-vel_coeff, vel_coeff +1, 1) * self.alpha) # v_tick.append(rd.randrange(-vel_coeff, vel_coeff +1, 1) * self.alpha)
v_tick.append(rd.uniform(-vel_coeff, vel_coeff) * self.alpha)
vel.append(v_tick) vel.append(v_tick)
return vel return vel