# -*- coding: utf-8 -*-
"""Creating sets, variables, constraints and parts of the objective function
for Flow objects with both Nonconvex and Investment options.
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: Simon Hilpert
SPDX-FileCopyrightText: Cord Kaldemeyer
SPDX-FileCopyrightText: Patrik Schönfeldt
SPDX-FileCopyrightText: Birgit Schachler
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: jmloenneberga
SPDX-FileCopyrightText: Johannes Kochems (jokochems)
SPDX-FileCopyrightText: Saeed Sayadi
SPDX-FileCopyrightText: Pierre-François Duc
SPDX-License-Identifier: MIT
"""
from pyomo.core import Binary
from pyomo.core import Constraint
from pyomo.core import Expression
from pyomo.core import NonNegativeReals
from pyomo.core import Set
from pyomo.core import Var
from ._non_convex_flow_block import NonConvexFlowBlock
[docs]class InvestNonConvexFlowBlock(NonConvexFlowBlock):
r"""
.. automethod:: _create_constraints
.. automethod:: _create_variables
.. automethod:: _create_sets
.. automethod:: _objective_expression
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _create(self, group=None):
"""Creates set, variables, constraints for all flow object with
an attribute flow of type class:`.InvestNonConvexFlowBlock`.
Parameters
----------
group : list
List of oemof.solph.InvestNonConvexFlowBlock objects for which
the constraints are build.
"""
if group is None:
return None
self._create_sets(group)
self._create_variables()
self._create_constraints()
[docs] def _create_sets(self, group):
"""
Creates all sets for investment non-convex flows.
INVEST_NON_CONVEX_FLOWS
A set of flows with the attribute `nonconvex` of type
:class:`.options.NonConvex` and the attribute `invest`
of type :class:`.options.Invest`.
.. automethod:: _sets_for_non_convex_flows
"""
self.INVEST_NON_CONVEX_FLOWS = Set(
initialize=[(g[0], g[1]) for g in group]
)
self._sets_for_non_convex_flows(group)
[docs] def _create_variables(self):
r"""
Status variable (binary) `om.InvestNonConvexFlowBlock.status`:
Variable indicating if flow is >= 0 indexed by FLOWS
:math::`P_{invest}` `InvestNonConvexFlowBlock.invest`
Value of the investment variable, i.e. equivalent to the nominal
value of the flows after optimization.
:math::`status\_nominal(i,o,t)` (non-negative real number)
New paramater representing the multiplication of `P_{invest}`
(from the <class 'oemof.solph.flows.InvestmentFlow'>) and
`status(i,o,t)` (from the
<class 'oemof.solph.flows.NonConvexFlow'>)
used for the constraints on the minimum and maximum
flow constraints.
.. automethod:: _variables_for_non_convex_flows
"""
m = self.parent_block()
# Create `status` variable representing the status of the flow
# at each time step
self.status = Var(
self.INVEST_NON_CONVEX_FLOWS, m.TIMESTEPS, within=Binary
)
self._variables_for_non_convex_flows()
# Investment-related variable similar to the
# <class 'oemof.solph.flows.InvestmentFlow'> class.
def _investvar_bound_rule(block, i, o):
"""Rule definition for bounds of the invest variable."""
if (i, o) in self.INVEST_NON_CONVEX_FLOWS:
return 0, m.flows[i, o].investment.maximum
# Create the `invest` variable for the nonconvex investment flow.
self.invest = Var(
self.INVEST_NON_CONVEX_FLOWS,
within=NonNegativeReals,
bounds=_investvar_bound_rule,
)
# `status_nominal` is a parameter which represents the
# multiplication of a binary variable (`status`)
# and a continuous variable (`invest` or `nominal_value`)
self.status_nominal = Var(
self.INVEST_NON_CONVEX_FLOWS, m.TIMESTEPS, within=NonNegativeReals
)
[docs] def _create_constraints(self):
r"""
.. automethod:: _shared_constraints_for_non_convex_flows
.. automethod:: _minimum_invest_constraint
.. automethod:: _maximum_invest_constraint
.. automethod:: _minimum_flow_constraint
.. automethod:: _maximum_flow_constraint
.. automethod:: _linearised_investment_constraints
"""
self._shared_constraints_for_non_convex_flows()
self.minimum_investment = self._minimum_invest_constraint()
self.maximum_investment = self._maximum_invest_constraint()
self.min = self._minimum_flow_constraint()
self.max = self._maximum_flow_constraint()
self._linearised_investment_constraints()
[docs] def _linearised_investment_constraints(self):
r"""
The resulting constraint is equivalent to
.. math::
status\_nominal(i,o,t) = Y_{status}(t) \cdot P_{invest}.
However, :math:`status` and :math:`invest` are variables
(binary and continuous, respectively).
Thus, three constraints are created which combination is equivalent.
.. automethod:: _linearised_investment_constraint_1
.. automethod:: _linearised_investment_constraint_2
.. automethod:: _linearised_investment_constraint_3
The following cases may occur:
* Case :math:`status = 0`
.. math::
(1) \Rightarrow status\_nominal = 0,\\
(2) \Rightarrow \text{ trivially fulfilled},\\
(3) \Rightarrow \text{ trivially fulfilled}.
* Case :math:`status = 1`
.. math::
(1) \Rightarrow \text{ trivially fulfilled},\\
(2) \Rightarrow status\_nominal \leq P_{invest},\\
(3) \Rightarrow status\_nominal \geq P_{invest}.
So, in total :math:`status\_nominal = P_{invest}`,
which is the desired result.
"""
self.invest_nc_one = self._linearised_investment_constraint_1()
self.invest_nc_two = self._linearised_investment_constraint_2()
self.invest_nc_three = self._linearised_investment_constraint_3()
[docs] def _linearised_investment_constraint_1(self):
r"""
.. math::
status\_nominal(i,o,t)
\leq Y_{status}(t) \cdot P_{invest, max}\quad (1)
"""
m = self.parent_block()
def _linearization_rule_invest_non_convex_one(_, i, o, t):
expr = (
self.status[i, o, t] * m.flows[i, o].investment.maximum
>= self.status_nominal[i, o, t]
)
return expr
return Constraint(
self.MIN_FLOWS,
m.TIMESTEPS,
rule=_linearization_rule_invest_non_convex_one,
)
[docs] def _linearised_investment_constraint_2(self):
r"""
.. math::
status\_nominal(i,o,t) \leq P_{invest}\quad (2)
"""
m = self.parent_block()
def _linearization_rule_invest_non_convex_two(_, i, o, t):
expr = self.invest[i, o] >= self.status_nominal[i, o, t]
return expr
return Constraint(
self.MIN_FLOWS,
m.TIMESTEPS,
rule=_linearization_rule_invest_non_convex_two,
)
[docs] def _linearised_investment_constraint_3(self):
r"""
.. math::
status\_nominal(i,o,t) \geq
P_{invest} - (1 - Y_{status}(t)) \cdot P_{invest, max}\quad (3)
"""
m = self.parent_block()
def _linearization_rule_invest_non_convex_three(_, i, o, t):
expr = (
self.invest[i, o]
- (1 - self.status[i, o, t]) * m.flows[i, o].investment.maximum
<= self.status_nominal[i, o, t]
)
return expr
return Constraint(
self.MIN_FLOWS,
m.TIMESTEPS,
rule=_linearization_rule_invest_non_convex_three,
)
[docs] def _objective_expression(self):
r"""Objective expression for nonconvex investment flows.
If `nonconvex.startup_costs` is set by the user:
.. math::
\sum_{i, o \in STARTUPFLOWS} \sum_t startup(i, o, t) \
\cdot c_{startup}
If `nonconvex.shutdown_costs` is set by the user:
.. math::
\sum_{i, o \in SHUTDOWNFLOWS} \sum_t shutdown(i, o, t) \
\cdot c_{shutdown}
.. math::
P_{invest} \cdot c_{invest,var}
"""
if not hasattr(self, "INVEST_NON_CONVEX_FLOWS"):
return 0
m = self.parent_block()
startup_costs = self._startup_costs()
shutdown_costs = self._shutdown_costs()
activity_costs = self._activity_costs()
inactivity_costs = self._inactivity_costs()
investment_costs = 0
for i, o in self.INVEST_NON_CONVEX_FLOWS:
investment_costs += (
self.invest[i, o] * m.flows[i, o].investment.ep_costs
+ m.flows[i, o].investment.offset
)
self.investment_costs = Expression(expr=investment_costs)
return (
startup_costs
+ shutdown_costs
+ activity_costs
+ inactivity_costs
+ investment_costs
)
[docs] def _minimum_invest_constraint(self):
r"""
.. math::
P_{invest, min} \le P_{invest}
"""
m = self.parent_block()
def _min_invest_rule(_, i, o):
"""Rule definition for applying a minimum investment"""
expr = m.flows[i, o].investment.minimum <= self.invest[i, o]
return expr
return Constraint(self.INVEST_NON_CONVEX_FLOWS, rule=_min_invest_rule)
[docs] def _maximum_invest_constraint(self):
r"""
.. math::
P_{invest} \le P_{invest, max}
"""
m = self.parent_block()
def _max_invest_rule(_, i, o):
"""Rule definition for applying a minimum investment"""
expr = self.invest[i, o] <= m.flows[i, o].investment.maximum
return expr
return Constraint(self.INVEST_NON_CONVEX_FLOWS, rule=_max_invest_rule)