Skip to content

Commit a83cd2b

Browse files
committed
Merge remote-tracking branch 'origin/p2h'
2 parents 4b6fc93 + 54dcc45 commit a83cd2b

File tree

3 files changed

+154
-42
lines changed

3 files changed

+154
-42
lines changed

dispaset/common.py

+28-12
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,44 @@
2020
# DispaSET fuels:
2121
commons['Fuels'] = ['BIO', 'GAS', 'HRD', 'LIG', 'NUC', 'OIL', 'PEA', 'SUN', 'WAT', 'WIN', 'WST', 'OTH', 'GEO']
2222
# Ordered list of fuels for plotting (the first ones are negative):
23-
commons['MeritOrder'] = ['Storage','FlowOut','GEO','NUC', 'LIG', 'HRD', 'BIO', 'GAS', 'OIL', 'PEA', 'WST', 'OTH', 'SUN', 'WIN', 'FlowIn', 'WAT']
23+
# commons['MeritOrder'] = ['Storage','FlowOut','GEO','NUC', 'LIG', 'HRD', 'BIO', 'GAS', 'OIL', 'PEA', 'WST', 'OTH', 'SUN', 'WIN', 'FlowIn', 'WAT']
24+
commons['MeritOrder'] = ['THMS','BATS','BEVS','HDAM','HPHS','FlowOut','GEO','NUC', 'LIG', 'HRD', 'BIO', 'GAS', 'OIL', 'PEA', 'WST', 'OTH', 'SUN', 'WIN', 'FlowIn', 'WAT']
25+
2426
# Colors associated with each fuel:
25-
commons['colors'] = {'LIG': '#af4b9180', 'PEA': '#af4b9199', 'HRD': '#af4b91b2', 'OIL': '#af4b91ff',
27+
# commons['colors'] = {'LIG': '#af4b9180', 'PEA': '#af4b9199', 'HRD': '#af4b91b2', 'OIL': '#af4b91ff',
28+
# # 'GAS': '#d7642dff',
29+
# # 'NUC': '#466eb4ff',
30+
# # 'SUN': '#e6a532ff',
31+
# # 'WIN': '#41afaaff',
32+
# # 'WAT': '#00a0e1ff',
33+
# # 'BIO': '#7daf4bff', 'GEO': '#7daf4bbf',
34+
# # 'Storage': '#b93c46ff', 'FlowIn': '#b93c46b2', 'FlowOut': '#b93c4666',
35+
# # 'OTH': '#b9c33799', 'WST': '#b9c337ff',
36+
# # 'HDAM': '#00a0e1ff',
37+
# # 'HPHS': '#3090C7ff',
38+
# # 'THMS': '#C04000ff',
39+
# # 'BATS': '#41A317ff',
40+
# # 'BEVS': '#CC80FFff'}
41+
commons['colors'] = {'LIG': '#af4b9180', 'PEA': '#af4b9199', 'HRD': 'darkviolet', 'OIL': 'magenta',
2642
'GAS': '#d7642dff',
2743
'NUC': '#466eb4ff',
2844
'SUN': '#e6a532ff',
2945
'WIN': '#41afaaff',
3046
'WAT': '#00a0e1ff',
3147
'BIO': '#7daf4bff', 'GEO': '#7daf4bbf',
3248
'Storage': '#b93c46ff', 'FlowIn': '#b93c46b2', 'FlowOut': '#b93c4666',
33-
'OTH': '#b9c33799', 'WST': '#b9c337ff'}
49+
'OTH': '#b9c33799', 'WST': '#b9c337ff',
50+
'HDAM': '#00a0e1ff',
51+
'HPHS': 'blue',
52+
'THMS': '#C04000ff',
53+
'BATS': '#41A317ff',
54+
'BEVS': '#b9c33799'}
55+
3456
commons['colors']['curtailment'] = 'red'
3557
# Hatches associated with each fuel:
36-
commons['hatches'] = {'LIG': '', 'PEA': '', 'HRD': '', 'OIL': '',
37-
'GAS': '',
38-
'NUC': '',
39-
'SUN': '',
40-
'WIN': '',
41-
'WAT': '',
42-
'BIO': '', 'GEO': '',
43-
'Storage': '', 'FlowIn': '/', 'FlowOut': '\\',
44-
'WST': '', 'OTH': ''
58+
commons['hatches'] = {'LIG': '', 'PEA': '', 'HRD': '', 'OIL': '', 'GAS': '', 'NUC': '', 'SUN': '', 'WIN': '', 'WAT': '',
59+
'BIO': '', 'GEO': '', 'Storage': '', 'WST': '', 'OTH': '',
60+
'FlowIn': '/', 'FlowOut': '\\', 'HDAM': '/','HPHS': '/','THMS': '','BATS': '/','BEVS': '/'
4561
}
4662

4763
commons['logfile'] = str(datetime.datetime.now()).replace(':','-').replace(' ','_') + '.dispa.log'

dispaset/postprocessing/plot.py

+89-29
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,19 @@
88
from ..misc.str_handler import clean_strings
99
from ..common import commons
1010

11-
from .postprocessing import get_imports, get_plot_data, filter_by_zone
11+
from .postprocessing import get_imports, get_plot_data, filter_by_zone, filter_by_tech, filter_by_storage
1212

1313

14-
def plot_dispatch(demand, plotdata, level=None, curtailment=None, rng=None,
14+
def plot_dispatch(demand, plotdata, level=None, curtailment=None, shedload=None, rng=None,
1515
alpha=None, figsize=(13, 6)):
1616
"""
1717
Function that plots the dispatch data and the reservoir level as a cumulative sum
1818
1919
:param demand: Pandas Series with the demand curve
2020
:param plotdata: Pandas Dataframe with the data to be plotted. Negative columns should be at the beginning. Output of the function GetPlotData
21-
:param level: Optional pandas series with an aggregated reservoir level for the considered zone.
21+
:param level: Optional pandas series/dataframe with an (dis)aggregated reservoir level for the considered zone.
2222
:param curtailment: Optional pandas series with the value of the curtailment
23+
:param shedload: Optional pandas series with the value of the shed load
2324
:param rng: Indexes of the values to be plotted. If undefined, the first week is plotted
2425
"""
2526
import matplotlib.patches as mpatches
@@ -34,7 +35,7 @@ def plot_dispatch(demand, plotdata, level=None, curtailment=None, rng=None,
3435
raise ValueError()
3536
elif rng[0] < plotdata.index[0] or rng[0] > plotdata.index[-1] or rng[-1] < plotdata.index[0] or rng[-1] > \
3637
plotdata.index[-1]:
37-
logging.warn('Plotting range is not properly defined, considering the first simulated week')
38+
logging.warning('Plotting range is not properly defined, considering the first simulated week')
3839
pdrng = plotdata.index[:min(len(plotdata) - 1, 7 * 24)]
3940
else:
4041
pdrng = rng
@@ -74,22 +75,50 @@ def plot_dispatch(demand, plotdata, level=None, curtailment=None, rng=None,
7475

7576
fig.suptitle('Power dispatch for zone ' + demand.name[1])
7677

78+
# Define labels, patches and colors
7779
labels = []
7880
patches = []
7981
colorlist = []
8082

81-
# # Plot negative values:
83+
# Plot reservoir levels (either separated or as one value)
84+
if level is not None:
85+
if isinstance(level, pd.DataFrame):
86+
cols_lvl = level.columns.tolist()
87+
sumplot_lev = level[cols_lvl[0:]].cumsum(axis=1)
88+
sumplot_lev['zero'] = 0
89+
sumplot_lev = sumplot_lev[['zero'] + sumplot_lev.columns[:-1].tolist()]
90+
print('Its dataframe')
91+
for j in range(len(sumplot_lev.columns) - 1):
92+
col3 = sumplot_lev.columns[j]
93+
col4 = sumplot_lev.columns[j + 1]
94+
rez_color = commons['colors'][col4]
95+
rez_hatch = commons['hatches'][col4]
96+
axes[1].fill_between(pdrng, sumplot_lev.loc[pdrng, col3], sumplot_lev.loc[pdrng, col4],
97+
facecolor=rez_color, alpha=alpha, hatch=rez_hatch)
98+
labels.append(col4)
99+
patches.append(mpatches.Patch(facecolor=rez_color, alpha=alpha, hatch=rez_hatch, label=col4))
100+
colorlist.append(rez_color)
101+
elif isinstance(level, pd.Series):
102+
# Create right axis:
103+
axes[1].plot(pdrng, level[pdrng], color='k', alpha=alpha, linestyle=':')
104+
axes[1].fill_between(pdrng, 0, level[pdrng],
105+
facecolor=commons['colors']['WAT'], alpha=.3)
106+
axes[1].set_ylabel('Level [TWh]')
107+
axes[1].yaxis.label.set_fontsize(12)
108+
line_SOC = mlines.Line2D([], [], color='black', alpha=alpha, label='Reservoir', linestyle=':')
109+
110+
# Plot negative values:
82111
for j in range(idx_zero):
83112
col1 = sumplot_neg.columns[j]
84113
col2 = sumplot_neg.columns[j + 1]
85114
color = commons['colors'][col2]
86115
hatch = commons['hatches'][col2]
87116
axes[0].fill_between(pdrng, sumplot_neg.loc[pdrng, col1], sumplot_neg.loc[pdrng, col2], facecolor=color,
88-
alpha=alpha,
89-
hatch=hatch)
90-
labels.append(col1)
91-
patches.append(mpatches.Patch(facecolor=color, alpha=alpha, hatch=hatch, label=col2))
92-
colorlist.append(color)
117+
alpha=alpha, hatch=hatch)
118+
if col2 not in labels:
119+
labels.append(col1)
120+
patches.append(mpatches.Patch(facecolor=color, alpha=alpha, hatch=hatch, label=col2))
121+
colorlist.append(color)
93122

94123
# Plot Positive values:
95124
for j in range(len(sumplot_pos.columns) - 1):
@@ -117,22 +146,32 @@ def plot_dispatch(demand, plotdata, level=None, curtailment=None, rng=None,
117146
axes[0].set_ylabel('Power [GW]')
118147
axes[0].yaxis.label.set_fontsize(12)
119148

120-
if level is not None:
121-
# Create right axis:
122-
axes[1].plot(pdrng, level[pdrng], color='k', alpha=alpha, linestyle=':')
123-
axes[1].fill_between(pdrng, 0, level[pdrng],
124-
facecolor=commons['colors']['WAT'], alpha=.3)
125-
126-
axes[1].set_ylabel('Level [TWh]')
127-
axes[1].yaxis.label.set_fontsize(12)
128-
line_SOC = mlines.Line2D([], [], color='black', alpha=alpha, label='Reservoir', linestyle=':')
149+
if isinstance(shedload, pd.Series):
150+
if not shedload.index.equals(demand.index):
151+
logging.error('The shedload time series must have the same index as the demand')
152+
sys.exit(1)
153+
reduced_demand = demand - shedload
154+
axes[0].plot(pdrng, reduced_demand[pdrng], color='k', alpha=alpha, linestyle='dashed')
155+
line_shedload = mlines.Line2D([], [], color='black', alpha=alpha, label='Shed Load', linestyle='dashed')
129156

130157
line_demand = mlines.Line2D([], [], color='black', label='Load')
131-
plt.legend(handles=[line_demand] + patches[::-1], loc=4)
132-
if level is None:
133-
plt.legend(handles=[line_demand] + patches[::-1], loc=4)
158+
# plt.legend(handles=[line_demand] + patches[::-1], loc=4)
159+
160+
if shedload is None and level is None:
161+
plt.legend(handles=[line_demand] + patches[::-1], loc=4, bbox_to_anchor=(1.2, 0.5))
162+
if shedload is None:
163+
plt.legend(handles=[line_demand] + [line_SOC] + patches[::-1], loc=4, bbox_to_anchor=(1.2, 0.5))
164+
elif level is None:
165+
plt.legend(handles=[line_demand] + [line_shedload] + patches[::-1], loc=4, bbox_to_anchor=(1.2, 0.5))
166+
axes[0].fill_between(demand.index, demand, reduced_demand, facecolor="none", hatch="X", edgecolor="k",
167+
linestyle='dashed')
134168
else:
135-
plt.legend(title='Dispatch for ' + demand.name[1], handles=[line_demand] + [line_SOC] + patches[::-1], loc=4)
169+
plt.legend(title='Dispatch for ' + demand.name[1], handles=[line_demand] + [line_shedload] + [line_SOC] +
170+
patches[::-1], loc=4, bbox_to_anchor=(1.2, 0.5))
171+
axes[0].fill_between(demand.index, demand, reduced_demand, facecolor="none", hatch="X", edgecolor="k",
172+
linestyle='dashed')
173+
174+
plt.subplots_adjust(right=0.8)
136175
plt.show()
137176

138177

@@ -249,6 +288,7 @@ def plot_energy_zone_fuel(inputs, results, PPindicators):
249288
demand = inputs['param_df']['Demand']['DA'].sum() / 1E6
250289
ax.barh(demand, left=ax.get_xticks() - 0.4, width=[0.8] * len(demand), height=ax.get_ylim()[1] * 0.005, linewidth=2,
251290
color='k')
291+
plt.show()
252292
return ax
253293

254294

@@ -274,7 +314,7 @@ def plot_zone_capacities(inputs, plot=True):
274314
PowerCapacity = PowerCapacity[cols]
275315
if plot:
276316
colors = [commons['colors'][tech] for tech in PowerCapacity.columns]
277-
ax = PowerCapacity.plot(kind="bar", figsize=(12, 8), stacked=True, color=colors, alpha=1.0, legend='reverse',
317+
ax = PowerCapacity.plot(kind="bar", figsize=(12, 8), stacked=True, color=colors, alpha=0.8, legend='reverse',
278318
title='Installed capacity per zone (the horizontal lines indicate the peak demand)')
279319
ax.set_ylabel('Capacity [MW]')
280320
demand = inputs['param_df']['Demand']['DA'].max()
@@ -304,9 +344,22 @@ def plot_zone(inputs, results, z='', rng=None, rug_plot=True):
304344

305345
plotdata = get_plot_data(inputs, results, z) / 1000 # GW
306346

347+
aggregation = False
307348
if 'OutputStorageLevel' in results:
308-
level = filter_by_zone(results['OutputStorageLevel'], inputs, z) / 1E6 # TWh
309-
level = level.sum(axis=1)
349+
lev = filter_by_zone(results['OutputStorageLevel'], inputs, z) / 1E6 # TWh
350+
level = filter_by_storage(lev, inputs, StorageSubset='s')
351+
levels = pd.DataFrame(index=results['OutputStorageLevel'].index,columns=inputs['sets']['t'])
352+
for t in ['HDAM','HPHS','BEVS','BATS']:
353+
temp = filter_by_tech(level,inputs,t)
354+
levels[t] = temp.sum(axis=1)
355+
levels.dropna(axis=1,inplace=True)
356+
for col in levels.columns:
357+
if levels[col].max() == 0 and levels[col].min() == 0:
358+
del levels[col]
359+
if aggregation is True:
360+
level = level.sum(axis=1)
361+
else:
362+
level = levels
310363
else:
311364
level = pd.Series(0, index=results['OutputPower'].index)
312365

@@ -324,18 +377,25 @@ def plot_zone(inputs, results, z='', rng=None, rug_plot=True):
324377
# if 'OutputShedLoad' in results:
325378
if 'OutputShedLoad' in results and z in results['OutputShedLoad']:
326379
shed_load = results['OutputShedLoad'][z] / 1000 # GW
380+
shed_load = pd.Series(shed_load, index=demand.index).fillna(0)
327381
else:
328382
shed_load = pd.Series(0, index=demand.index) / 1000 # GW
329383
diff = (sum_generation - demand + shed_load).abs()
330384
if diff.max() > 0.01 * demand.max():
331385
logging.critical('There is up to ' + str(
332386
diff.max() / demand.max() * 100) + '% difference in the instantaneous energy balance of zone ' + z)
333387

334-
if 'OutputCurtailedPower' in results and z in results['OutputCurtailedPower']:
388+
if 'OutputCurtailedPower' in results and z in results['OutputCurtailedPower'] \
389+
and 'OutputShedLoad' in results and z in results['OutputShedLoad']:
390+
curtailment = results['OutputCurtailedPower'][z] / 1000 # GW
391+
plot_dispatch(demand, plotdata, level, curtailment=curtailment, shedload = shed_load, rng=rng, alpha=0.8)
392+
elif 'OutputCurtailedPower' in results and z in results['OutputCurtailedPower']:
335393
curtailment = results['OutputCurtailedPower'][z] / 1000 # GW
336-
plot_dispatch(demand, plotdata, level, curtailment=curtailment, rng=rng)
394+
plot_dispatch(demand, plotdata, level, curtailment=curtailment, rng=rng, alpha=0.8)
395+
elif 'OutputShedLoad' in results and z in results['OutputShedLoad']:
396+
plot_dispatch(demand, plotdata, level, shedload = shed_load, rng=rng, alpha=0.8)
337397
else:
338-
plot_dispatch(demand, plotdata, level, rng=rng)
398+
plot_dispatch(demand, plotdata, level, rng=rng, alpha=0.8)
339399

340400
# Generation plot:
341401
if rug_plot:

dispaset/postprocessing/postprocessing.py

+37-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,34 @@ def filter_by_zone(PowerOutput, inputs, z):
9797
return Power
9898

9999

100+
def filter_by_tech(PowerOutput, inputs, t):
101+
"""
102+
This function filters the dispaset power output dataframe by technology
103+
104+
:param PowerOutput: Dataframe of power generation with units as columns and time as index
105+
:param inputs: Dispaset inputs version 2.1.1
106+
:param t: Selected tech (e.g. 'HDAM')
107+
:returns Power:
108+
"""
109+
loc = inputs['units']['Technology']
110+
Power = PowerOutput.loc[:, [u for u in PowerOutput.columns if loc[u] == t]]
111+
return Power
112+
113+
114+
def filter_by_storage(PowerOutput, Inputs, StorageSubset=None):
115+
"""
116+
This function filters the power generation curves of the different storage units by storage type
117+
118+
:param PowerOutput: Dataframe of power generationwith units as columns and time as index
119+
:param Inputs: Dispaset inputs version 2.1.1
120+
:param SpecifySubset: If not all EES storages should be considered, list containing the relevant ones
121+
:returns PowerByFuel: Dataframe with power generation by fuel
122+
"""
123+
storages = Inputs['sets'][StorageSubset]
124+
Power = PowerOutput.loc[:, PowerOutput.columns.isin(storages)]
125+
return Power
126+
127+
100128
def get_plot_data(inputs, results, z):
101129
"""
102130
Function that reads the results dataframe of a DispaSET simulation and extract the dispatch data spedific to one zone
@@ -112,7 +140,15 @@ def get_plot_data(inputs, results, z):
112140
#onnly take the columns that correspond to storage units (StorageInput is also used for CHP plants):
113141
cols = [col for col in results['OutputStorageInput'] if inputs['units'].loc[col,'Technology'] in commons['tech_storage']]
114142
tmp = filter_by_zone(results['OutputStorageInput'][cols], inputs, z)
115-
plotdata['Storage'] = -tmp.sum(axis=1)
143+
bb = pd.DataFrame()
144+
for tech in commons['tech_storage']:
145+
aa = filter_by_tech(tmp, inputs, tech)
146+
aa = aa.sum(axis=1)
147+
aa = pd.DataFrame(aa,columns=[tech])
148+
bb = pd.concat([bb,aa],axis=1)
149+
bb = -bb
150+
plotdata = pd.concat([plotdata,bb], axis=1)
151+
# plotdata['Storage'] = -tmp.sum(axis=1)
116152
else:
117153
plotdata['Storage'] = 0
118154
plotdata.fillna(value=0, inplace=True)

0 commit comments

Comments
 (0)