# -*- coding: utf-8 -*-
"""
In-development Converter with piecewise linear efficiencies.
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: Simon Hilpert
SPDX-FileCopyrightText: Cord Kaldemeyer
SPDX-FileCopyrightText: Patrik Schönfeldt
SPDX-FileCopyrightText: Johannes Röder
SPDX-FileCopyrightText: jakob-wo
SPDX-FileCopyrightText: gplssm
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: Johannes Kochems
SPDX-License-Identifier: MIT
"""
from oemof.network import Node
from pyomo.core.base.block import ScalarBlock
from pyomo.environ import BuildAction
from pyomo.environ import Constraint
from pyomo.environ import Piecewise
from pyomo.environ import Set
from pyomo.environ import Var
[docs]class PiecewiseLinearConverter(Node):
"""Component to model an energy converter with one input and one output
and an arbitrary piecewise linear conversion function.
Parameters
----------
in_breakpoints : list
List containing the domain breakpoints, i.e. the breakpoints for the
incoming flow.
conversion_function : func
The function describing the relation between incoming flow and outgoing
flow which is to be approximated.
pw_repn : string
Choice of piecewise representation that is passed to
pyomo.environ.Piecewise
Examples
--------
>>> import oemof.solph as solph
>>> b_gas = solph.buses.Bus(label='biogas')
>>> b_el = solph.buses.Bus(label='electricity')
>>> pwltf = solph.components.experimental.PiecewiseLinearConverter(
... label='pwltf',
... inputs={b_gas: solph.flows.Flow(
... nominal_value=100,
... variable_costs=1)},
... outputs={b_el: solph.flows.Flow()},
... in_breakpoints=[0,25,50,75,100],
... conversion_function=lambda x: x**2,
... pw_repn='CC')
>>> type(pwltf)
<class 'oemof.solph.components.experimental._piecewise_linear_converter.\
PiecewiseLinearConverter'>
"""
def __init__(
self,
label,
*,
inputs,
outputs,
conversion_function,
in_breakpoints,
pw_repn,
custom_properties=None,
):
super().__init__(
label,
inputs=inputs,
outputs=outputs,
custom_properties=custom_properties,
)
self.in_breakpoints = list(in_breakpoints)
self.conversion_function = conversion_function
self.pw_repn = pw_repn
if len(self.inputs) > 1 or len(self.outputs) > 1:
raise ValueError(
"Component `PiecewiseLinearConverter` cannot have "
+ "more than 1 input and 1 output!"
)
nominal_value = [a.nominal_value for a in self.inputs.values()][0]
if max(self.in_breakpoints) < nominal_value:
raise ValueError(
"Largest in_breakpoint must be larger or equal "
+ "nominal value"
)
[docs] def constraint_group(self):
return PiecewiseLinearConverterBlock
[docs]class PiecewiseLinearConverterBlock(ScalarBlock):
r"""Block for the relation of nodes with type
:class:`~oemof.solph.components.experimental._piecewise_linear_converter.PiecewiseLinearConverter`
**The following constraints are created:**
"""
CONSTRAINT_GROUP = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _create(self, group=None):
"""Creates the relation for the class:`PiecewiseLinearConverter`.
Parameters
----------
group : list
List of
oemof.solph.components.experimental.PiecewiseLinearConverter
objects for which the relation of inputs and outputs is created
e.g. group = [pwltf1, pwltf2, pwltf3, ...].
"""
if group is None:
return None
m = self.parent_block()
self.PWLINEARCONVERTERS = Set(initialize=[n for n in group])
pw_repns = [n.pw_repn for n in group]
if all(x == pw_repns[0] for x in pw_repns):
self.pw_repn = pw_repns[0]
else:
print(
"Cannot model different piecewise representations ",
[n.pw_repn for n in group],
)
self.breakpoints = {}
def build_breakpoints(block, n):
for t in m.TIMESTEPS:
self.breakpoints[(n, t)] = n.in_breakpoints
self.breakpoint_build = BuildAction(
self.PWLINEARCONVERTERS, rule=build_breakpoints
)
def _conversion_function(block, n, t, x):
expr = n.conversion_function(x)
return expr
# bounds are min/max of breakpoints
lower_bound_in = {n: min(n.in_breakpoints) for n in group}
upper_bound_in = {n: max(n.in_breakpoints) for n in group}
lower_bound_out = {
n: n.conversion_function(bound)
for (n, bound) in lower_bound_in.items()
}
upper_bound_out = {
n: n.conversion_function(bound)
for (n, bound) in upper_bound_in.items()
}
def get_inflow_bounds(model, n, t):
return lower_bound_in[n], upper_bound_in[n]
def get_outflow_bounds(model, n, t):
return lower_bound_out[n], upper_bound_out[n]
self.inflow = Var(
self.PWLINEARCONVERTERS, m.TIMESTEPS, bounds=get_inflow_bounds
)
self.outflow = Var(
self.PWLINEARCONVERTERS, m.TIMESTEPS, bounds=get_outflow_bounds
)
def _in_equation(block, n, p, t):
"""Link binary input and output flow to component outflow."""
expr = 0
expr += -m.flow[list(n.inputs.keys())[0], n, p, t]
expr += self.inflow[n, t]
return expr == 0
self.equate_in = Constraint(
self.PWLINEARCONVERTERS, m.TIMEINDEX, rule=_in_equation
)
def _out_equation(block, n, p, t):
"""Link binary input and output flow to component outflow."""
expr = 0
expr += -m.flow[n, list(n.outputs.keys())[0], p, t]
expr += self.outflow[n, t]
return expr == 0
self.equate_out = Constraint(
self.PWLINEARCONVERTERS, m.TIMEINDEX, rule=_out_equation
)
self.piecewise = Piecewise(
self.PWLINEARCONVERTERS,
m.TIMESTEPS,
self.outflow,
self.inflow,
pw_repn=self.pw_repn,
pw_constr_type="EQ",
pw_pts=self.breakpoints,
f_rule=_conversion_function,
)