Skip to content

Commit

Permalink
Merge pull request #962 from oemof/features/storage_costs
Browse files Browse the repository at this point in the history
Add storage_costs to GenericStorage
  • Loading branch information
p-snft committed Aug 11, 2023
2 parents 3006fa7 + db8e499 commit d89ef93
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 2 deletions.
143 changes: 143 additions & 0 deletions examples/storage_costs/storage_costs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-

"""
General description
-------------------
Example that shows the parameter `storage_costs` of `GenericStorage`.
A battery is used to make profit from fluctuating electricity prices.
For a battery without storage costs, it is beneficial to be empty
the end of the time horizon of the optimisation. For a battery that
assumes the average revenue, energy is kept at the end.
Installation requirements
-------------------------
This example requires oemof.solph (v0.5.x), install by:
pip install oemof.solph[examples]
License
-------
`MIT license <https://github.com/oemof/oemof-solph/blob/dev/LICENSE>`_
"""

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt

from oemof import solph


def storage_costs_example():
# create an energy system
idx = pd.date_range("1/1/2023", periods=13, freq="H")
es = solph.EnergySystem(timeindex=idx, infer_last_interval=False)

# power bus
bel = solph.Bus(label="bel")
es.add(bel)

es.add(
solph.components.Source(
label="source_el",
outputs={bel: solph.Flow()},
)
)

es.add(
solph.components.Sink(
label="sink_el",
inputs={bel: solph.Flow()},
)
)

electricity_price = np.array(
[
0.38,
0.31,
0.32,
0.33,
0.37,
0.32,
0.33,
0.34,
0.39,
0.38,
0.37,
0.35,
0.35,
]
)

# Electric Storage 1
# Costs are designed in a way that storing energy is benificial until the
# last four time steps but emptying it is not a good option.
battery1 = solph.components.GenericStorage(
label="battery 1",
nominal_storage_capacity=10,
inputs={
bel: solph.Flow(
nominal_value=1,
variable_costs=electricity_price,
)
},
outputs={
bel: solph.Flow(
nominal_value=1,
variable_costs=-electricity_price,
)
},
initial_storage_level=0.5,
balanced=False,
)
es.add(battery1)

# storages that balance our fluctuating costs
# Electric Storage 2
battery2 = solph.components.GenericStorage(
label="battery 2",
nominal_storage_capacity=10,
inputs={
bel: solph.Flow(
nominal_value=1,
variable_costs=electricity_price,
)
},
outputs={
bel: solph.Flow(
nominal_value=1,
variable_costs=-electricity_price,
)
},
storage_costs=12 * [0] + [-np.mean(electricity_price)],
initial_storage_level=0.5,
balanced=False,
)
es.add(battery2)

# create an optimization problem and solve it
model = solph.Model(es)

# solve model
model.solve(solver="cbc")

# create result object
results = solph.processing.results(model)

plt.plot(
results[(battery1, None)]["sequences"],
label="content w/o storage costs",
)
plt.plot(
results[(battery2, None)]["sequences"],
label="content w/ storage revenue",
)
plt.legend()
plt.grid()

plt.show()


if __name__ == "__main__":
storage_costs_example()
29 changes: 27 additions & 2 deletions src/oemof/solph/components/_generic_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class GenericStorage(network.Component):
investment variable instead of to the nominal_storage_capacity. The
nominal_storage_capacity should not be set (or set to None) if an
investment object is used.
storage_costs : numeric (iterable or scalar), :math:`c_{storage}(t)`
Cost (per energy) for having energy in the storage.
lifetime_inflow : int, :math:`n_{in}`
Determine the lifetime of an inflow; only applicable for multi-period
models which can invest in storage capacity and have an
Expand Down Expand Up @@ -178,6 +180,7 @@ def __init__(
inflow_conversion_factor=1,
outflow_conversion_factor=1,
fixed_costs=0,
storage_costs=None,
lifetime_inflow=None,
lifetime_outflow=None,
custom_attributes=None,
Expand Down Expand Up @@ -231,6 +234,7 @@ def __init__(
self.max_storage_level = solph_sequence(max_storage_level)
self.min_storage_level = solph_sequence(min_storage_level)
self.fixed_costs = solph_sequence(fixed_costs)
self.storage_costs = solph_sequence(storage_costs)
self.investment = investment
self.invest_relation_input_output = invest_relation_input_output
self.invest_relation_input_capacity = invest_relation_input_capacity
Expand Down Expand Up @@ -416,13 +420,19 @@ class GenericStorageBlock(ScalarBlock):
:math:`\delta(t)` and
timeincrement
:math:`\tau(t)`
:math:`c_{storage}(t)` costs of having `storage_costs`
energy stored
=========================== ======================= =========
**The following parts of the objective function are created:**
*Standard model*
Nothing added to the objective function.
* :attr: `storage_costs` not 0
..math::
\sum_{t \in \textrm{TIMESTEPS}} c_{storage}(t) \cdot E(t)
*Multi-period model*
Expand Down Expand Up @@ -597,7 +607,22 @@ def _objective_expression(self):
* ((1 + m.discount_rate) ** -p)
)
self.fixed_costs = Expression(expr=fixed_costs)
return self.fixed_costs

storage_costs = 0

for n in self.STORAGES:
if n.storage_costs[0] is not None:
storage_costs += (
self.storage_content[n, 0] * n.storage_costs[0]
)
for t in m.TIMESTEPS:
storage_costs += (
self.storage_content[n, t + 1] * n.storage_costs[t + 1]
)

self.storage_costs = Expression(expr=storage_costs)

return self.fixed_costs + self.storage_costs


class GenericInvestmentStorageBlock(ScalarBlock):
Expand Down
1 change: 1 addition & 0 deletions tests/constraint_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ def test_storage(self):
},
nominal_storage_capacity=1e5,
loss_rate=0.13,
storage_costs=0.1,
inflow_conversion_factor=0.97,
outflow_conversion_factor=0.86,
initial_storage_level=0.4,
Expand Down
5 changes: 5 additions & 0 deletions tests/lp_files/storage.lp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

min
objective:
+4000.0 ONE_VAR_CONSTANT
+0.1 GenericStorageBlock_storage_content(storage_no_invest_1)
+0.1 GenericStorageBlock_storage_content(storage_no_invest_2)
+0.1 GenericStorageBlock_storage_content(storage_no_invest_3)
+56 flow(electricityBus_storage_no_invest_0_0)
+56 flow(electricityBus_storage_no_invest_0_1)
+56 flow(electricityBus_storage_no_invest_0_2)
Expand Down Expand Up @@ -51,6 +55,7 @@ c_e_GenericStorageBlock_balanced_cstr(storage_no_invest)_:
= 40000.0

bounds
+1 <= ONE_VAR_CONSTANT <= 1
0 <= flow(electricityBus_storage_no_invest_0_0) <= 16667
0 <= flow(electricityBus_storage_no_invest_0_1) <= 16667
0 <= flow(electricityBus_storage_no_invest_0_2) <= 16667
Expand Down

0 comments on commit d89ef93

Please sign in to comment.