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

time_series_equidistant   A

Complexity

Total Complexity 0

Size/Duplication

Total Lines 233
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 0
eloc 151
dl 0
loc 233
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
df = pd.read_csv(
36
    Path(file_path, "input_data.csv"),
37
    parse_dates=["time"],
38
    index_col="time",
39
)
40
print(df)
41
42
time_index = df.index
43
44
# Dummy pv profile
45
h = np.arange(len(time_index))
46
pv_profile = df["PV (W)"]
47
48
# Dummy electricity profile
49
house_elec_kw = pd.Series(
50
    0.3 + 0.7 * np.random.rand(len(time_index)), index=time_index
51
)
52
53
# Dummy heat profile
54
house_heat_kw = pd.Series(
55
    0.3 + 0.7 * np.random.rand(len(time_index)), index=time_index
56
)
57
58
ev_charge_kW = pd.Series(0.0, index=time_index)
59
60
# COP constant value by, but will be replaced by a temperature-depending
61
# profile
62
cop_hp = pd.Series(3.5, index=time_index)
63
64
es = EnergySystem(timeindex=time_index)
65
66
bus_el = Bus(label="electricity")
67
bus_heat = Bus(label="heat")
68
es.add(bus_el, bus_heat)
69
70
pv = cmp.Source(
71
    label="PV",
72
    outputs={
73
        bus_el: Flow(fix=pv_profile, nominal_capacity=Investment(ep_costs=400))
74
    },
75
)
76
es.add(pv)
77
78
# Battery
79
battery = cmp.GenericStorage(
80
    label="Battery",
81
    inputs={bus_el: Flow()},
82
    outputs={bus_el: Flow()},
83
    nominal_capacity=Investment(ep_costs=500),  # kWh
84
    initial_storage_level=0.5,  # 50%
85
    min_storage_level=0.0,
86
    max_storage_level=1.0,
87
    loss_rate=0.001,  # 0.1%/h
88
    inflow_conversion_factor=0.95,  # Lade-Wirkungsgrad
89
    outflow_conversion_factor=0.95,  # Entlade-Wirkungsgrad
90
)
91
es.add(battery)
92
93
# Electricity demand
94
house_sink = cmp.Sink(
95
    label="Electricity demand",
96
    inputs={bus_el: Flow(fix=house_elec_kw, nominal_capacity=1.0)},
97
)
98
es.add(house_sink)
99
100
# Electric vehicle demand
101
wallbox_sink = cmp.Sink(
102
    label="Electric Vehicle",
103
    inputs={bus_el: Flow(fix=ev_charge_kW, nominal_capacity=1.0)},
104
)
105
es.add(wallbox_sink)
106
107
# Heat Pump
108
hp = cmp.Converter(
109
    label="Heat pump",
110
    inputs={bus_el: Flow()},
111
    outputs={bus_heat: Flow(nominal_capacity=Investment(ep_costs=500))},
112
    conversion_factors={bus_heat: cop_hp},
113
)
114
es.add(hp)
115
116
# Heat demand
117
heat_sink = cmp.Sink(
118
    label="Heat demand",
119
    inputs={bus_heat: Flow(fix=house_elec_kw, nominal_capacity=5.0)},
120
)
121
es.add(heat_sink)
122
123
grid_import = cmp.Source(
124
    label="Grid import", outputs={bus_el: Flow(variable_costs=0.30)}
125
)
126
es.add(grid_import)
127
128
# Grid feed-in
129
feed_in = cmp.Sink(
130
    label="Grid Feed-in", inputs={bus_el: Flow(variable_costs=-0.08)}
131
)
132
es.add(feed_in)
133
134
# Create Model and solve it
135
logging.info("Creating Model...")
136
m = Model(es)
137
logging.info("Solving Model...")
138
m.solve(solver="cbc", solve_kwargs={"tee": False})
139
140
# Create Results
141
results = Results(m)
142
flow = results.flow
143
soc = results.storage_content
144
soc.name = "Battery SOC [kWh]"
145
investments = results.invest.rename(
146
    columns={
147
        c: c[0].label for c in results.invest.columns if isinstance(c, tuple)
148
    },
149
)
150
151
print("Energy Balance")
152
print(flow.sum())
153
print("")
154
print("Investment")
155
print(investments.squeeze())
156
157
investments.squeeze().plot(kind="bar")
158
159
day = 186  # day of the year
160
n = 2  # number of days to plot
161
flow = flow[day * 24 * 6 : day * 24 * 6 + n * 24 * 6]
162
soc = soc[day * 24 * 6 : day * 24 * 6 + 48 * 6]
163
164
supply = flow[[c for c in flow.columns if c[1].label == "electricity"]]
165
supply = supply.droplevel(1, axis=1)
166
supply.rename(columns={c: c.label for c in supply.columns}, inplace=True)
167
demand = flow[[c for c in flow.columns if c[0].label == "electricity"]]
168
demand = demand.droplevel(0, axis=1)
169
demand.rename(columns={c: c.label for c in demand.columns}, inplace=True)
170
171
# A plot from GPT :-)
172
fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
173
# Top: Electricity bus — supply vs. demand (negative stack), net balance
174
sup_handles = axes[0].stackplot(
175
    supply.index,
176
    *[supply[c] for c in supply.columns],
177
    labels=list(supply.columns),
178
    alpha=0.8,
179
)
180
dem_handles = axes[0].stackplot(
181
    demand.index,
182
    *[-demand[c] for c in demand.columns],
183
    labels=list(demand.columns),
184
    alpha=0.7,
185
)
186
187
net = supply.sum(axis=1) - demand.sum(axis=1)
188
(net_line,) = axes[0].plot(
189
    net.index, net, color="k", linewidth=1.3, label="Net balance"
190
)
191
axes[0].axhline(0, color="gray", linestyle="--", linewidth=0.8)
192
axes[0].set_ylabel("Power [kW]")
193
axes[0].set_title("Electricity bus: supply (positive) vs demand (negative)")
194
195
# Legend combining both stacks and net line
196
handles = sup_handles + dem_handles + [net_line]
197
labels = list(supply.columns) + list(demand.columns) + ["Net balance"]
198
axes[0].legend(handles, labels, ncol=2, fontsize=9, loc="upper left")
199
200
# Optional: overlay SOC on right axis
201
if soc is not None:
202
    ax2 = axes[0].twinx()
203
    ax2.plot(
204
        soc.index, soc, color="tab:purple", linewidth=1.2, label="Battery SOC"
205
    )
206
    ax2.set_ylabel("Energy [kWh]")
207
    ax2.legend(loc="upper right")
208
209
# Bottom: Heat — HP output vs heat demand and unmet heat area
210
hp_heat = flow[[c for c in flow.columns if c[0].label == "heat"]].squeeze()
211
heat_dem = flow[[c for c in flow.columns if c[1].label == "heat"]].squeeze()
212
213
axes[1].plot(hp_heat.index, hp_heat, label="HP heat output", linewidth=2)
214
axes[1].plot(
215
    heat_dem.index, heat_dem, label="Heat demand", linewidth=2, linestyle="--"
216
)
217
axes[1].fill_between(
218
    heat_dem.index,
219
    hp_heat,
220
    heat_dem,
221
    where=(heat_dem > hp_heat),
222
    color="tab:red",
223
    alpha=0.2,
224
    label="Unmet heat",
225
)
226
axes[1].set_ylabel("Heat [kW]")
227
axes[1].set_title("Heat bus")
228
axes[1].legend(loc="upper left")
229
axes[1].set_xlabel("Time")
230
231
plt.tight_layout()
232
plt.show()
233