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

multi_period   A

Complexity

Total Complexity 0

Size/Duplication

Total Lines 204
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 0
eloc 121
dl 0
loc 204
rs 10
c 0
b 0
f 0
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
from matplotlib import pyplot as plt
17
from oemof.tools import debugging
18
from oemof.tools import logger
19
20
from oemof.solph import Bus
21
from oemof.solph import EnergySystem
22
from oemof.solph import Flow
23
from oemof.solph import Investment
24
from oemof.solph import Model
25
from oemof.solph import Results
26
from oemof.solph import components as cmp
27
28
warnings.filterwarnings(
29
    "ignore", category=debugging.ExperimentalFeatureWarning
30
)
31
logger.define_logging()
32
33
file_path = Path(__file__).parent
34
35
36
# Load CSV file and parse time column as datetime index
37
df = pd.read_csv(
38
    Path(file_path, "input_data.csv"),
39
    parse_dates=["time"],
40
    index_col="time",
41
)
42
print(df)
43
df = df.fillna(0)
44
45
# Initial time index from the input data
46
initial_index = df.index
47
print(f"Initial index length: {len(initial_index)}")
48
49
# Number of additional years to add
50
years = 2  # Example: add 2 more years
51
52
# Define original start and calculate one-year duration
53
original_start = initial_index[0]
54
one_year = pd.DateOffset(years=1)
55
56
# Build periods: each starts at same time in different years, ends one hour before next year
57
periods = []
58
for y in range(years + 1):  # include original year
59
    start = original_start + pd.DateOffset(years=y)
60
    end = start + one_year - pd.Timedelta(hours=1)
61
    periods.append(pd.date_range(start, end, freq="h"))
62
63
# Combine all periods into one long DatetimeIndex
64
long_time_index = pd.DatetimeIndex(np.concatenate(periods))
65
66
print(f"Total length: {len(long_time_index)}")
67
print("First period:", periods[0][:5], "...", periods[0][-5:])
68
print("Second period:", periods[1][:5], "...", periods[1][-5:])
69
70
# --- Stretch data to match new index ---
71
pv_profile_one_year = df["PV (W)"].values
72
original_len = len(pv_profile_one_year)
73
new_len = len(long_time_index)
74
75
repeat_factor = int(np.ceil(new_len / original_len))
76
pv_profile_stretched = np.tile(pv_profile_one_year, repeat_factor)[:new_len]
77
78
pv_profile = pd.Series(pv_profile_stretched, index=long_time_index)
79
80
# Dummy profiles
81
house_elec_kw = pd.Series(
82
    0.3 + 0.7 * np.random.rand(len(long_time_index)), index=long_time_index
83
)
84
house_heat_kw = pd.Series(
85
    0.3 + 0.7 * np.random.rand(len(long_time_index)), index=long_time_index
86
)
87
ev_charge_kW = pd.Series(0.0, index=long_time_index)
88
cop_hp = pd.Series(3.5, index=long_time_index)
89
90
print(pv_profile.head())
91
print(pv_profile.tail())
92
93
94
es = EnergySystem(timeindex=long_time_index, periods=periods)
95
96
97
bus_el = Bus(label="electricity")
98
bus_heat = Bus(label="heat")
99
es.add(bus_el, bus_heat)
100
101
pv = cmp.Source(
102
    label="PV",
103
    outputs={
104
        bus_el: Flow(
105
            fix=pv_profile,
106
            nominal_capacity=Investment(
107
                ep_costs=[400, 380, 350],
108
                lifetime=10,
109
            ),
110
        )
111
    },
112
)
113
es.add(pv)
114
115
# Battery
116
battery = cmp.GenericStorage(
117
    label="Battery",
118
    inputs={bus_el: Flow()},
119
    outputs={bus_el: Flow()},
120
    nominal_capacity=Investment(
121
        ep_costs=[800, 700, 600],
122
        lifetime=10,
123
    ),  # kWh
124
    # initial_storage_level=0.5,  # 50%
125
    min_storage_level=0.0,
126
    max_storage_level=1.0,
127
    loss_rate=0.001,  # 0.1%/h
128
    inflow_conversion_factor=0.95,  # Lade-Wirkungsgrad
129
    outflow_conversion_factor=0.95,  # Entlade-Wirkungsgrad
130
)
131
es.add(battery)
132
133
# Electricity demand
134
house_sink = cmp.Sink(
135
    label="Electricity demand",
136
    inputs={bus_el: Flow(fix=house_elec_kw, nominal_capacity=1.0)},
137
)
138
es.add(house_sink)
139
140
# Electric vehicle demand
141
wallbox_sink = cmp.Sink(
142
    label="Electric Vehicle",
143
    inputs={bus_el: Flow(fix=ev_charge_kW, nominal_capacity=1.0)},
144
)
145
es.add(wallbox_sink)
146
147
# Heat Pump
148
hp = cmp.Converter(
149
    label="Heat pump",
150
    inputs={bus_el: Flow()},
151
    outputs={
152
        bus_heat: Flow(
153
            nominal_capacity=Investment(ep_costs=[500, 400, 300], lifetime=20)
154
        )
155
    },
156
    conversion_factors={bus_heat: cop_hp},
157
)
158
es.add(hp)
159
160
# Heat demand
161
heat_sink = cmp.Sink(
162
    label="Heat demand",
163
    inputs={bus_heat: Flow(fix=house_elec_kw, nominal_capacity=5.0)},
164
)
165
es.add(heat_sink)
166
167
grid_import = cmp.Source(
168
    label="Grid import", outputs={bus_el: Flow(variable_costs=0.30)}
169
)
170
es.add(grid_import)
171
172
# Grid feed-in
173
feed_in = cmp.Sink(
174
    label="Grid Feed-in", inputs={bus_el: Flow(variable_costs=-0.08)}
175
)
176
es.add(feed_in)
177
178
# debugging
179
180
# Check for NaN in input data
181
print("----------debugging------")
182
print("df: ", df.isna().sum())  # For original data
183
print("pv: ", pv_profile.isna().sum())  # For stretched profile
184
print("el: ", house_elec_kw.isna().sum())
185
print("heat: ", house_heat_kw.isna().sum())
186
187
# Check length consistency
188
print("Check length consistency")
189
print(
190
    len(long_time_index),
191
    len(pv_profile),
192
    len(house_elec_kw),
193
    len(house_heat_kw),
194
)
195
196
197
# Create Model and solve it
198
logging.info("Creating Model...")
199
m = Model(es)
200
logging.info("Solving Model...")
201
m.solve(solver="gurobi", solve_kwargs={"tee": True})
202
203
"""
204
# Create Results
205
results = Results(m)
206
flow = results.flow
207
soc = results.storage_content
208
soc.name = "Battery SOC [kWh]"
209
investments = results.invest.rename(
210
    columns={
211
        c: c[0].label for c in results.invest.columns if isinstance(c, tuple)
212
    },
213
)
214
215
print("Energy Balance")
216
print(flow.sum())
217
print("")
218
print("Investment")
219
print(investments.squeeze())
220
221
investments.squeeze().plot(kind="bar")
222
223
day = 186  # day of the year
224
n = 2  # number of days to plot
225
flow = flow[day * 24 * 6 : day * 24 * 6 + n * 24 * 6]
226
soc = soc[day * 24 * 6 : day * 24 * 6 + 48 * 6]
227
228
supply = flow[[c for c in flow.columns if c[1].label == "electricity"]]
229
supply = supply.droplevel(1, axis=1)
230
supply.rename(columns={c: c.label for c in supply.columns}, inplace=True)
231
demand = flow[[c for c in flow.columns if c[0].label == "electricity"]]
232
demand = demand.droplevel(0, axis=1)
233
demand.rename(columns={c: c.label for c in demand.columns}, inplace=True)
234
235
# A plot from GPT :-)
236
fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
237
# Top: Electricity bus — supply vs. demand (negative stack), net balance
238
sup_handles = axes[0].stackplot(
239
    supply.index,
240
    *[supply[c] for c in supply.columns],
241
    labels=list(supply.columns),
242
    alpha=0.8,
243
)
244
dem_handles = axes[0].stackplot(
245
    demand.index,
246
    *[-demand[c] for c in demand.columns],
247
    labels=list(demand.columns),
248
    alpha=0.7,
249
)
250
251
net = supply.sum(axis=1) - demand.sum(axis=1)
252
(net_line,) = axes[0].plot(
253
    net.index, net, color="k", linewidth=1.3, label="Net balance"
254
)
255
axes[0].axhline(0, color="gray", linestyle="--", linewidth=0.8)
256
axes[0].set_ylabel("Power [kW]")
257
axes[0].set_title("Electricity bus: supply (positive) vs demand (negative)")
258
259
# Legend combining both stacks and net line
260
handles = sup_handles + dem_handles + [net_line]
261
labels = list(supply.columns) + list(demand.columns) + ["Net balance"]
262
axes[0].legend(handles, labels, ncol=2, fontsize=9, loc="upper left")
263
264
# Optional: overlay SOC on right axis
265
if soc is not None:
266
    ax2 = axes[0].twinx()
267
    ax2.plot(
268
        soc.index, soc, color="tab:purple", linewidth=1.2, label="Battery SOC"
269
    )
270
    ax2.set_ylabel("Energy [kWh]")
271
    ax2.legend(loc="upper right")
272
273
# Bottom: Heat — HP output vs heat demand and unmet heat area
274
hp_heat = flow[[c for c in flow.columns if c[0].label == "heat"]].squeeze()
275
heat_dem = flow[[c for c in flow.columns if c[1].label == "heat"]].squeeze()
276
277
axes[1].plot(hp_heat.index, hp_heat, label="HP heat output", linewidth=2)
278
axes[1].plot(
279
    heat_dem.index, heat_dem, label="Heat demand", linewidth=2, linestyle="--"
280
)
281
axes[1].fill_between(
282
    heat_dem.index,
283
    hp_heat,
284
    heat_dem,
285
    where=(heat_dem > hp_heat),
286
    color="tab:red",
287
    alpha=0.2,
288
    label="Unmet heat",
289
)
290
axes[1].set_ylabel("Heat [kW]")
291
axes[1].set_title("Heat bus")
292
axes[1].legend(loc="upper left")
293
axes[1].set_xlabel("Time")
294
295
plt.tight_layout()
296
plt.show()
297
"""
298