Passed
Pull Request — dev (#1226)
by
unknown
01:59
created

multi_period   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 374
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 4
eloc 153
dl 0
loc 374
rs 10
c 0
b 0
f 0

3 Functions

Rating   Name   Duplication   Size   Complexity  
A determine_periods() 0 24 2
A lifetime_adjusted() 0 2 1
A discount_rate_adjusted() 0 2 1
1
# -*- coding: utf-8 -*-
2
3
"""
4
SPDX-FileCopyrightText: Patrik Schönfeldt
5
SPDX-FileCopyrightText: DLR e.V.
6
7
SPDX-License-Identifier: MIT
8
"""
9
10
import logging
11
import warnings
12
from pathlib import Path
13
14
import numpy as np
15
import pandas as pd
16
import tsam.timeseriesaggregation as tsam
17
from cost_data import investment_costs
18
from matplotlib import pyplot as plt
19
from oemof.tools import debugging
20
from oemof.tools import logger
21
from shared import prepare_input_data
22
23
from oemof import solph
24
from oemof.solph import Bus
25
from oemof.solph import EnergySystem
26
from oemof.solph import Flow
27
from oemof.solph import Investment
28
from oemof.solph import Model
29
from oemof.solph import Results
30
from oemof.solph import components as cmp
31
32
33
def determine_periods(datetimeindex):
34
    """Explicitly define and return periods of the energy system
35
36
    Leap years have 8784 hourly time steps, regular years 8760.
37
38
    Parameters
39
    ----------
40
    datetimeindex : pd.date_range
41
        DatetimeIndex of the model comprising all time steps
42
43
    Returns
44
    -------
45
    periods : list
46
        periods for the optimization run
47
    """
48
    years = sorted(list(set(getattr(datetimeindex, "year"))))
49
    periods = []
50
    filter_series = datetimeindex.to_series()
51
    for number, year in enumerate(years):
52
        start = filter_series.loc[filter_series.index.year == year].min()
53
        end = filter_series.loc[filter_series.index.year == year].max()
54
        periods.append(pd.date_range(start, end, freq=datetimeindex.freq))
55
56
    return periods
57
58
59
warnings.filterwarnings(
60
    "ignore", category=debugging.ExperimentalFeatureWarning
61
)
62
logger.define_logging()
63
64
# ---------- read cost data ------------------------------------------------------------
65
66
investment_costs = investment_costs()
67
68
# ---------- read time series data and resample-----------------------------------------
69
df_temperature, df_energy = prepare_input_data(plot_resampling=False)
70
71
df_temperature = df_temperature.resample("1 h").mean()
72
df_energy = df_energy.resample("1 h").mean()
73
74
time_series_data_full = pd.concat([df_temperature, df_energy], axis=1)
75
76
time_series_data_full = time_series_data_full.drop(
77
    columns=["Air Temperature (°C)", "heat demand (kWh)"]
78
).drop(time_series_data_full.index[0])
79
80
time_index = time_series_data_full.index
81
82
# -------------- Clustering of Input time-series with TSAM -----------------------------
83
typical_periods = 40
84
hours_per_period = 24
85
86
aggregation = tsam.TimeSeriesAggregation(
87
    timeSeries=time_series_data_full.iloc[:8760],
88
    noTypicalPeriods=typical_periods,
89
    hoursPerPeriod=hours_per_period,
90
    clusterMethod="k_means",
91
    sortValues=False,
92
    rescaleClusterPeriods=False,
93
)
94
aggregation.createTypicalPeriods()
95
96
# pandas DatTime for the aggregated time series
97
tindex_agg = pd.date_range(
98
    "2025-01-01", periods=typical_periods * hours_per_period, freq="h"
99
)
100
101
# ------------ create timeindex etc. for multiperiod -----------------------------------
102
# list with years in which investment is possible
103
years = [2025, 2030, 2035, 2040, 2045]
104
# base_year = tindex_agg[0].year
105
106
# # Create a list of shifted copies of the original index, one per investment year
107
# shifted = [tindex_agg + pd.DateOffset(years=(y - base_year)) for y in years]
108
109
# # Concatenate them into one DatetimeIndex
110
# tindex_agg_full = shifted[0]
111
# for s in shifted[1:]:
112
#     tindex_agg_full = tindex_agg_full.append(s)
113
114
tindex_agg_full = pd.date_range(
115
    "2025-01-01",
116
    periods=typical_periods * hours_per_period * len(years),
117
    freq="h",
118
)
119
120
# list of with time index for each year
121
# periods = determine_periods(tindex_agg_full)
122
periods = [tindex_agg] * len(years)
123
124
# parameters for time series aggregation in oemof-solph with one dict per year
125
tsa_parameters = [
126
    {
127
        "timesteps_per_period": aggregation.hoursPerPeriod,
128
        "order": aggregation.clusterOrder,
129
        "timeindex": aggregation.timeIndex,
130
    }
131
] * len(years)
132
133
# # ---------- read time series data -----------------------------------------------------
134
135
# file_path = Path(__file__).parent
136
137
# df = pd.read_csv(
138
#     Path(file_path, "energy.csv"),
139
# )
140
# df["time"] = pd.to_datetime(df["Unix Epoch"], unit="s")
141
# # time als Index setzen
142
# df = df.set_index("time")
143
# df = df.drop(columns=["Unix Epoch"])
144
# # print(df)
145
146
# time_index = df.index
147
148
# # Dummy pv profile
149
# h = np.arange(len(time_index))
150
# pv_profile = df["PV (W)"]
151
152
# # Dummy electricity profile
153
# df["house_elec_kW"] = 0.3 + 0.7 * np.random.rand(len(time_index))
154
155
# # Dummy heat profile
156
# df["house_heat_kW"] = 0.3 + 0.7 * np.random.rand(len(time_index))
157
158
# # EV-Ladeprofil
159
# df["ev_charge_kW"] = (
160
#     0.0  # wird automatisch auf alle Zeitschritte gebroadcastet
161
# )
162
163
# # COP-Profil (konstant, später evtl. temperaturabhängig)
164
# df["cop_hp"] = 3.5
165
166
# df = df.resample("1h").mean()
167
168
# # -------------- Clustering of Input time-series with TSAM -----------------------------
169
# typical_periods = 40
170
# hours_per_period = 24
171
172
# aggregation = tsam.TimeSeriesAggregation(
173
#     timeSeries=df.iloc[:8760],
174
#     noTypicalPeriods=typical_periods,
175
#     hoursPerPeriod=hours_per_period,
176
#     clusterMethod="k_means",
177
#     sortValues=False,
178
#     rescaleClusterPeriods=False,
179
# )
180
# aggregation.createTypicalPeriods()
181
182
# # pandas DatTime for the aggregated time series
183
# tindex_agg_one_year = pd.date_range(
184
#     "2022-01-01", periods=typical_periods * hours_per_period, freq="h"
185
# )
186
187
# # ------------ create timeindex etc. for multiperiod -----------------------------------
188
# # list with years in which investment is possible
189
# years = [2025, 2030, 2035, 2040, 2045]
190
191
# # stretch time index to include all years (continously)
192
# tindex_agg_full = pd.date_range(
193
#     "2022-01-01",
194
#     periods=typical_periods * hours_per_period * len(years),
195
#     freq="h",
196
# )
197
198
# # list of with time index for each year
199
# periods = [tindex_agg_one_year] * len(years)
200
201
# # parameters for time series aggregation in oemof-solph with one dict per year
202
# tsa_parameters = [
203
#     {
204
#         "timesteps_per_period": aggregation.hoursPerPeriod,
205
#         "order": aggregation.clusterOrder,
206
#         "timeindex": aggregation.timeIndex,
207
#     }
208
# ] * len(years)
209
210
# ------------------ calculate discount rate and lifetime ------------------------------
211
212
# the annuity has to be calculated for a period of 5 years
213
investment_period_length_in_years = 5
214
215
216
def lifetime_adjusted(lifetime, investment_period_length_in_years):
217
    return lifetime / investment_period_length_in_years
218
219
220
def discount_rate_adjusted(discount_rate, investment_period_length_in_years):
221
    return (1 + discount_rate) ** investment_period_length_in_years - 1
222
223
224
# ------------------ create energy system ----------------------------------------------
225
es = EnergySystem(
226
    timeindex=tindex_agg_full,
227
    timeincrement=[1] * len(tindex_agg_full),
228
    periods=periods,
229
    tsa_parameters=tsa_parameters,
230
    infer_last_interval=False,
231
)
232
233
234
bus_el = Bus(label="electricity")
235
bus_heat = Bus(label="heat")
236
es.add(bus_el, bus_heat)
237
238
# new_s = pd.concat(
239
#     [aggregation.typicalPeriods["PV (W)"]] * len(years), ignore_index=True
240
# )
241
# print(new_s)
242
pv = cmp.Source(
243
    label="PV",
244
    outputs={
245
        bus_el: Flow(
246
            fix=pd.concat(
247
                [aggregation.typicalPeriods["PV (W)"]] * len(years),
248
                ignore_index=True,
249
            ),
250
            nominal_capacity=Investment(
251
                ep_costs=investment_costs[("pv", "specific_costs [Eur/W]")],
252
                lifetime=lifetime_adjusted(
253
                    50, investment_period_length_in_years
254
                ),
255
                fixed_costs=investment_costs[("pv", "fixed_costs [Eur]")],
256
                maximum=500,
257
            ),
258
        )
259
    },
260
)
261
es.add(pv)
262
263
# Battery
264
battery = cmp.GenericStorage(
265
    label="Battery",
266
    inputs={bus_el: Flow()},
267
    outputs={bus_el: Flow()},
268
    nominal_capacity=Investment(
269
        ep_costs=investment_costs[("battery", "specific_costs [Eur/Wh]")],
270
        lifetime=lifetime_adjusted(50, investment_period_length_in_years),
271
    ),
272
    # kWh
273
    # initial_storage_level=0.5,  # 50%
274
    min_storage_level=0.0,
275
    max_storage_level=1.0,
276
    loss_rate=0.001,  # 0.1%/h
277
    inflow_conversion_factor=0.95,  # Lade-Wirkungsgrad
278
    outflow_conversion_factor=0.95,  # Entlade-Wirkungsgrad
279
)
280
es.add(battery)
281
282
# Electricity demand
283
house_sink = cmp.Sink(
284
    label="Electricity demand",
285
    inputs={
286
        bus_el: Flow(
287
            fix=pd.concat(
288
                [aggregation.typicalPeriods["electricity demand (W)"]]
289
                * len(years),
290
                ignore_index=True,
291
            ),
292
            nominal_capacity=1.0,
293
        )
294
    },
295
)
296
es.add(house_sink)
297
298
# Electric vehicle demand
299
# wallbox_sink = cmp.Sink(
300
#     label="Electric Vehicle",
301
#     inputs={
302
#         bus_el: Flow(
303
#             fix=pd.concat(
304
#                 [aggregation.typicalPeriods["ev_charge_kW"]] * len(years),
305
#                 ignore_index=True,
306
#             ),
307
#             nominal_capacity=1.0,
308
#         )
309
#     },
310
# )
311
# es.add(wallbox_sink)
312
313
# Heat Pump
314
hp = cmp.Converter(
315
    label="Heat pump",
316
    inputs={bus_el: Flow()},
317
    outputs={
318
        bus_heat: Flow(
319
            nominal_capacity=Investment(
320
                ep_costs=investment_costs[
321
                    ("heat pump", "specific_costs [Eur/W]")
322
                ],
323
                lifetime=lifetime_adjusted(
324
                    50, investment_period_length_in_years
325
                ),
326
                fixed_costs=investment_costs[
327
                    ("heat pump", "fixed_costs [Eur]")
328
                ],
329
            )
330
        )
331
    },
332
    conversion_factors={bus_heat: 3.5},
333
)
334
es.add(hp)
335
336
# Heat demand
337
heat_sink = cmp.Sink(
338
    label="Heat demand",
339
    inputs={
340
        bus_heat: Flow(
341
            fix=pd.concat(
342
                [aggregation.typicalPeriods["heat demand (W)"]] * len(years),
343
                ignore_index=True,
344
            ),
345
            nominal_capacity=1.0,
346
        )
347
    },
348
)
349
es.add(heat_sink)
350
351
grid_import = cmp.Source(
352
    label="Grid import", outputs={bus_el: Flow(variable_costs=0.30)}
353
)
354
es.add(grid_import)
355
356
# Grid feed-in
357
feed_in = cmp.Sink(
358
    label="Grid Feed-in", inputs={bus_el: Flow(variable_costs=-0.08)}
359
)
360
es.add(feed_in)
361
362
# Create Model and solve it
363
logging.info("Creating Model...")
364
m = Model(es)
365
logging.info("Solving Model...")
366
m.solve(solver="gurobi", solve_kwargs={"tee": True})
367
368
369
# Create Results
370
results = Results(m)
371
print(results.keys())
372
total = results.total
373
print(total)
374