# -*- coding: utf-8 -*-
"""Creating sets, variables, constraints and parts of the objective function
for Flow objects with neither nonconvex nor investment options.
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: Simon Hilpert
SPDX-FileCopyrightText: Cord Kaldemeyer
SPDX-FileCopyrightText: Stephan Günther
SPDX-FileCopyrightText: Birgit Schachler
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: jmloenneberga
SPDX-FileCopyrightText: Pierre-François Duc
SPDX-FileCopyrightText: Saeed Sayadi
SPDX-License-Identifier: MIT
"""
from pyomo.core import BuildAction
from pyomo.core import Constraint
from pyomo.core import NonNegativeIntegers
from pyomo.core import Set
from pyomo.core import Var
from pyomo.core.base.block import ScalarBlock
[docs]class SimpleFlowBlock(ScalarBlock):
r"""Flow block with definitions for standard flows.
See :class:`~oemof.solph.flows._flow.Flow` class for all parameters of the
*Flow*.
.. automethod:: _create_constraints
.. automethod:: _create_variables
.. automethod:: _create_sets
.. automethod:: _objective_expression
Note
----
See the :class:`~oemof.solph.flows._flow.Flow` class for the definition of
all parameters from the "List of Parameters above.
""" # noqa: E501
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _create(self, group=None):
r"""Creates sets, variables and constraints for all standard flows.
Parameters
----------
group : list
List containing tuples containing flow (f) objects and the
associated source (s) and target (t)
of flow e.g. groups=[(s1, t1, f1), (s2, t2, f2),..]
"""
if group is None:
return None
self._create_sets(group)
self._create_variables(group)
self._create_constraints()
[docs] def _create_sets(self, group):
"""
Creates all sets for standard flows.
"""
self.FULL_LOAD_TIME_MAX_FLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].full_load_time_max is not None
and g[2].nominal_value is not None
]
)
self.FULL_LOAD_TIME_MIN_FLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].full_load_time_min is not None
and g[2].nominal_value is not None
]
)
self.NEGATIVE_GRADIENT_FLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].negative_gradient_limit[0] is not None
]
)
self.POSITIVE_GRADIENT_FLOWS = Set(
initialize=[
(g[0], g[1])
for g in group
if g[2].positive_gradient_limit[0] is not None
]
)
self.INTEGER_FLOWS = Set(
initialize=[(g[0], g[1]) for g in group if g[2].integer]
)
[docs] def _create_variables(self, group):
r"""Creates all variables for standard flows.
All *Flow* objects are indexed by a starting and ending node
:math:`(i, o)`, which is omitted in the following for the sake of
convenience. The creation of some variables depend on the values of
*Flow* attributes. The following variables are created:
* :math:`P(t)`
Actual flow value (created in :class:`~oemof.solph._models.Model`).
The variable is bound to:
:math:`f_\mathrm{min}(t) \cdot P_\mathrm{nom}
\le P(t)
\le f_\mathrm{max}(t) \cdot P_\mathrm{nom}`.
If `Flow.fix` is not None the variable is bound to
:math:`P(t) = f_\mathrm{fix}(t) \cdot P_\mathrm{nom}`.
* :math:`\dot{P}_{down}` (`Flow.negative_gradient` is not `None`)
Difference of a flow in consecutive timesteps if flow is reduced.
The variable is bound to: :math:`0 \ge ve_n \ge ve_n^{max}`.
* :math:`\dot{P}_{up}` (`Flow.positive_gradient` is not `None`)
Difference of a flow in consecutive timesteps if flow is increased.
The variable is bound to: :math:`0 \ge ve_p \ge ve_p^{max}`.
"""
m = self.parent_block()
self.positive_gradient = Var(self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS)
self.negative_gradient = Var(self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS)
self.integer_flow = Var(
self.INTEGER_FLOWS, m.TIMESTEPS, within=NonNegativeIntegers
)
# set upper bound of gradient variable
for i, o, f in group:
if m.flows[i, o].positive_gradient_limit[0] is not None:
for t in m.TIMESTEPS:
self.positive_gradient[i, o, t].setub(
f.positive_gradient_limit[t] * f.nominal_value
)
if m.flows[i, o].negative_gradient_limit[0] is not None:
for t in m.TIMESTEPS:
self.negative_gradient[i, o, t].setub(
f.negative_gradient_limit[t] * f.nominal_value
)
[docs] def _create_constraints(self):
r"""Creates all constraints for standard flows.
The following constraints are created, if the appropriate attribute of
the *Flow* (see :class:`~oemof.solph.flows._flow.Flow`) object is set:
* `Flow.full_load_time_max` is not `None` (full_load_time_max_constr):
.. math::
\sum_t P(t) \cdot \tau \leq F_{max} \cdot P_{nom}
* `Flow.full_load_time_min` is not `None` (full_load_time_min_constr):
.. math::
\sum_t P(t) \cdot \tau \geq F_{min} \cdot P_{nom}
* `Flow.negative_gradient` is not `None` (negative_gradient_constr):
.. math::
P(t-1) - P(t) \geq ve_n(t)
* `Flow.positive_gradient` is not `None` (positive_gradient_constr):
.. math::
P(t) - P(t-1) \geq ve_p(t)
* `Flow.integer` is `True`
.. math::
P(t) = i(t)
"""
m = self.parent_block()
def _flow_full_load_time_max_rule(model):
"""Rule definition for build action of max. sum flow constraint."""
for inp, out in self.FULL_LOAD_TIME_MAX_FLOWS:
lhs = sum(
m.flow[inp, out, ts] * m.timeincrement[ts]
for ts in m.TIMESTEPS
)
rhs = (
m.flows[inp, out].full_load_time_max
* m.flows[inp, out].nominal_value
)
self.full_load_time_max_constr.add((inp, out), lhs <= rhs)
self.full_load_time_max_constr = Constraint(
self.FULL_LOAD_TIME_MAX_FLOWS, noruleinit=True
)
self.full_load_time_max_build = BuildAction(
rule=_flow_full_load_time_max_rule
)
def _flow_full_load_time_min_rule(model):
"""Rule definition for build action of min. sum flow constraint."""
for inp, out in self.FULL_LOAD_TIME_MIN_FLOWS:
lhs = sum(
m.flow[inp, out, ts] * m.timeincrement[ts]
for ts in m.TIMESTEPS
)
rhs = (
m.flows[inp, out].full_load_time_min
* m.flows[inp, out].nominal_value
)
self.full_load_time_min_constr.add((inp, out), lhs >= rhs)
self.full_load_time_min_constr = Constraint(
self.FULL_LOAD_TIME_MIN_FLOWS, noruleinit=True
)
self.full_load_time_min_build = BuildAction(
rule=_flow_full_load_time_min_rule
)
def _positive_gradient_flow_rule(model):
"""Rule definition for positive gradient constraint."""
for inp, out in self.POSITIVE_GRADIENT_FLOWS:
for ts in m.TIMESTEPS:
if ts > 0:
lhs = m.flow[inp, out, ts] - m.flow[inp, out, ts - 1]
rhs = self.positive_gradient[inp, out, ts]
self.positive_gradient_constr.add(
(inp, out, ts), lhs <= rhs
)
self.positive_gradient_constr = Constraint(
self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True
)
self.positive_gradient_build = BuildAction(
rule=_positive_gradient_flow_rule
)
def _negative_gradient_flow_rule(model):
"""Rule definition for negative gradient constraint."""
for inp, out in self.NEGATIVE_GRADIENT_FLOWS:
for ts in m.TIMESTEPS:
if ts > 0:
lhs = m.flow[inp, out, ts - 1] - m.flow[inp, out, ts]
rhs = self.negative_gradient[inp, out, ts]
self.negative_gradient_constr.add(
(inp, out, ts), lhs <= rhs
)
self.negative_gradient_constr = Constraint(
self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True
)
self.negative_gradient_build = BuildAction(
rule=_negative_gradient_flow_rule
)
def _integer_flow_rule(block, ii, oi, ti):
"""Force flow variable to NonNegativeInteger values."""
return self.integer_flow[ii, oi, ti] == m.flow[ii, oi, ti]
self.integer_flow_constr = Constraint(
self.INTEGER_FLOWS, m.TIMESTEPS, rule=_integer_flow_rule
)
[docs] def _objective_expression(self):
r"""Objective expression for all standard flows with fixed costs
and variable costs.
Depending on the attributes of the `Flow` object the following parts of
the objective function are created:
* `Flow.variable_costs` is not `None`:
.. math::
\sum_{(i,o)} \sum_t P(t) \cdot c_{var}(i, o, t)
"""
m = self.parent_block()
variable_costs = 0
for i, o in m.FLOWS:
if m.flows[i, o].variable_costs[0] is not None:
for t in m.TIMESTEPS:
variable_costs += (
m.flow[i, o, t]
* m.objective_weighting[t]
* m.flows[i, o].variable_costs[t]
)
return variable_costs