Compare commits

24 Commits
Author SHA1 Message Date
SihamDaanouni 2dadd20da8 Update Report 2026-01-19 17:53:47 +01:00
SihamDaanouni 12c11b0634 Supprimer Rapport_MOPSO_Surrogate.pdf 2026-01-19 17:53:22 +01:00
Amimine 29b613753f Téléverser les fichiers vers "/" 2026-01-19 17:26:56 +01:00
Amimine a8323a2633 Téléverser les fichiers vers "/" 2026-01-19 17:24:57 +01:00
SihamDaanouni ce3b7cf527 Update Report 2026-01-19 16:05:18 +01:00
SihamDaanouni 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
Amimine ca254e97ac updating main.py 2026-01-18 17:29:46 +01:00
Amimine ac5cbbc690 final version of demo notebook 2026-01-18 17:23:22 +01:00
SihamDaanouni 2d7841dc82 Rapport 2026-01-18 15:56:00 +01:00
Amimine 4e97f4d1a1 adding plot of pareto 2026-01-18 15:44:21 +01:00
Amimine 874813e29e updating mopso demo 2026-01-18 14:51:35 +01:00
Amimine 0f0a4e540d updating particle.py 2026-01-18 14:50:48 +01:00
Amimine 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
KuMiShi b3f51d8363 Merge modifications 2026-01-18 13:05:42 +01:00
KuMiShi 698d1ff7dd Main Modifications 2026-01-18 12:27:51 +01:00
Amimine c71fde5088 first version of demo notebook 2026-01-18 12:14:58 +01:00
SihamDaanouni 05298908e5 Main update with different scenario 2026-01-17 23:20:15 +01:00
SihamDaanouni da82ee9185 update without blocking errors 2026-01-17 23:19:50 +01:00
SihamDaanouni 345ac1166c update without blocking errors 2026-01-17 23:19:22 +01:00
Amimine 41c3134c9f adding the surrogate handler 2026-01-17 23:16:20 +01:00
13 changed files with 1003 additions and 63 deletions

No files matched your search

-7
View File
@@ -1,10 +1,3 @@
# Scripts
main.py
# UV Environment # UV Environment
.python-version .python-version
.venv .venv
# Datasets
dataset.py
data/capacity.csv
+16 -8
View File
@@ -1,15 +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.
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
@@ -27,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
@@ -36,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.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -14,7 +14,7 @@ Winter 2025; Summer 2025
12.54; 76.77 12.54; 76.77
0.4; 63.01 0.4; 63.01
60.01; 54.1 60.01; 54.1
1158; 69.52 115.8; 69.52
93.49; 94.16 93.49; 94.16
71.25; 30.5 71.25; 30.5
79.76; 46.2 79.76; 46.2
1 Winter 2025 Summer 2025
14 12.54 76.77
15 0.4 63.01
16 60.01 54.1
17 1158 115.8 69.52
18 93.49 94.16
19 71.25 30.5
20 79.76 46.2
+9
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)
+281 -4
View File
@@ -1,6 +1,283 @@
def main(): import time
print("Hello from optim-meta!") import numpy as np
import matplotlib.pyplot as plt
import copy
from mopso import MOPSO
from surrogate_handler import SurrogateHandler
import pandas as pd
class SmartMOPSO(MOPSO):
def __init__(self, model_type=None, **kwargs):
super().__init__(**kwargs)
# Initialize Surrogate Handler if model_type is provided
self.use_surrogate = (model_type is not None)
if self.use_surrogate:
self.surrogate_handler = SurrogateHandler(model_type)
# Pre-fill with initial particle data
for p in self.particles:
self.surrogate_handler.add_data(p.x, p.f_current[1])
def iterate(self, prediction_freq:int=10):
# Main loop (overriding original logic to manage control flow)
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)
if (t % (prediction_freq) != 0) and self.use_surrogate:
# Fast exact calculation (f1, f3)
f1 = self.particles[i].f1(self.prices)
f3 = self.particles[i].f3()
# Slow prediction (f2) by using Surrogate
f2_pred = self.surrogate_handler.predict(self.particles[i].x)
# Inject scores without running the expensive 'updating_socs'
self.particles[i].f_current = [f1, f2_pred, f3]
else:
# 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)
self.particles[i].update_best()
self.update_archive()
if __name__ == "__main__": # Run Classic MOPSO, collect data and run training for the model
main() 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=';'):
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)
elec_mean = (elec_df['Winter 2025'].mean() + elec_df['Summer 2025'].mean())/2
# 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_mean = elec_mean / 1000
elec_std = elec_std / 1000
print(f'Electricity prices:\n - Mean: ${elec_mean}€/Mwh\n - Std: ${elec_std}€/Mwh')
return elec_mean, elec_std
def generate_capacities(csv_file:str, nb_vehicles:int, seed:int=42, sep:str=';'):
cap_df = pd.read_csv(filepath_or_buffer=csv_file, sep=sep)
# Getting back all kind of battery capacities with unique values
all_capacities = cap_df['Battery Capacity kwh'].dropna().unique()
# Extracting random values for generating the array of capacities
capacities = pd.Series(all_capacities).sample(n=nb_vehicles, random_state=seed)
print(f'Capacities of vehicles (kwh): ${capacities}')
return capacities.tolist()
def get_power_constants(nb_vehicles:int, nb_consumers:int=67000000):
mean_consumption = (87028 + 46847 + 52374 + 29819)/4 # Mean of consumption in France in 2025 (estimate according to data/grid_capacity.txt)
sim_ratio = nb_vehicles / nb_consumers # Ratio to reduce A_max of simulation to realistic restrictions
a_max = sim_ratio * mean_consumption
x_max = a_max / nb_vehicles # For init, uniform charging/discharging for every vehicle
x_min = -x_max
return a_max, x_max, x_min
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):
A_MAX, X_MAX, X_MIN = get_power_constants(nb_vehicles=nb_vehicles)
print(f"\n--- Launching Scenario: {scenario_name} ---")
# Simulation parameters
params = {
'A_max': A_MAX, 'price_mean': price_mean, 'price_std': price_std,
'capacities': capacities, 'n': n, 't': t,
'w': w, 'c1': c1, 'c2': c2,
'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
optimizer = SmartMOPSO(model_type=model_type, **params)
if(model_type is not None):
optimizer.train_surrogate_model()
start_time = time.time()
# Run simulation
optimizer.iterate()
end_time = time.time()
duration = end_time - start_time
# Retrieve best f2 (e.g. from archive)
best_f2 = min([p.f_best[1] for p in optimizer.archive]) if optimizer.archive else 0
print(f"Finished in {duration:.2f} seconds.")
print(f"Best f2 found: {best_f2:.4f}")
return duration, best_f2, optimizer.archive
# 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)
d1, f1_score, _ = run_scenario(
"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
d2, f2_score, _ = run_scenario(
"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
d3, f3_score, _ = run_scenario(
"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 ---
print("\n=== SUMMARY ===")
print(f"{'Mode':<15} | {'Time (s)':<10} | {'Best f2':<10}")
print("-" * 45)
for k, v in results.items():
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)
+39 -26
View File
@@ -1,8 +1,9 @@
import random as rd import random as rd
from .particle import Particle from particle import Particle
import copy
class MOPSO(): class MOPSO():
def __init__(self, f_weights:list, A_max:float, price_mean:float, price_std:float, capacities:list, n:int, t:int, w:float, c1:float, c2:float, archive_size:int=10, nb_vehicles:int=10, delta_t:int=60, nb_of_ticks:int=72, x_min=-100, x_max=100, v_alpha=0.1, surrogate=False): def __init__(self, A_max:float, price_mean:float, price_std:float, capacities:list, n:int, t:int, w:float, c1:float, c2:float, archive_size:int=10, nb_vehicles:int=10, delta_t:int=60, nb_of_ticks:int=72, x_min=-100, x_max=100, v_alpha=0.1, surrogate=False):
# Constants # Constants
self.n = n # Number of particles self.n = n # Number of particles
self.t = t # Number of simulation iterations self.t = t # Number of simulation iterations
@@ -10,24 +11,34 @@ class MOPSO():
self.c1 = c1 # Individual trust self.c1 = c1 # Individual trust
self.c2 = c2 # Social trust self.c2 = c2 # Social trust
self.archive_size = archive_size # Archive size self.archive_size = archive_size # Archive size
self.f_weights = f_weights # Weigths for aggregation of all function objective
self.surrogate = surrogate # Using AI calculation self.surrogate = surrogate # Using AI calculation
# 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(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
# Particles of the simulation # Particles of the simulation
self.particles = [Particle(nb_vehicles=nb_vehicles, nb_of_ticks=nb_of_ticks, delta_t=delta_t, x_min=x_min, x_max=x_max, alpha=v_alpha) for _ in range(self.n)] self.particles = [
Particle(
socs=copy.deepcopy(self.socs),
times=self.times, # Ajouté ici
nb_vehicles=nb_vehicles,
nb_of_ticks=nb_of_ticks,
delta_t=delta_t,
x_min=x_min,
x_max=x_max,
alpha=v_alpha
) for _ in range(self.n)
]
self.archive = [] self.archive = []
self.leader = self.particles[0] # it doesnt matter as the first thing done is choosing a new leader self.leader = self.particles[0] # it doesnt matter as the first thing done is choosing a new leader
for i in range(self.n): for i in range(self.n):
self.particles[i].evaluate(self.f_weights, self.prices, self.socs, self.socs_req, self.times) self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times)
self.update_archive() self.update_archive()
def iterate(self): def iterate(self):
@@ -59,7 +70,7 @@ class MOPSO():
# Checking for best positions # Checking for best positions
# Generation of arriving and leaving times for every vehicle # Generation of arriving and leaving times for every vehicle
def generate_times(self, nb_vehicles, nb_of_ticks, delta_t): def generate_times(self, nb_vehicles, nb_of_ticks):
times = [] times = []
for _ in range(nb_vehicles): for _ in range(nb_vehicles):
# Minumun, we have one tick of charging/discharging during simulation # Minumun, we have one tick of charging/discharging during simulation
@@ -72,52 +83,54 @@ 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
# Genrates the coordinated states of charges requested and initial (duplicated initially for other ticks) # Genrates the coordinated states of charges requested and initial (duplicated initially for other ticks)
def generate_state_of_charges(self, nb_vehicles:int, nb_of_ticks:int): def generate_state_of_charges(self, nb_vehicles:int, nb_of_ticks:int):
socs = [] # Structure souhaitée : socs[tick][vehicle] pour être cohérent avec self.x[tick][vehicle]
socs = [[0.0 for _ in range(nb_vehicles)] for _ in range(nb_of_ticks)]
socs_req = [] socs_req = []
# We ensure soc_req is greater than what the soc_init is (percentage transformed into floats)
for _ in range(nb_vehicles): for i in range(nb_vehicles):
soc_init = rd.randrange(0, 100, 1) soc_init = rd.randrange(0, 100, 1)
soc_req = rd.randrange(soc_init + 1, 101, 1) soc_req = rd.randrange(soc_init + 1, 101, 1)
# Creating states of charges for each tick in time # Remplissage de la matrice 2D
for _ in range(nb_of_ticks): for tick in range(nb_of_ticks):
socs.append(soc_init/100) socs[tick][i] = soc_init / 100.0
socs_req.append(soc_req / 100.0)
# Adding the requested state of charge
socs_req.append(soc_req/100)
return socs, socs_req return socs, socs_req
# True if a dominates b, else false # True if a dominates b, else false
def dominates(a:Particle, b:Particle): def dominates(self, a:Particle, b:Particle):
dominates = (a.f_current[0] >= b.f_current[0]) and (a.f_current[1] >= b.f_current[1]) and (a.f_current[2] >= b.f_current[2]) dominates = (a.f_current[0] <= b.f_current[0]) and (a.f_current[1] <= b.f_current[1]) and (a.f_current[2] <= b.f_current[2])
if dominates: if dominates:
# Not strict superiority yet # Not strict superiority yet
dominates = (a.f_current[0] > b.f_current[0]) or (a.f_current[1] > b.f_current[1]) or (a.f_current[2] > b.f_current[2]) dominates = (a.f_current[0] < b.f_current[0]) or (a.f_current[1] < b.f_current[1]) or (a.f_current[2] < b.f_current[2])
return dominates return dominates
def update_archive(self): def update_archive(self):
candidates = self.archive + self.particles candidates = self.archive + self.particles
length = len(candidates) length = len(candidates)
non_dominated = [] non_dominated = []
for i in range(length): for i in range(length):
candidate_i = candidates[i] candidate_i = candidates[i]
dominates = True is_dominated = False
for j in range(length): for j in range(length):
if i != j: if i != j:
candidate_j = candidates[j] candidate_j = candidates[j]
dominates = dominates and self.dominates(candidate_i, candidate_j) if self.dominates(candidate_j, candidate_i):
if dominates: is_dominated = True
break
if not is_dominated:
non_dominated.append(candidate_i) non_dominated.append(candidate_i)
# Keeping only a certain number of solutions depending on archive_size (to avoid overloading the number of potential directions for particles)
if len(non_dominated) > self.archive_size: if len(non_dominated) > self.archive_size:
final_non_dominated = [] final_non_dominated = []
while len(final_non_dominated) < self.archive_size: while len(final_non_dominated) < self.archive_size:
+554
View File
@@ -0,0 +1,554 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "dcf378e6",
"metadata": {},
"source": [
"# Optimisation de charge de véhicules électriques par MOPSO assisté par IA\n",
"### Par Amine AIT MOUSSA, Siham DAANOUNI, et Clément DELAMOTTE\n",
"\n",
"---\n",
"\n",
"**Objectif :** Minimiser le coût de charge, l'insatisfaction utilisateur et la tension sur le réseau.\n",
"\n",
"**Méthode :** Utilisation d'un algorithme MOPSO (Multi-Objective Particle Swarm Optimization), couplé à un modèle de régression (Surrogate Model) pour accélérer l'évaluation des particules."
]
},
{
"cell_type": "markdown",
"id": "ea73e020",
"metadata": {},
"source": [
"## 1. Imports"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "5cf6f9f8",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import copy\n",
"from mopso import MOPSO \n",
"from surrogate_handler import SurrogateHandler\n",
"import pandas as pd"
]
},
{
"cell_type": "markdown",
"id": "ee8b04e4",
"metadata": {},
"source": [
"## 2. SmartMOPSO class\n",
"We created a new class that heritate the classic MOPSO, but we added :\n",
"- in the initialization the surrogate type\n",
"- a new implementation to the iteration function, to use the surrogate for predictions"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "f783ba32",
"metadata": {},
"outputs": [],
"source": [
"class SmartMOPSO(MOPSO):\n",
" def __init__(self, model_type=None, **kwargs):\n",
" super().__init__(**kwargs)\n",
" \n",
" # Initialize Surrogate Handler if model_type is provided\n",
" self.use_surrogate = (model_type is not None)\n",
" if self.use_surrogate:\n",
" self.surrogate_handler = SurrogateHandler(model_type)\n",
"\n",
" # Pre-fill with initial particle data\n",
" for p in self.particles:\n",
" self.surrogate_handler.add_data(p.x, p.f_current[1])\n",
"\n",
" def iterate(self, prediction_freq:int=10):\n",
" # Main loop (overriding original logic to manage control flow)\n",
" for t in range(self.t): \n",
" self.select_leader()\n",
" \n",
" for i in range(self.n):\n",
" # Movement \n",
" self.particles[i].update_velocity(self.leader.x, self.c1, self.c2, self.w)\n",
" self.particles[i].update_position()\n",
" self.particles[i].keep_boudaries(self.A_max)\n",
"\n",
" if (t % (prediction_freq) != 0) and self.use_surrogate:\n",
" # Fast exact calculation (f1, f3)\n",
" f1 = self.particles[i].f1(self.prices)\n",
" f3 = self.particles[i].f3()\n",
" \n",
" # Slow prediction (f2) by using Surrogate\n",
" f2_pred = self.surrogate_handler.predict(self.particles[i].x)\n",
" \n",
" # Inject scores without running the expensive 'updating_socs'\n",
" self.particles[i].f_current = [f1, f2_pred, f3]\n",
" \n",
" else:\n",
" # Standard Calculation (Slow and Exact)\n",
" self.particles[i].updating_socs(self.socs, self.capacities)\n",
" self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times)\n",
"\n",
" self.particles[i].update_best()\n",
" \n",
" self.update_archive()\n",
"\n",
"\n",
" # Run Classic MOPSO, collect data and run training for the model\n",
" def train_surrogate_model(self):\n",
" # Generation of data\n",
" for t in range(self.t): \n",
" self.select_leader()\n",
" \n",
" for i in range(self.n):\n",
" # Movement \n",
" self.particles[i].update_velocity(self.leader.x, self.c1, self.c2, self.w)\n",
" self.particles[i].update_position()\n",
" self.particles[i].keep_boudaries(self.A_max)\n",
" # Standard Calculation (Slow and Exact)\n",
" self.particles[i].updating_socs(self.socs, self.capacities)\n",
" self.particles[i].evaluate(self.prices, self.socs, self.socs_req, self.times)\n",
" \n",
" # Capture data for AI training\n",
" self.surrogate_handler.add_data(self.particles[i].x, self.particles[i].f_current[1])\n",
" \n",
" # End of dataset generation (based on classic MOPSO)\n",
" self.surrogate_handler.train()"
]
},
{
"cell_type": "markdown",
"id": "83075d35",
"metadata": {},
"source": [
"## 3. Utility functions\n",
"Manage CSV files reading and calculate physical constants"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "a7789c8c",
"metadata": {},
"outputs": [],
"source": [
"def calculate_elec_prices(csv_file:str, sep:str=';'):\n",
" elec_df = pd.read_csv(filepath_or_buffer=csv_file, sep=sep, skipinitialspace=True)\n",
"\n",
" # Mean of Winter and Summer of 2025 electric prices (Euros/MWh)\n",
" elec_mean = (elec_df['Winter 2025'].mean() + elec_df['Summer 2025'].mean())/2\n",
"\n",
" # Standard variation of Winter and Summer of 2025 electric prices (Euros/MWh)\n",
" elec_std = (elec_df['Winter 2025'].std() + elec_df['Summer 2025'].std())/2\n",
"\n",
" elec_mean = elec_mean / 1000\n",
" elec_std = elec_std / 1000\n",
"\n",
"\n",
" print(f'Electricity prices:\\n - Mean: ${elec_mean}€/Mwh\\n - Std: ${elec_std}€/Mwh')\n",
" return elec_mean, elec_std\n",
"\n",
"def generate_capacities(csv_file:str, nb_vehicles:int, seed:int=42, sep:str=';'):\n",
" cap_df = pd.read_csv(filepath_or_buffer=csv_file, sep=sep)\n",
"\n",
" # Getting back all kind of battery capacities with unique values\n",
" all_capacities = cap_df['Battery Capacity kwh'].dropna().unique()\n",
"\n",
" # Extracting random values for generating the array of capacities\n",
" capacities = pd.Series(all_capacities).sample(n=nb_vehicles, random_state=seed)\n",
"\n",
" print(f'Capacities of vehicles (kwh): ${capacities}')\n",
" return capacities.tolist()\n",
"\n",
"def get_power_constants(nb_vehicles:int, nb_consumers:int=67000000):\n",
" mean_consumption = (87028 + 46847 + 52374 + 29819)/4 # Mean of consumption in France in 2025 (estimate according to data/grid_capacity.txt)\n",
" sim_ratio = nb_vehicles / nb_consumers # Ratio to reduce A_max of simulation to realistic restrictions\n",
"\n",
" a_max = sim_ratio * mean_consumption\n",
" x_max = a_max / nb_vehicles # For init, uniform charging/discharging for every vehicle\n",
" x_min = -x_max\n",
" return a_max, x_max, x_min"
]
},
{
"cell_type": "markdown",
"id": "a2b510ed",
"metadata": {},
"source": [
"## 4. Execution Function\n",
"Execute the pipeline on a scenario with simulation parameters by using the SmartMOPSO class for the classic MOPSO or for MOPSO and Surrogate. It does the calculation for the functions f1 and f3, and calculate or predict by AI the f2 function"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "a8797755",
"metadata": {},
"outputs": [],
"source": [
"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):\n",
" A_MAX, X_MAX, X_MIN = get_power_constants(nb_vehicles=nb_vehicles)\n",
"\n",
"\n",
" print(f\"\\n--- Launching Scenario: {scenario_name} ---\")\n",
" # Simulation parameters\n",
" params = {\n",
" 'A_max': A_MAX, 'price_mean': price_mean, 'price_std': price_std,\n",
" 'capacities': capacities, 'n': n, 't': t, \n",
" 'w': w, 'c1': c1, 'c2': c2,\n",
" 'nb_vehicles': nb_vehicles, 'delta_t': delta_t, 'nb_of_ticks': nb_of_ticks,\n",
" 'x_min':X_MIN, 'x_max':X_MAX\n",
" }\n",
" \n",
"# Instantiate extended class\n",
" optimizer = SmartMOPSO(model_type=model_type, **params)\n",
"\n",
" if(model_type is not None):\n",
" optimizer.train_surrogate_model()\n",
" \n",
" start_time = time.time()\n",
" \n",
" # Run simulation\n",
" optimizer.iterate()\n",
" \n",
" end_time = time.time()\n",
" duration = end_time - start_time\n",
" \n",
" # Retrieve best f2 (e.g. from archive)\n",
" best_f2 = min([p.f_best[1] for p in optimizer.archive]) if optimizer.archive else 0\n",
" \n",
" print(f\"Finished in {duration:.2f} seconds.\")\n",
" print(f\"Best f2 found: {best_f2:.4f}\")\n",
" \n",
" return duration, best_f2, optimizer.archive"
]
},
{
"cell_type": "markdown",
"id": "cdbd5f06",
"metadata": {},
"source": [
"## 5. Scenario comparison\n",
"\n",
"We use here 3 configurations to evaluate our surrogate models :\n",
"1. **No AI (Baseline) :** Exact calculation, without any approximation.\n",
"2. **MLP (Multi-Layer Perceptron) :** Using a neural network to approximate $f_2$.\n",
"3. **RF (Random Forest) :** Using random forest to approximate $f_2$.\n",
"\n",
"The goal is to compare the **execution time** and **solutions quality**"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6cdd4953",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Electricity prices:\n",
" - Mean: $0.08469423999999999€/Mwh\n",
" - Std: $0.043112482841363334€/Mwh\n",
"Capacities of vehicles (kwh): $33 83.0\n",
"36 20.0\n",
"4 72.8\n",
"13 35.8\n",
"30 77.4\n",
"26 17.3\n",
"6 65.0\n",
"27 11.4\n",
"24 82.0\n",
"15 87.0\n",
"17 93.4\n",
"8 65.5\n",
"16 79.2\n",
"12 58.0\n",
"19 26.0\n",
"9 39.2\n",
"32 90.0\n",
"0 95.0\n",
"25 200.0\n",
"5 75.0\n",
"dtype: float64\n",
"\n",
"--- Launching Scenario: Only MOPSO ---\n",
"Finished in 0.39 seconds.\n",
"Best f2 found: 5.6198\n",
"\n",
"--- Launching Scenario: With MLP ---\n",
"Finished in 0.32 seconds.\n",
"Best f2 found: 6.7202\n",
"\n",
"--- Launching Scenario: With Random Forest ---\n",
"Finished in 2.77 seconds.\n",
"Best f2 found: 5.7399\n",
"\n",
"--- Launching Scenario: Only MOPSO ---\n",
"Finished in 0.92 seconds.\n",
"Best f2 found: 4.0497\n",
"\n",
"--- Launching Scenario: With MLP ---\n",
"Finished in 0.78 seconds.\n",
"Best f2 found: 4.2297\n",
"\n",
"--- Launching Scenario: With Random Forest ---\n",
"Finished in 6.91 seconds.\n",
"Best f2 found: 5.2098\n",
"\n",
"--- Launching Scenario: Only MOPSO ---\n",
"Finished in 1.90 seconds.\n",
"Best f2 found: 6.0193\n",
"\n",
"--- Launching Scenario: With MLP ---\n",
"Finished in 1.80 seconds.\n",
"Best f2 found: 3.2313\n",
"\n",
"--- Launching Scenario: With Random Forest ---\n",
"Finished in 13.86 seconds.\n",
"Best f2 found: 5.1697\n",
"\n",
"--- Launching Scenario: Only MOPSO ---\n",
"Finished in 9.62 seconds.\n",
"Best f2 found: 5.3396\n",
"\n",
"--- Launching Scenario: With MLP ---\n",
"Finished in 8.16 seconds.\n",
"Best f2 found: 6.0898\n",
"\n",
"--- Launching Scenario: With Random Forest ---\n",
"Finished in 69.95 seconds.\n",
"Best f2 found: 7.1100\n"
]
}
],
"source": [
"\n",
"# CSV files\n",
"elec_price_csv = 'data/elec_prices.csv'\n",
"capacity_csv = 'data/vehicle_capacity.csv'\n",
"\n",
"# Global Simulation parameters\n",
"T = 30 # Number of iterations (for the particles)\n",
"W = 0.4 # Inertia (for exploration)\n",
"C1 = 0.3 # Individual trust\n",
"C2 = 0.2 # Social trust\n",
"ARC_SIZE = 10 # Archive size\n",
"nb_vehicle = 20\n",
"\n",
"P_MEAN, P_STD = calculate_elec_prices(elec_price_csv)\n",
"CAPACITIES = generate_capacities(capacity_csv, nb_vehicles=nb_vehicle)\n",
"\n",
"NB_TICKS = 48\n",
"DELTA = 60\n",
"\n",
"results = {\n",
" 'MOPSO':[],\n",
" 'MLP': [],\n",
" 'RF': []\n",
"}\n",
"nb_particles = [20,50,100,500]\n",
"\n",
"for k in range(len(nb_particles)):\n",
" # 1. Without Surrogate (Baseline)\n",
" d1, f1_score, _ = run_scenario(\n",
" \"Only MOPSO\", \n",
" capacities=CAPACITIES, \n",
" price_mean=P_MEAN, \n",
" price_std=P_STD, \n",
" nb_vehicles=nb_vehicle, # Important pour la cohérence\n",
" model_type=None,\n",
" n=nb_particles[k]\n",
" )\n",
" results['MOPSO'].append((d1, f1_score))\n",
"\n",
" # 2. With MLP\n",
" d2, f2_score, _ = run_scenario(\n",
" \"With MLP\", \n",
" capacities=CAPACITIES, \n",
" price_mean=P_MEAN, \n",
" price_std=P_STD, \n",
" nb_vehicles=nb_vehicle,\n",
" model_type='mlp',\n",
" n=nb_particles[k]\n",
" )\n",
" results['MLP'].append((d2, f2_score))\n",
"\n",
" # 3. With Random Forest\n",
" d3, f3_score, _ = run_scenario(\n",
" \"With Random Forest\", \n",
" capacities=CAPACITIES, \n",
" price_mean=P_MEAN, \n",
" price_std=P_STD, \n",
" nb_vehicles=nb_vehicle,\n",
" model_type='rf',\n",
" n=nb_particles[k]\n",
" )\n",
" results['RF'].append((d3, f3_score))"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "165b48cd",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\n",
"=== SUMMARY ===\n",
"Mode | Time (s) | Best f2 \n",
"---------------------------------------------\n",
"MOPSO _20 | 0.39 | 5.6198 \n",
"MOPSO _50 | 0.92 | 4.0497 \n",
"MOPSO _100 | 1.90 | 6.0193 \n",
"MOPSO _500 | 9.62 | 5.3396 \n",
"MLP _20 | 0.32 | 6.7202 \n",
"MLP _50 | 0.78 | 4.2297 \n",
"MLP _100 | 1.80 | 3.2313 \n",
"MLP _500 | 8.16 | 6.0898 \n",
"RF _20 | 2.77 | 5.7399 \n",
"RF _50 | 6.91 | 5.2098 \n",
"RF _100 | 13.86 | 5.1697 \n",
"RF _500 | 69.95 | 7.1100 \n"
]
}
],
"source": [
"# --- DISPLAY RESULTS ---\n",
"print(\"\\n=== SUMMARY ===\")\n",
"print(f\"{'Mode':<15} | {'Time (s)':<10} | {'Best f2':<10}\")\n",
"print(\"-\" * 45)\n",
"for k, v in results.items():\n",
" for i in range(len(nb_particles)):\n",
" print(f\"{k:<15}_{nb_particles[i]:<15} | {v[i][0]:<10.2f} | {v[i][1]:<10.4f}\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "314f8d5a",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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 truncated
"text/plain": [
"<Figure size 1000x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"def plot_time_benchmark(nb_particles_list, results_dict):\n",
" \n",
" t_mopso = [item[0] for item in results_dict['MOPSO']]\n",
" t_mlp = [item[0] for item in results_dict['MLP']]\n",
" t_rf = [item[0] for item in results_dict['RF']]\n",
"\n",
" plt.figure(figsize=(10, 6))\n",
" \n",
" plt.plot(nb_particles_list, t_mopso, 'o-', label='Sans IA (MOPSO)', color='#1f77b4', linewidth=2)\n",
" plt.plot(nb_particles_list, t_mlp, 's--', label='Avec MLP', color='#ff7f0e', linewidth=2)\n",
" plt.plot(nb_particles_list, t_rf, '^-.', label='Avec Random Forest', color='#2ca02c', linewidth=2)\n",
" \n",
" plt.title(\"Temps d'exécution selon le nombre de particules\", fontsize=14, fontweight='bold')\n",
" plt.xlabel(\"Nombre de Particules\", fontsize=12)\n",
" plt.ylabel(\"Temps (s)\", fontsize=12)\n",
" plt.grid(True, linestyle=':', alpha=0.7)\n",
" plt.legend(fontsize=11)\n",
" \n",
"\n",
" \n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
" \n",
"plot_time_benchmark(nb_particles, results)"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "1e3fa2ff",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA94AAAJOCAYAAABBfN/cAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4U2X7wPFvku5dugullLJKy2pZAoooQ0UEQTaIgqAiIuB63b6+jp++KqIoiPiCioCiDFFwA7JnWaVAgVJKF7R07ybn90fooekG2qbj/lwXF8lzzknuJzlJc59naRRFURBCCCGEEEIIIUSt0Jo7ACGEEEIIIYQQojGTxFsIIYQQQgghhKhFkngLIYQQQgghhBC1SBJvIYQQQgghhBCiFkniLYQQQgghhBBC1CJJvIUQQgghhBBCiFokibcQQgghhBBCCFGLJPEWQgghhBBCCCFqkSTeQgghhBBCCCFELZLEW4ga8NBDD6HRaNBoNNx+++0m24rLNRoNy5cvV8uXL19usk2I2nL58mWcnJzQaDR06tQJRVHMHZJoBHJzc/Hw8ECj0dCqVSvy8/PNHVIZlX031xetWrVSY3z99dfNHU699frrr6uvU6tWrcwdzk1pCOeluZn7c3H77berz//QQw/V+fOLxkkSb9Hgbd261SSB1Wg03HfffeXu+9tvv5XZV75Qa8aePXuYMGECrVq1wsbGBnt7e/z8/OjRowfTpk3j888/N3eIdeb8+fNlzrPy/pX8wVVUVMS6det46qmn6N27Ny1btsTGxgYHBwc6d+7M888/z6VLl24onjfffJPMzEwAnnnmmQov9OzcuZMZM2YQEhKCi4sLlpaWuLu7c+utt/Laa69x9uzZG3p+0TjZ2tryxBNPABATE8OiRYvMHJEQoiEwd1IthLlYmDsAIWrDL7/8wrlz52jdurVJ+YIFC2rl+caNG0dISAgAfn5+tfIc9dnSpUuZMWNGmZbUnJwcLl68yIEDB/jxxx959NFHzRRh/ZecnMzIkSPLlOfn53Ps2DGOHTvGsmXL2LJlC8HBwdV+3ISEBBYvXgyAu7s7EyZMKLNPamoqU6dOZf369WW2paSksGPHDnbs2MG2bdvYunVrtZ9bNH5PPPEEb731FkVFRbz99ts8/vjjWFtbmzssIUQD99JLL5Geng5Anz59zByNEDVDEm/RKBkMBhYuXMiHH36olp0+fZpff/21Vp7vrrvu4q677qqVxza3zMxMHB0dK9x+5coVZs+erSbdLVq04IEHHsDT05PMzEyOHz/OP//8U1fhXpeMjAycnJxq/XkGDRrE4MGDy5SXd5HG0tKSgQMH0qNHDwoLC1mzZg1nzpwBjF3GH330UXbs2FHt5162bBkFBQUAjBo1CktLS5Pt2dnZDB48mAMHDqhl3t7ejBgxgpYtW5KZmcmhQ4f466+/qv2cjVV2dja2trZotdJZrJiHhwd33HEHv//+O5cvX2bt2rWMHz/e3GEJUS16vZ78/Hzs7OzMHUqjd73fn9OnT6/liIQwA0WIBm7Lli0KoP7TarUKoDg7OytZWVnqfrNmzVL30el06u0pU6aUeczExETlhRdeULp06aI4ODgo1tbWSmBgoDJz5kwlJiamzP5TpkxRH69///4m20rGtmzZMrV82bJlJttKy8vLUz755BPl1ltvVVxdXRVLS0vF29tbeeCBB5Rdu3aV2f+1115TH8vf399kW3R0tMlzbdmypcLjkpOTlZkzZyrNmzdXtFqtMn/+/HJf92IbNmwweezz58+X2aewsFD57bffyj0+NjZWee6555SuXbsqjo6OirW1teLn56cMHz5c+f3338vs/8MPPyj33HOP4uXlpVhaWiouLi7KLbfcorz//vtKdnZ2mf1Lv/7r169XbrnlFsXe3l5xdnY22feff/5Rxo4dq/j5+SlWVlaKo6Oj0rt3b2XhwoVKQUFBpa9DSaVf79dee63KYy5fvqzMmTNHSUhIMCnPyclROnToYPJ46enp1Y6lTZs26nHlvZ7/+te/TB57+PDh5b6OcXFxyuLFi8uU//nnn8qoUaOU5s2bq69Zt27dlFdffVVJSUkps7+/v7/J63LgwAFl6NChirOzs2Jra6v069dP2b59u7p/VFRUhedusZ49e6rbH3nkEZNthw8fVh5++GGldevWio2NjWJvb6907dpVeeutt0y+HyqKb/v27cqdd96pODk5KYCSmpqqKIrxnH733XeVNm3aKFZWVkrr1q2Vt956SykoKKjwM1/ses+z0o/3+++/K7fffrtib2+vODg4KHfddZdy/PjxMscpyvV/vn766SflvvvuU7y9vdXP14ABA5QVK1YoBoOh3OdYsmSJGt/AgQPL3ac8hYWFyvz585XevXsrzs7Oik6nU5o1a6Z07NhRmTx5srJq1aoyx9Tkd7OiKMrFixeVZ555RgkJCVHs7e0Va2trxd/fX5k4caKyd+/eMvuX/r5MS0tTnnnmGaVly5aKpaWlEhAQoLz11lsVvlblKX3OlXa953BFyvs7sGrVKqVnz56Kra2t4uLiojzwwAPKhQsXyj3+wIEDyuTJk5VWrVop1tbWir29vRIcHKzMmzdPiY2NLbN///79Tf7O7t27V7nzzjsVe3t7xdPTU5k5c6aSmZmpKIqifPfdd0poaKhiY2Oj+Pr6KvPmzVPy8vJMHq/0a5+RkaHMmzdPadGihWJtba0EBQUpn3zySZnXvvQ5EBMTo0yaNEnx9PRUNBqNsm7dOnXf6z2/qrJt2zalf//+ip2dneLq6qo88MADypkzZ6o8L2syjtLn1549e5RBgwYpTk5OioODgzJ48GDlwIEDZY778ssvldGjRysdOnRQ3NzcFAsLC8XR0VHp0qWL8txzzymXL1+u8rlKf38OHz7c5Bws719Fj1VaZGSkMnPmTCUoKEixt7dXbG1tlYCAAGXs2LHK/v371f1Kn4clVfY7rLLjFEVRzp49qzz55JNKhw4dFDs7O8XGxkYJCgpSnn/++XJfm8uXLytPP/200rFjR8XOzk6xtLRUvLy8lB49eihPPPGEsnv37jLHiMZHEm/R4JVOvEeMGKHe/vTTTxVFUZT09HTF0dFRAZRu3bqZfKGX/kLdtWuX4u7uXuEfBWdnZ+Wff/4xOaamE+9Lly4pXbt2rTAGrVarfPTRRybH1ETi7e7uXibJqyrx/vHHH03237BhQ6X7l/TLL7+o70t5/5566il136KiImXMmDGV/sEOCgpS4uPjTZ6j5PZbb721zHtZ7MUXX6z0sW+99dZq/8i9kcS7Mk8//bTJ4yUnJ1fruHPnzpmcMxkZGSbbCwoKTF5/b2/v6/ohP2/evEpfs+bNm5dJCEt+9nr27KlYWlqWOc7a2lo5ceKEekzJ923GjBkmj3fmzBmTY0telPrss88UCwuLCuPr2LFjmQsdJeO75ZZbTC7SwbXEe9y4ceU+5rBhwyr8zCvKjZ1nJbf37dtX0Wg0ZY5zc3NTLl26ZHLc9Xy+9Hq9Mnny5EpjGz16tFJUVFTmPDh27JjJe1c6YapIye/N8v716tXLZP+a/m7etm2b4urqWuHjabVa5YMPPjA5puT3pZubmxIUFFTusa+88kq1XgNFqTzBuJFzuCKlv5f69etX7mO2bdtWyc3NNTl2/vz56kXtil770hfFSiYuwcHBirW1dZnjbr/9duX9998v9zEnT55c4Wvv5eWldO/evdzjnnzySZPjSp4Dbdu2Vby9vU32L068b+T8qszGjRvLfe+aNWum3HLLLRWelzUdR8nzq1+/fuV+59ra2ppc8FQURQkLC6v089m8eXMlLi6uwucq7/uzphLvpUuXKlZWVhU+RsnfLbWReK9fv16xs7Or9LUp+TcsNzdXad++faX1fv7556v5joqGTLqai0Zn4sSJ7Nixg+TkZBYuXMjMmTNZtmyZOrnU7NmzK5zMIyMjgxEjRpCcnAyAv78/Y8eOxdbWlh9++IGIiAjS09MZNWoUUVFRODs710odJk+ezOHDhwFwdHRkwoQJtGjRgp07d/Lrr79iMBiYO3cu3bt3p2/fvjX2vMnJySQnJzNw4ED69u3L5cuX8fLyqvSYrl27otFo1K7mw4cPp3Xr1vTu3ZvQ0FBuvfVWevToUWZCr5iYGEaPHk1OTg6AOile165duXz5Mn///bfJ/m+//Tbff/+9er93794MHjyYyMhI1qxZA0BkZCQTJ04sc2yx7du34+7uzrhx43BzcyMiIgKA1atX8/bbb6v7DRkyhL59+5KUlMRXX31FVlYW27dvZ+7cuSxZsqQ6L6WJXbt28f7775cpv/vuu6s1XvvkyZPq7datW+Pm5lat592+fbt6u127dmWGDOzfv1/9XACMHTsWe3v7aj32N998YzKUIzg4mPvvv5/4+Hi++uor9Ho9cXFxjBw5koiICCwsyv652bdvHy1atGDixInExsaycuVKwDiufcGCBerY9Icfflityw8//MDChQvVLvOrVq1SH69Dhw7ccsstgPE1nzVrFgaDATCeL3fddReZmZl89dVXJCcnc+LECR588EF+//33cuu4e/du7OzsmDRpEs2bNyc8PBydTscPP/zA6tWr1f1at27NuHHjuHDhAt9++22Fr1lNnGc7d+6kQ4cOjBw5ksOHD7Np0ybAOBb/yy+/5F//+hdw/Z+v9957j2+++Ubdd9SoUXTp0oXo6Gi++eYbddhD165defHFF02ODQoKwt7enuzsbPLz89m3bx+33nprha8DQFZWFitWrFDvjxo1itDQUNLT04mJiWHbtm0m+9f0d3NaWhojR44kNTUVME4U9/DDD+Pk5MSqVauIiYnBYDDwzDPPEBYWRv/+/cs8RkpKCqmpqTz44IP4+vqydOlSNb4FCxbw8ssvY2VlVWkclamJc7gyO3bsoEePHgwZMoQtW7awc+dOAKKioli/fj3jxo0D4J9//mHevHnqd3zLine truncated
"text/plain": [
"<Figure size 1000x600 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"def plot_f2_benchmark(nb_particles_list, results_dict):\n",
" s_mopso = [item[1] for item in results_dict['MOPSO']]\n",
" s_mlp = [item[1] for item in results_dict['MLP']]\n",
" s_rf = [item[1] for item in results_dict['RF']]\n",
"\n",
"\n",
" plt.figure(figsize=(10, 6))\n",
" \n",
" plt.plot(nb_particles_list, s_mopso, 'o-', label='Sans IA (MOPSO)', color='#1f77b4', linewidth=2)\n",
" plt.plot(nb_particles_list, s_mlp, 's--', label='Avec MLP', color='#ff7f0e', linewidth=2)\n",
" plt.plot(nb_particles_list, s_rf, '^-.', label='Avec Random Forest', color='#2ca02c', linewidth=2)\n",
" \n",
" plt.title(\"Meilleur Score F2 (Convergence) selon le nombre de particules\", fontsize=14, fontweight='bold')\n",
" plt.xlabel(\"Nombre de Particules (log scale)\", fontsize=12)\n",
" plt.ylabel(\"Meilleur F2 Score\", fontsize=12)\n",
" plt.grid(True, linestyle=':', alpha=0.7)\n",
" plt.legend(fontsize=11)\n",
" \n",
" plt.xscale('log') \n",
" \n",
" plt.tight_layout()\n",
" plt.show()\n",
"\n",
"plot_f2_benchmark(nb_particles, results)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Optim_Metaheuristique (3.11.14)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.14"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+26 -15
View File
@@ -1,12 +1,14 @@
import random as rd import random as rd
import copy
class Particle(): class Particle():
def __init__(self,socs:list, nb_vehicles:int=10, delta_t:int=60, nb_of_ticks:int=72, x_min=-100, x_max=100, alpha=0.1): def __init__(self, socs:list, times:list, nb_vehicles:int=10, delta_t:int=60, nb_of_ticks:int=72, x_min=-100, x_max=100, alpha=0.1):
# Problem specific attributes # Problem specific attributes
self.nb_vehicles = nb_vehicles # Number of vehicles handles for the generations of position x self.nb_vehicles = nb_vehicles # Number of vehicles handles for the generations of position x
self.delta_t = delta_t # delta_t for update purposes self.delta_t = delta_t # delta_t for update purposes
self.nb_of_ticks = nb_of_ticks # Accounting for time evolution of the solution (multiplied by delta_t) self.nb_of_ticks = nb_of_ticks # Accounting for time evolution of the solution (multiplied by delta_t)
self.socs = socs # States of charges for the particle current position (self.x) self.socs = socs # States of charges for the particle current position (self.x)
self.times = times
# Minima and maxima of a position value # Minima and maxima of a position value
self.x_min = x_min self.x_min = x_min
@@ -63,17 +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 update_socs(self, capacities):
for tick in range(self.nb_of_ticks):
for i in range(self.nb_vehicles-1):
self.socs[tick][i+1] = self.socs[tick][i] + (self.x[tick][i] / capacities[i])
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
@@ -84,14 +81,15 @@ 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
# Function objective # Function objective
def evaluate(self,elec_prices,socs,socs_req,times): def evaluate(self,elec_prices,socs,socs_req,times):
f1 = self.f1(elec_prices) f1 = self.f1(elec_prices)
f2 = self.f2(socs,socs_req,times) f2 = self.f2(self.socs,socs_req,times)
f3 = self.f3() f3 = self.f3()
# Keeping in memory evaluation of each objective for domination evaluation # Keeping in memory evaluation of each objective for domination evaluation
@@ -103,13 +101,13 @@ class Particle():
self.f_current = f_current self.f_current = f_current
def update_best(self): def update_best(self):
current_better = (self.f_current[0] >= self.f_best[0]) and (self.f_current[1] >= self.f_best[1]) and (self.f_current[2] >= self.f_best[2]) current_better = (self.f_current[0] <= self.f_best[0]) and (self.f_current[1] <= self.f_best[1]) and (self.f_current[2] <= self.f_best[2])
if current_better: if current_better:
# Not strict superiority yet # Not strict superiority yet
current_dominates = (self.f_current[0] > self.f_best[0]) or (self.f_current[1] > self.f_best[1]) or (self.f_current[2] > self.f_best[2]) current_dominates = (self.f_current[0] < self.f_best[0]) or (self.f_current[1] < self.f_best[1]) or (self.f_current[2] < self.f_best[2])
if current_dominates: if current_dominates:
self.p_best = self.x self.p_best = copy.deepcopy(self.x)
self.f_best = self.f_current self.f_best = self.f_current[:]
# Calculate the price of the electricity consumption in the grid SUM(1_to_T)(Epsilon_t * A_t * delta_t) # Calculate the price of the electricity consumption in the grid SUM(1_to_T)(Epsilon_t * A_t * delta_t)
def f1(self,elec_prices): def f1(self,elec_prices):
@@ -142,5 +140,18 @@ class Particle():
current_grid_stress += self.x[tick][i] current_grid_stress += self.x[tick][i]
return current_grid_stress return current_grid_stress
def updating_socs(self, socs, capacities): def updating_socs(self, initial_socs, capacities):
pass
# Calcul de l'évolution temporelle
for tick in range(self.nb_of_ticks - 1): # On s'arrête à l'avant-dernier pour calculer le suivant
for i in range(self.nb_vehicles):
# SoC(t+1) = SoC(t) + (Puissance(t) * delta_t / Capacité)
# Attention: x est en kW, delta_t en minutes -> conversion en heures (/60) si capacité en kWh
energy_added = (self.x[tick][i] * (self.delta_t / 60))
# Mise à jour du tick suivant basé sur le tick actuel
# On utilise initial_socs comme base si c'est une liste de listes [tick][vehicule]
self.socs[tick+1][i] = self.socs[tick][i] + (energy_added / capacities[i])
# Bornage entre 0 et 1 (0% et 100%)
self.socs[tick+1][i] = max(0.0, min(1.0, self.socs[tick+1][i]))
+3
View File
@@ -5,5 +5,8 @@ description = "Metaheuristic Optimization Project"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"matplotlib>=3.10.8",
"numpy>=2.4.1",
"pandas>=2.3.3", "pandas>=2.3.3",
"scikit-learn>=1.8.0",
] ]
+40
View File
@@ -0,0 +1,40 @@
import numpy as np
from sklearn.neural_network import MLPRegressor
from sklearn.ensemble import RandomForestRegressor
class SurrogateHandler:
def __init__(self, model_type='mlp'):
self.model_type = model_type
self.is_trained = False
self.data_X = []
self.data_Y = []
# Model choice
if model_type == 'mlp':
self.model = MLPRegressor(hidden_layer_sizes=(100, 50), max_iter=500, random_state=42)
elif model_type == 'rf':
# RandomForest is generaly more robust "out of the box"
self.model = RandomForestRegressor(n_estimators=100, random_state=42)
else:
raise ValueError("Model type must be 'mlp' or 'rf'")
def add_data(self, x_matrix, f2_value):
# Flattening the position matrix to a 1 dimension vector
flat_x = np.array(x_matrix).flatten()
self.data_X.append(flat_x)
self.data_Y.append(f2_value)
def train(self):
if len(self.data_X) < 20: # No training if their is too few data
return
X = np.array(self.data_X)
y = np.array(self.data_Y)
self.model.fit(X, y)
self.is_trained = True
def predict(self, x_matrix):
if not self.is_trained:
return None
flat_x = np.array(x_matrix).flatten().reshape(1, -1)
return self.model.predict(flat_x)[0]
Generated
+34 -2
View File
@@ -1,14 +1,46 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv pip compile pyproject.toml -o uv.lock # uv pip compile pyproject.toml -o uv.lock
contourpy==1.3.3
# via matplotlib
cycler==0.12.1
# via matplotlib
fonttools==4.61.1
# via matplotlib
joblib==1.5.3
# via scikit-learn
kiwisolver==1.4.9
# via matplotlib
matplotlib==3.10.8
# via optim-meta (pyproject.toml)
numpy==2.4.1 numpy==2.4.1
# via pandas # via
# optim-meta (pyproject.toml)
# contourpy
# matplotlib
# pandas
# scikit-learn
# scipy
packaging==25.0
# via matplotlib
pandas==2.3.3 pandas==2.3.3
# via optim-meta (pyproject.toml) # via optim-meta (pyproject.toml)
pillow==12.1.0
# via matplotlib
pyparsing==3.3.1
# via matplotlib
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
# via pandas # via
# matplotlib
# pandas
pytz==2025.2 pytz==2025.2
# via pandas # via pandas
scikit-learn==1.8.0
# via optim-meta (pyproject.toml)
scipy==1.17.0
# via scikit-learn
six==1.17.0 six==1.17.0
# via python-dateutil # via python-dateutil
threadpoolctl==3.6.0
# via scikit-learn
tzdata==2025.3 tzdata==2025.3
# via pandas # via pandas