Skip to content

Commit ea573dd

Browse files
committed
Add tests for TSAM integration of invest storage and original timeseries
1 parent f59cb85 commit ea573dd

File tree

2 files changed

+392
-0
lines changed

2 files changed

+392
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
General description:
5+
---------------------
6+
7+
The example models the following energy system:
8+
9+
input/output bel
10+
| |
11+
wind(FixedSource) |--------->|
12+
| |
13+
demand(Sink) |<---------|
14+
| |
15+
storage(Storage) |<---------|
16+
|--------->|
17+
18+
19+
20+
An initial SOC of zero leads to infeasible solution, as last inter SOC has to match first inter SOC.
21+
Following equations have to be fulfilled:
22+
F_{el,st}[0] = F_{el,st}[6]
23+
SOC_{init} * discharge + F_{el,st}[0] = \sum_{i=1}^{n=5}F_{st,el}[i]/eff_{out}/(1 - discharge)^i
24+
F_{el,st}[6] = (SOC_{init} + F_{el,st}[5]/eff_{out}) / (1 - discharge)
25+
26+
This file is part of project oemof (github.com/oemof/oemof). It's copyrighted
27+
by the contributors recorded in the version control history of the file,
28+
available from its original location oemof/tests/test_scripts/test_solph/
29+
test_storage_investment/test_storage_investment.py
30+
31+
SPDX-License-Identifier: MIT
32+
"""
33+
34+
import logging
35+
import pandas as pd
36+
import pytest
37+
38+
from oemof.tools import logger
39+
from oemof.tools import economics
40+
from oemof import solph
41+
42+
##########################################################################
43+
# Initialize the energy system and read/calculate necessary parameters
44+
##########################################################################
45+
46+
logger.define_logging()
47+
logging.info("Initialize the energy system")
48+
49+
tindex_original = pd.date_range("2022-01-01", periods=8, freq="H")
50+
tindex = pd.date_range("2022-01-01", periods=4, freq="H")
51+
52+
energysystem = solph.EnergySystem(
53+
timeindex=tindex,
54+
timeincrement=[1] * len(tindex),
55+
periods=[tindex],
56+
tsa_parameters=[
57+
{
58+
"timesteps_per_period": 2,
59+
"order": [0, 1, 1, 0],
60+
"occurrences": {0: 2, 1: 2},
61+
"timeindex": tindex_original,
62+
},
63+
],
64+
infer_last_interval=False,
65+
)
66+
67+
##########################################################################
68+
# Create oemof objects
69+
##########################################################################
70+
71+
logging.info("Create oemof objects")
72+
73+
# create electricity bus
74+
bel = solph.Bus(label="electricity")
75+
energysystem.add(bel)
76+
77+
# create fixed source object representing wind power plants
78+
wind = solph.components.Source(
79+
label="wind",
80+
outputs={bel: solph.Flow(fix=[1000, 0, 0, 50], nominal_value=1)},
81+
)
82+
83+
# create simple sink object representing the electrical demand
84+
demand = solph.components.Sink(
85+
label="demand",
86+
inputs={
87+
bel: solph.Flow(
88+
fix=[100] * 4,
89+
nominal_value=1,
90+
)
91+
},
92+
)
93+
94+
# create storage object representing a battery
95+
epc = economics.annuity(capex=1000, n=20, wacc=0.05)
96+
storage = solph.components.GenericStorage(
97+
label="storage",
98+
inputs={bel: solph.Flow(lifetime=20)},
99+
outputs={bel: solph.Flow(lifetime=20)},
100+
investment=solph.Investment(ep_costs=epc, lifetime=20),
101+
loss_rate=0.01,
102+
inflow_conversion_factor=0.9,
103+
outflow_conversion_factor=0.8,
104+
)
105+
106+
excess = solph.components.Sink(
107+
label="excess",
108+
inputs={bel: solph.Flow()},
109+
)
110+
111+
energysystem.add(wind, demand, storage, excess)
112+
113+
##########################################################################
114+
# Optimise the energy system
115+
##########################################################################
116+
117+
logging.info("Optimise the energy system")
118+
119+
# initialise the operational model
120+
om = solph.Model(energysystem)
121+
122+
# if tee_switch is true solver messages will be displayed
123+
logging.info("Solve the optimization problem")
124+
om.solve(solver="cbc", solve_kwargs={"tee": True})
125+
126+
##########################################################################
127+
# Check and plot the results
128+
##########################################################################
129+
130+
# check if the new result object is working for custom components
131+
results = solph.processing.results(om)
132+
133+
# Concatenate flows:
134+
flows = pd.concat([flow["sequences"] for flow in results.values()], axis=1)
135+
flows.columns = [
136+
f"{oemof_tuple[0]}-{oemof_tuple[1]}" for oemof_tuple in results.keys()
137+
]
138+
139+
first_input = (
140+
(100 * 1 / 0.8) / (1 - 0.01)
141+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
142+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3
143+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 4
144+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 5
145+
)
146+
# In this example SOC can e zero, as last SOC does not have to be equal
147+
# to first SOC in investment mode (maybe this should be changed?)
148+
init_soc = 0
149+
150+
151+
def test_storage_investment():
152+
"""Make sure that max SOC investment equals max load"""
153+
assert results[storage, None]["period_scalars"]["invest"].iloc[0] == pytest.approx(first_input)
154+
155+
156+
def test_storage_input():
157+
assert flows["electricity-storage"][0] == pytest.approx((first_input - 0.99 * init_soc) / 0.9)
158+
assert flows["electricity-storage"][1] == 0
159+
assert flows["electricity-storage"][2] == 0
160+
assert flows["electricity-storage"][3] == 0
161+
assert flows["electricity-storage"][4] == 0
162+
assert flows["electricity-storage"][5] == 0
163+
assert flows["electricity-storage"][6] == flows["electricity-storage"][0]
164+
assert flows["electricity-storage"][7] == 0
165+
166+
167+
def test_storage_output():
168+
assert flows["storage-electricity"][0] == 0
169+
assert flows["storage-electricity"][1] == 100
170+
assert flows["storage-electricity"][2] == 100
171+
assert flows["storage-electricity"][3] == 50
172+
assert flows["storage-electricity"][4] == 100
173+
assert flows["storage-electricity"][5] == 50
174+
assert flows["storage-electricity"][6] == 0
175+
assert flows["storage-electricity"][7] == 100
176+
177+
178+
def test_soc():
179+
assert flows["storage-None"][0] == pytest.approx(init_soc)
180+
assert flows["storage-None"][1] == pytest.approx(
181+
(100 * 1 / 0.8) / (1 - 0.01)
182+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
183+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3
184+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 4
185+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 5,
186+
abs=1e-2,
187+
)
188+
assert flows["storage-None"][2] == pytest.approx(
189+
(100 * 1 / 0.8) / (1 - 0.01)
190+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 2
191+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 3
192+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 4,
193+
abs=1e-2,
194+
)
195+
assert flows["storage-None"][3] == pytest.approx(
196+
(50 * 1 / 0.8) / (1 - 0.01)
197+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
198+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3,
199+
abs=1e-2,
200+
)
201+
assert flows["storage-None"][4] == pytest.approx(
202+
(100 * 1 / 0.8) / (1 - 0.01) + (50 * 1 / 0.8) / (1 - 0.01) ** 2,
203+
abs=1e-2,
204+
)
205+
assert flows["storage-None"][5] == pytest.approx(
206+
(50 * 1 / 0.8) / (1 - 0.01), abs=1e-2
207+
)
208+
assert flows["storage-None"][6] == pytest.approx(0, abs=1e-2)
209+
assert flows["storage-None"][7] == pytest.approx(first_input)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# -*- coding: utf-8 -*-
2+
3+
"""
4+
General description:
5+
---------------------
6+
7+
The example models the the same setup as in test_storage_tsam_integration,
8+
but instead of aggregating timeseries, timeseries are left untouched.
9+
Nevertheless, storage input/output and SOC should equal TSAM example.
10+
11+
This file is part of project oemof (github.com/oemof/oemof). It's copyrighted
12+
by the contributors recorded in the version control history of the file,
13+
available from its original location oemof/tests/test_scripts/test_solph/
14+
test_storage_investment/test_storage_investment.py
15+
16+
SPDX-License-Identifier: MIT
17+
"""
18+
19+
import logging
20+
import pandas as pd
21+
import pytest
22+
23+
from oemof.tools import logger
24+
from oemof import solph
25+
26+
##########################################################################
27+
# Initialize the energy system and read/calculate necessary parameters
28+
##########################################################################
29+
30+
logger.define_logging()
31+
logging.info("Initialize the energy system")
32+
33+
tindex = pd.date_range("2022-01-01", periods=8, freq="H")
34+
35+
energysystem = solph.EnergySystem(
36+
timeindex=tindex,
37+
infer_last_interval=True,
38+
)
39+
40+
##########################################################################
41+
# Create oemof objects
42+
##########################################################################
43+
44+
logging.info("Create oemof objects")
45+
46+
# create electricity bus
47+
bel = solph.Bus(label="electricity")
48+
energysystem.add(bel)
49+
50+
# create fixed source object representing wind power plants
51+
wind = solph.components.Source(
52+
label="wind",
53+
outputs={
54+
bel: solph.Flow(fix=[1000, 0, 0, 50, 0, 50, 1000, 0], nominal_value=1)
55+
},
56+
)
57+
58+
# create simple sink object representing the electrical demand
59+
demand = solph.components.Sink(
60+
label="demand",
61+
inputs={
62+
bel: solph.Flow(
63+
fix=[100] * 8,
64+
nominal_value=1,
65+
)
66+
},
67+
)
68+
69+
# Solving equations from above, needed initial SOC is as follows:
70+
first_input = (
71+
(100 * 1 / 0.8) / (1 - 0.01)
72+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
73+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3
74+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 4
75+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 5
76+
)
77+
last_output = (100 * 1 / 0.8) / 0.99
78+
init_soc = (first_input - last_output) / (1 / 0.99 + 0.99)
79+
80+
# create storage object representing a battery
81+
storage = solph.components.GenericStorage(
82+
label="storage",
83+
nominal_storage_capacity=2000,
84+
initial_storage_level=init_soc / 2000,
85+
inputs={bel: solph.Flow()},
86+
outputs={bel: solph.Flow()},
87+
loss_rate=0.01,
88+
inflow_conversion_factor=0.9,
89+
outflow_conversion_factor=0.8,
90+
)
91+
92+
excess = solph.components.Sink(
93+
label="excess",
94+
inputs={bel: solph.Flow()},
95+
)
96+
97+
energysystem.add(wind, demand, storage, excess)
98+
99+
##########################################################################
100+
# Optimise the energy system
101+
##########################################################################
102+
103+
logging.info("Optimise the energy system")
104+
105+
# initialise the operational model
106+
om = solph.Model(energysystem)
107+
108+
# if tee_switch is true solver messages will be displayed
109+
logging.info("Solve the optimization problem")
110+
om.solve(solver="cbc", solve_kwargs={"tee": True})
111+
112+
##########################################################################
113+
# Check and plot the results
114+
##########################################################################
115+
116+
# check if the new result object is working for custom components
117+
results = solph.processing.results(om)
118+
119+
# Concatenate flows:
120+
flows = pd.concat([flow["sequences"] for flow in results.values()], axis=1)
121+
flows.columns = [
122+
f"{oemof_tuple[0]}-{oemof_tuple[1]}" for oemof_tuple in results.keys()
123+
]
124+
125+
126+
def test_storage_input():
127+
assert flows["electricity-storage"][0] == pytest.approx(
128+
(first_input - 0.99 * init_soc) / 0.9
129+
)
130+
assert flows["electricity-storage"][1] == 0
131+
assert flows["electricity-storage"][2] == 0
132+
assert flows["electricity-storage"][3] == 0
133+
assert flows["electricity-storage"][4] == 0
134+
assert flows["electricity-storage"][5] == 0
135+
assert flows["electricity-storage"][6] == flows["electricity-storage"][0]
136+
assert flows["electricity-storage"][7] == 0
137+
138+
139+
def test_storage_output():
140+
assert flows["storage-electricity"][0] == 0
141+
assert flows["storage-electricity"][1] == 100
142+
assert flows["storage-electricity"][2] == 100
143+
assert flows["storage-electricity"][3] == 50
144+
assert flows["storage-electricity"][4] == 100
145+
assert flows["storage-electricity"][5] == 50
146+
assert flows["storage-electricity"][6] == 0
147+
assert flows["storage-electricity"][7] == 100
148+
149+
150+
def test_soc():
151+
assert flows["storage-None"][0] == pytest.approx(init_soc)
152+
assert flows["storage-None"][1] == pytest.approx(
153+
(100 * 1 / 0.8) / (1 - 0.01)
154+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
155+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3
156+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 4
157+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 5,
158+
abs=1e-2,
159+
)
160+
assert flows["storage-None"][2] == pytest.approx(
161+
(100 * 1 / 0.8) / (1 - 0.01)
162+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 2
163+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 3
164+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 4,
165+
abs=1e-2,
166+
)
167+
assert flows["storage-None"][3] == pytest.approx(
168+
(50 * 1 / 0.8) / (1 - 0.01)
169+
+ (100 * 1 / 0.8) / (1 - 0.01) ** 2
170+
+ (50 * 1 / 0.8) / (1 - 0.01) ** 3,
171+
abs=1e-2,
172+
)
173+
assert flows["storage-None"][4] == pytest.approx(
174+
(100 * 1 / 0.8) / (1 - 0.01) + (50 * 1 / 0.8) / (1 - 0.01) ** 2,
175+
abs=1e-2,
176+
)
177+
assert flows["storage-None"][5] == pytest.approx(
178+
(50 * 1 / 0.8) / (1 - 0.01), abs=1e-2
179+
)
180+
assert flows["storage-None"][6] == pytest.approx(0, abs=1e-2)
181+
assert flows["storage-None"][7] == pytest.approx(
182+
(init_soc + (100 * 1 / 0.8)) / 0.99
183+
)

0 commit comments

Comments
 (0)