-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
488 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
examples/storage_level_constraint/storage_level_constraint.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import pandas as pd | ||
from oemof.solph import Bus, EnergySystem, Flow, Model | ||
from oemof.solph.components import GenericStorage, Source, Sink | ||
from oemof.solph.processing import results | ||
|
||
import matplotlib.pyplot as plt | ||
|
||
from oemof.solph.constraints import storage_level_constraint | ||
|
||
|
||
es = EnergySystem( | ||
timeindex=pd.date_range("2022-01-01", freq="1H", periods=24), | ||
infer_last_interval=True, | ||
) | ||
|
||
multiplexer = Bus( | ||
label="multiplexer", | ||
) | ||
|
||
storage = GenericStorage( | ||
label="storage", | ||
nominal_storage_capacity=3, | ||
initial_storage_level=1, | ||
balanced=True, | ||
loss_rate=0.05, | ||
inputs={multiplexer: Flow()}, | ||
outputs={multiplexer: Flow()}, | ||
) | ||
|
||
es.add(multiplexer, storage) | ||
|
||
in_0 = Source( | ||
label="in_0", | ||
outputs={multiplexer: Flow(nominal_value=0.5, variable_costs=0.15)}, | ||
) | ||
es.add(in_0) | ||
|
||
in_1 = Source(label="in_1", outputs={multiplexer: Flow(nominal_value=0.1)}) | ||
es.add(in_1) | ||
|
||
out_0 = Sink( | ||
label="out_0", | ||
inputs={multiplexer: Flow(nominal_value=0.25, variable_costs=-0.1)}, | ||
) | ||
es.add(out_0) | ||
|
||
out_1 = Sink( | ||
label="out_1", | ||
inputs={multiplexer: Flow(nominal_value=0.15, variable_costs=-0.1)}, | ||
) | ||
es.add(out_1) | ||
|
||
|
||
model = Model(es) | ||
|
||
storage_level_constraint( | ||
model=model, | ||
name="multiplexer", | ||
storage_component=storage, | ||
multiplexer_bus=multiplexer, | ||
input_levels={in_1: 1 / 3}, # in_0 is always active | ||
output_levels={out_0: 1 / 6, out_1: 1 / 2}, | ||
) | ||
model.solve() | ||
|
||
my_results = results(model) | ||
|
||
df = pd.DataFrame(my_results[(storage, None)]["sequences"]) | ||
df["in1_status"] = my_results[(in_1, None)]["sequences"] | ||
df["out1_status"] = my_results[(out_1, None)]["sequences"] | ||
df["out0_status"] = my_results[(out_0, None)]["sequences"] | ||
|
||
df["in1"] = my_results[(in_1, multiplexer)]["sequences"] | ||
df["in0"] = my_results[(in_0, multiplexer)]["sequences"] | ||
df["out0"] = my_results[(multiplexer, out_0)]["sequences"] | ||
df["out1"] = my_results[(multiplexer, out_1)]["sequences"] | ||
|
||
plt.step(df.index, df["in0"], where="post", label="inflow (<= 1)") | ||
plt.step(df.index, df["in1"], where="post", label="inflow (< 1/3)") | ||
plt.step(df.index, df["out0"], where="post", label="outflow (> 1/6)") | ||
plt.step(df.index, df["out1"], where="post", label="outflow (> 1/2)") | ||
|
||
plt.grid() | ||
plt.legend() | ||
plt.ylabel("Flow Power (arb. units)") | ||
|
||
plt.twinx() | ||
|
||
plt.plot(df.index, df["storage_content"], "r-", label="storage content") | ||
plt.ylim(0, 3) | ||
plt.legend(loc="center right") | ||
plt.ylabel("Stored Energy (arb. units)") | ||
|
||
print(df) | ||
|
||
plt.show() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
"""A constraint to allow flows from to a storage based on the storage level. | ||
SPDX-FileCopyrightText: Patrik Schönfeldt | ||
SPDX-FileCopyrightText: Johannes Kochems | ||
SPDX-FileCopyrightText: Deutsches Zentrum für Luft- und Raumfahrt (DLR) | ||
SPDX-License-Identifier: MIT | ||
""" | ||
|
||
from pyomo import environ as po | ||
|
||
|
||
def storage_level_constraint( | ||
model, | ||
name, | ||
storage_component, | ||
multiplexer_bus, | ||
input_levels, | ||
output_levels, | ||
): | ||
r""" | ||
Add constraints to implement storage content based access. | ||
As a GenericStorage just allows exactly one input and one output, | ||
an additional bus, the multiplexer_bus, is used for the connections. | ||
Note that all Flow objects connected to the multiplexer_bus have to have | ||
a nominal_value. | ||
Parameters | ||
---------- | ||
model : oemof.solph.Model | ||
Model to which the constraint is added. | ||
name : string | ||
Name of the multiplexer. | ||
storage_component : oemof.solph.components.GenericStorage | ||
Storage component whose content should mandate | ||
the possible inputs and outputs. | ||
multiplexer_bus : oemof.solph.Bus | ||
Bus which connects the input and output levels to the storage. | ||
input_levels : dictionary with oemof.solph.Bus as keys and float as values | ||
Dictionary of buses which act as inputs and corresponding levels | ||
output_levels : dictionary with oemof.solph.Bus as keys and float as values | ||
Dictionary of buses which act as outputs and corresponding level | ||
Verbose description can be found in https://arxiv.org/abs/2211.14080 | ||
""" | ||
|
||
def _outputs(): | ||
OUTPUTS = po.Set(initialize=output_levels.keys()) | ||
setattr(model, f"{name}_OUTPUTS", OUTPUTS) | ||
|
||
active_output = po.Var( | ||
OUTPUTS, model.TIMESTEPS, domain=po.Binary, bounds=(0, 1) | ||
) | ||
setattr(model, f"{name}_active_output", active_output) | ||
|
||
constraint_name = f"{name}_output_active_constraint" | ||
|
||
def _output_active_rule(m): | ||
r""" | ||
.. math:: | ||
y_n \le E(t) / E_n | ||
""" | ||
for t in m.TIMESTEPS: | ||
for o in output_levels: | ||
getattr(m, constraint_name).add( | ||
(o, t), | ||
m.GenericStorageBlock.storage_content[ | ||
storage_component, t + 1 | ||
] | ||
/ storage_component.nominal_storage_capacity | ||
>= active_output[o, t] * output_levels[o], | ||
) | ||
|
||
setattr( | ||
model, | ||
constraint_name, | ||
po.Constraint( | ||
OUTPUTS, | ||
model.TIMESTEPS, | ||
noruleinit=True, | ||
), | ||
) | ||
setattr( | ||
model, | ||
constraint_name + "build", | ||
po.BuildAction(rule=_output_active_rule), | ||
) | ||
|
||
# Define constraints on the output flows | ||
def _constraint_output_rule(m, o, p, t): | ||
return ( | ||
m.flow[multiplexer_bus, o, p, t] | ||
/ m.flows[multiplexer_bus, o].nominal_value | ||
<= active_output[o, t] | ||
) | ||
|
||
setattr( | ||
model, | ||
f"{name}_output_constraint", | ||
po.Constraint( | ||
OUTPUTS, | ||
model.PERIODS, | ||
model.TIMESTEPS, | ||
rule=_constraint_output_rule, | ||
), | ||
) | ||
|
||
_outputs() | ||
|
||
def _inputs(): | ||
INPUTS = po.Set(initialize=input_levels.keys()) | ||
setattr(model, f"{name}_INPUTS", INPUTS) | ||
|
||
inactive_input = po.Var( | ||
INPUTS, model.TIMESTEPS, domain=po.Binary, bounds=(0, 1) | ||
) | ||
setattr(model, f"{name}_active_input", inactive_input) | ||
|
||
constraint_name = f"{name}_input_active_constraint" | ||
|
||
def _input_active_rule(m): | ||
r""" | ||
.. math:: | ||
\hat{y}_n \ge (E(t) - E_n) - E_{max} | ||
""" | ||
for t in m.TIMESTEPS: | ||
for o in input_levels: | ||
getattr(m, constraint_name).add( | ||
(o, t), | ||
( | ||
m.GenericStorageBlock.storage_content[ | ||
storage_component, t | ||
] | ||
- input_levels[o] | ||
) | ||
/ storage_component.nominal_storage_capacity | ||
<= inactive_input[o, t], | ||
) | ||
|
||
setattr( | ||
model, | ||
constraint_name, | ||
po.Constraint( | ||
INPUTS, | ||
model.TIMESTEPS, | ||
noruleinit=True, | ||
), | ||
) | ||
setattr( | ||
model, | ||
constraint_name + "build", | ||
po.BuildAction(rule=_input_active_rule), | ||
) | ||
|
||
# Define constraints on the input flows | ||
def _constraint_input_rule(m, i, p, t): | ||
return ( | ||
m.flow[i, multiplexer_bus, p, t] | ||
/ m.flows[i, multiplexer_bus].nominal_value | ||
<= 1 - inactive_input[i, t] | ||
) | ||
|
||
setattr( | ||
model, | ||
f"{name}_input_constraint", | ||
po.Constraint( | ||
INPUTS, | ||
model.PERIODS, | ||
model.TIMESTEPS, | ||
rule=_constraint_input_rule, | ||
), | ||
) | ||
|
||
_inputs() | ||
|
||
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.