-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathecran_resultats.py
More file actions
429 lines (358 loc) · 19.5 KB
/
ecran_resultats.py
File metadata and controls
429 lines (358 loc) · 19.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# -*- coding: utf-8 -*-
"""
Created on Fri Mar 7 14:46:36 2025
@author: Despouysoli
"""
import streamlit as st
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from unidecode import unidecode
# import pickle
import pandas as pd
import numpy as np
import pickle
from battman_communs import DEFAULT_PRICE, DEFAULT_GENERATION
from battman_communs import validation_parametrage, get_optim_by_label
import optimisation_batterie_EOD as obe
# =============================================================================
# Validation des données, optimisation +
# production et affichage des résultats
# =============================================================================
# Dico des labels des courbes affichées
dico_labels_vizu = {
'puissance_reseau' : 'Puissance vue du réseau (MW)',
'energie' : 'Energie (MWh)',
'energie_charge_batterie' : 'Energie en charge vue de la batterie (MWh)',
'energie_decharge_batterie' : 'Energie en décharge vue de la batterie (MWh)',
'energie_charge_cumulee' : 'Energie en charge cumulée sur une fenêtre glissante (MWh)',
'energie_decharge_cumulee' : 'Energie en décharge cumulée sur une fenêtre glissante (MWh)',
'p_min' : 'Puissance minimale (MW)',
'p_max' : 'Puissance maximale (MW)',
'prix' : "Prix de l'électricité (€/MWh)",
'gains' : "Bénéfices pour la batterie (€)",
'gains_cumules' : "Bénéfices cumulés (€)"}
# Dico des labels __sans accent__ pour le fichier CSV
dico_labels_csv = {k:unidecode(v) for k,v in dico_labels_vizu.items()}
dico_legende = dict(
x=1.02, # Position horizontale de la légende
y=1, # Position verticale de la légende
traceorder='normal', # L'ordre des éléments dans la légende
orientation='v', # Disposition verticale de la légende
itemwidth=50, # Largeur maximale des éléments dans la légende
borderwidth=2, # Largeur de la bordure autour de la légende
bordercolor='black', # Couleur de la bordure de la légende
)
def generer_graphique_puissances(df_synthese, dico_site, delta_t):
fig = go.Figure()
# Bande indiquant les limitations de RTE
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_min,
mode='lines',
fill=None,
showlegend=False,
line=dict(color='red', dash='dot', width=0)))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_max,
mode='lines',
fill='tonexty',
fillcolor='rgba(255, 0, 0, 0.2)',
name="Limitations de puissance",
line=dict(color='red', dash='dot', width=0)))
# Pracc (délimitée par deux droites dashed)
fig.add_trace(go.Scatter(x=df_synthese.index,
y=[dico_site['p_racc'] for _ in df_synthese.index[1:]],
mode='lines',
name="Pracc",
line=dict(color='red', dash='dash')))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=[-dico_site['p_racc'] for _ in df_synthese.index[1:]],
mode='lines',
showlegend=False,
line=dict(color='red', dash='dash')))
# Puissances du site, de la batterie, spilled etc.
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_site,
mode='lines',
name="Puissance site",
line=dict(color='black')))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_batterie,
mode='lines',
name="Puissance batterie",
line=dict(color='blue')))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_inj,
mode='lines',
name="Prod/conso intra-site",
line=dict(color='green')))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_spilled,
mode='lines',
name="Puissance écrêtée",
line=dict(color='orange')))
p_aux = dico_site['conso_veille'] * dico_site['e_max'] * 1e-3
fig.add_trace(go.Scatter(x=df_synthese.index,
y=[-p_aux for _ in df_synthese.index[1:]],
mode='lines',
name="Consommation auxiliaires",
line=dict(color='grey', dash='solid')))
# Mise à jour du layout (options du graphique)
fig.update_layout(
title="Puissances",
xaxis_title=f"Timesteps (intervalle {delta_t} minutes)",
yaxis_title="Puissance (MW)",
legend=dico_legende,
template="plotly",
showlegend=True,
height=600
)
st.plotly_chart(fig)
def generer_graphique_energies(df_synthese, dico_site, delta_t):
fig = go.Figure()
# Valeurs maximale et minimales en énergie (délimitée par deux droites dashed)
fig.add_trace(go.Scatter(x=df_synthese.index,
y=[dico_site['soc_min'] * dico_site['e_max'] / 100 for _ in df_synthese.index],
mode='lines',
# name="Energie minimale (batterie)",
showlegend=False,
line=dict(color='red', dash='dash')))
fig.add_trace(go.Scatter(x=df_synthese.index,
y=[dico_site['soc_max'] * dico_site['e_max'] / 100 for _ in df_synthese.index],
mode='lines',
name="Energie min/max",
line=dict(color='red', dash='dash')))
# Valeurs imposées en énergie
contraintes_energie = pd.Series(data=np.nan, index=df_synthese.index)
# Energie périodique (hors pas de temps négatifs et dernier pas de temps)
if dico_site['periode_soc'] is not None and dico_site['soc_periodique'] is not None:
filtre = (contraintes_energie.index % dico_site['periode_soc'] == 0) & (contraintes_energie.index > 0) &\
(contraintes_energie.index != contraintes_energie.index[-1])
contraintes_energie.loc[filtre] = dico_site['soc_periodique'] * dico_site['e_max'] / 100
# Energie initiale
if dico_site['soc_initiale'] is not None:
contraintes_energie.loc[contraintes_energie.index < 0] = dico_site['soc_initiale'] * dico_site['e_max'] / 100
# Energie finale
if dico_site['soc_finale'] is not None:
contraintes_energie.loc[contraintes_energie.index == contraintes_energie.index[-1]] = dico_site['soc_finale'] * dico_site['e_max'] / 100
fig.add_trace(go.Scatter(x=df_synthese.index,
y=contraintes_energie,
mode='markers',
name="Valeurs d'énergie imposées",
marker=dict(color='red', size=10)))
# Energie de la batterie
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.energie,
mode='lines',
name="Energie batterie",
line=dict(color='blue', width=2)))
# Energie spilled
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.p_spilled * delta_t / 60,
mode='lines',
name="Energie non écacuée",
line=dict(color='orange', width=2)))
# Mise à jour du layout (options du graphique)
fig.update_layout(
title="Energies",
xaxis_title=f"Timesteps (intervalle {delta_t} minutes)",
yaxis_title="Energie (MWh)",
legend=dico_legende,
template="plotly",
showlegend=True,
height=600
)
st.plotly_chart(fig)
def generer_graphique_benefices(df_synthese, dico_site, delta_t):
rows, cols = 2, 1
fig = make_subplots(rows=rows, cols=cols, shared_xaxes=True,
# specs=[[{"secondary_y": True}] * cols] * rows,
vertical_spacing=0.15)
# 1. Premier graphique : Prix électricité
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.prix,
mode='lines',
name="Prix électricité",
line=dict(color='blue')),
row=1, col=1)
fig.update_yaxes(title_text="Prix de l'électricité (€/MWh)",
row=1, col=1)
# 2. Deuxième graphique : Gains cumulés
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese.gains_cumules,
mode='lines',
name="Gains cumulés",
line=dict(color='green')),
row=2, col=1)
fig.update_yaxes(title_text="Bénéfices cumulés (€)",
row=2, col=1)
fig.update_xaxes(title_text=f"Timesteps (intervalle {delta_t} minutes)",
row=2, col=1)
# Mise à jour du layout (options du graphique)
fig.update_layout(
title="Données économiques",
# xaxis_title=f"Timesteps (intervalle {delta_t} minutes)",
# yaxis_title="Bénéfices (€)",
legend=dico_legende,
template="plotly",
showlegend=True,
height=600
)
st.plotly_chart(fig)
def affichage_courbes (df_synthese):
# Création de subplots avec axes partagés
rows, cols = 3, 1
fig = make_subplots(rows=rows, cols=cols, shared_xaxes=True,
# subplot_titles=("Puissances", "Energies"),
specs=[[{"secondary_y": True}] * cols] * rows,
vertical_spacing=0.15)
# Subplot des euros --------------------------------------
row_euros, col_euros = 1, 1
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese["Prix de l'électricité (€/MWh)"],
mode="lines",
name="Prix de l'électricité (€/MWh)",
legendgroup="grp_euros",
legendgrouptitle_text="Données économiques"),
secondary_y=False,
row=row_euros, col=col_euros)
fig.add_trace(go.Scatter(x=df_synthese.index,
y=df_synthese["Bénéfices pour la batterie (€)"],
mode="lines",
name="Bénéfices pour la batterie (€)",
legendgroup="grp_euros",
legendgrouptitle_text="Données économiques"),
secondary_y=True,
row=row_euros, col=col_euros)
fig.update_xaxes(title_text="Données économiques", row=row_euros, col=col_euros)
fig.update_yaxes(title_text="Prix (€)", row=row_euros, col=col_euros, secondary_y=False)
fig.update_yaxes(title_text="Gains (€)", row=row_euros, col=col_euros, secondary_y=True)
# Subplot des puissances --------------------------------------
row_puissance, col_puissance = 2, 1
fig.add_trace(go.Scatter(x=df_synthese.index, y=df_synthese['Puissance vue du réseau (MW)'],
mode="lines", name='Puissance vue du réseau (MW)',
legendgroup="grp_puissance",
legendgrouptitle_text="Données en puissance"),
row=row_puissance, col=col_puissance)
# Affichage de la zone "fill between"
fig.add_trace(go.Scatter(x=df_synthese.index, y=df_synthese['Puissance minimale (MW)'],
line=dict(color='rgba(255,255,255,0)'), # Pas de ligne visible
showlegend=False),
row=row_puissance, col=col_puissance)
fig.add_trace(go.Scatter(x=df_synthese.index, y=df_synthese['Puissance maximale (MW)'],
fill='tonexty', fillcolor='rgba(0, 100, 100, 0.2)', # Couleur semi-transparente
line=dict(color='rgba(255,255,255,0)'), # Pas de ligne visible
showlegend=False),
row=row_puissance, col=col_puissance)
fig.update_xaxes(title_text="Données en puissance", row=row_puissance, col=col_puissance)
fig.update_yaxes(title_text="Puissance (MW)", row=row_puissance, col=col_puissance)
# Subplot des energies --------------------------------------
row_energie, col_energie = 3, 1
lst_cols_energie = ['Energie (MWh)',
'Energie en charge vue de la batterie (MWh)',
'Energie en décharge vue de la batterie (MWh)',
'Energie en charge cumulée sur une fenêtre glissante (MWh)',
'Energie en décharge cumulée sur une fenêtre glissante (MWh)']
for col in list(reversed(lst_cols_energie)):
fig.add_trace(go.Scatter(x=df_synthese.index, y=df_synthese[col],
mode="lines", name=col,
legendgroup="grp_energie",
legendgrouptitle_text="Données en énergie"),
row=row_energie, col=col_energie)
fig.update_xaxes(title_text="Données en énergie", row=row_energie, col=col_energie)
fig.update_yaxes(title_text="Energie (MWh)", row=row_energie, col=col_energie)
fig.update_layout(height=900,
legend_groupclick="toggleitem",
# margin=dict(l=50, r=50, t=50, b=50) # Ajuster les marges pour le range slider
)
# Activer le zoom synchronisé
# Il faut d'abord le créer en **non visible** puis le rendre visible
# pour qu'il soit situé comme attendu
fig.update_layout(xaxis=dict(rangeslider=dict(visible=False)))
fig.update_xaxes(rangeslider= {'visible':True},
rangeslider_thickness = 0.025,
row=1, col=1)
# Afficher le graphique interactif dans Streamlit
st.plotly_chart(fig, use_container_width=True)
def display_resultats():
st.header("Optimisation et résultats")
msg_optim = '''
Cet onglet permet de lancer la l'optimisation afin d'obtenir le comportement
**idéal** de la batterie et du reste du site, selon le mode d'optimisation
défini dans l'écran de paramétrage de l'étude.
Par temps favorable, on peut même observer des courbes de résultats et
les télécharger depuis cet écran.
🚨 **ATTENTION** 🚨
Cette optimisation ne peut se faire qu'après avoir validé le paramétrage
de l'étude (écran précédent).
'''
st.markdown(msg_optim)
st.markdown("<br>", unsafe_allow_html=True)
if st.button("Optimisation", type="primary"):
data = validation_parametrage(display_msgs=False,
default_prices=DEFAULT_PRICE,
default_generation=DEFAULT_GENERATION)
if data == None:
st.error('''
**La configuration de l'étude n'est pas valide.**
Veuillez la valider dans l'écran de paramétrage de l'étude.
''')
else:
dico_site, df_chroniques, _ = data
if 'delta_t' in st.session_state:
delta_t = st.session_state['delta_t']
else:
st.error("Le pas de temps des chroniques n'est pas indiqué. Optimisation impossible!")
# Dump des données en entrée
df_chroniques.to_csv("df_chroniques.csv", sep=';')
with open('dico_site.pickle', 'wb') as handle:
pickle.dump(dico_site, handle, protocol=pickle.HIGHEST_PROTOCOL)
# Optimisation !!!
df_optim = None
type_optim = get_optim_by_label(st.session_state['mode_optim'])
if type_optim is None:
st.error("Ce mode n'est pas encore implémenté. Demander à Olivier!")
st.error(st.session_state['mode_optim'])
else:
fonction_optim = type_optim['fonction']
try:
df_optim = fonction_optim(dico_site, df_chroniques, delta_t)
except Exception as e:
st.error(str(e))
# Analyse des résultats
if df_optim is None:
# Pas de solution trouvée
st.warning("Problème infaisable. Pas de solution.")
else:
# Youpi, on a une solutions !
df_optim.to_csv("df_optim.csv", sep=';')
# On vérifie qu'on n'a pas de timesteps pour lesquels on
# charge et on décharge en même temps
if (df_optim.e_charge * df_optim.e_decharge).fillna(0).abs().sum() > 0:
st.error("Résultat d'optimisation incohérent selon Olivier ==> Le contacter avec les fichiers d'entrée.")
else: # Les résultats sont a priori crédibles
# Indicateurs globaux
st.subheader("Indicateurs globaux")
with st.container(height="content", border=True):
col_gains, col_ene = st.columns([1, 1])
with col_gains:
gains_cumules_finaux = df_optim.gains_cumules.iloc[-1]
msg_gains_cumules_finaux = "Les gains obtenus en fin de période de simulation par le site "
msg_gains_cumules_finaux += f"s'élèvent à **{gains_cumules_finaux} €**."
st.info(msg_gains_cumules_finaux)
with col_ene:
energie_spilled_totale = round(df_optim.p_spilled.sum() * delta_t / 60, 2)
msg_energie_spilled_totale = "Le volume total d'ENE sur toute la période de simulation "
msg_energie_spilled_totale += f"est de **{energie_spilled_totale} MWh**."
st.info(msg_energie_spilled_totale)
# Affichage des graphiques
st.subheader("Visualisation des résultats")
with st.container(height="content", border=True):
generer_graphique_puissances(df_optim, dico_site, delta_t)
with st.container(height="content", border=True):
generer_graphique_energies(df_optim, dico_site, delta_t)
with st.container(height="content", border=True):
generer_graphique_benefices(df_optim, dico_site, delta_t)
# Affichage du tableau des résultats
st.subheader("Détail des résultats")
with st.container(height="content", border=True):
st.dataframe(df_optim)