Source code for oemof.solph.blocks.investment_flow

# -*- coding: utf-8 -*-

"""Creating sets, variables, constraints and parts of the objective function
for Flow objects with investment option.

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-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 pyomo.core.base.block import SimpleBlock


[docs]class InvestmentFlow(SimpleBlock): r"""Block for all flows with :attr:`Investment` being not None. See :class:`oemof.solph.options.Investment` for all parameters of the *Investment* class. See :class:`oemof.solph.network.Flow` for all parameters of the *Flow* class. **Variables** All *InvestmentFlow* are indexed by a starting and ending node :math:`(i, o)`, which is omitted in the following for the sake of convenience. The following variables are created: * :math:`P(t)` Actual flow value (created in :class:`oemof.solph.models.BaseModel`). * :math:`P_{invest}` Value of the investment variable, i.e. equivalent to the nominal value of the flows after optimization. * :math:`b_{invest}` Binary variable for the status of the investment, if :attr:`nonconvex` is `True`. **Constraints** Depending on the attributes of the *InvestmentFlow* and *Flow*, different constraints are created. The following constraint is created for all *InvestmentFlow*:\ Upper bound for the flow value .. math:: P(t) \le ( P_{invest} + P_{exist} ) \cdot f_{max}(t) Depeding on the attribute :attr:`nonconvex`, the constraints for the bounds of the decision variable :math:`P_{invest}` are different:\ * :attr:`nonconvex = False` .. math:: P_{invest, min} \le P_{invest} \le P_{invest, max} * :attr:`nonconvex = True` .. math:: & P_{invest, min} \cdot b_{invest} \le P_{invest}\\ & P_{invest} \le P_{invest, max} \cdot b_{invest}\\ For all *InvestmentFlow* (independent of the attribute :attr:`nonconvex`), the following additional constraints are created, if the appropriate attribute of the *Flow* (see :class:`oemof.solph.network.Flow`) is set: * :attr:`fix` is not None Actual value constraint for investments with fixed flow values .. math:: P(t) = ( P_{invest} + P_{exist} ) \cdot f_{fix}(t) * :attr:`min != 0` Lower bound for the flow values .. math:: P(t) \geq ( P_{invest} + P_{exist} ) \cdot f_{min}(t) * :attr:`summed_max is not None` Upper bound for the sum of all flow values (e.g. maximum full load hours) .. math:: \sum_t P(t) \cdot \tau(t) \leq ( P_{invest} + P_{exist} ) \cdot f_{sum, min} * :attr:`summed_min is not None` Lower bound for the sum of all flow values (e.g. minimum full load hours) .. math:: \sum_t P(t) \cdot \tau(t) \geq ( P_{invest} + P_{exist} ) \cdot f_{sum, min} **Objective function** The part of the objective function added by the *InvestmentFlow* also depends on whether a convex or nonconvex *InvestmentFlow* is selected. The following parts of the objective function are created: * :attr:`nonconvex = False` .. math:: P_{invest} \cdot c_{invest,var} * :attr:`nonconvex = True` .. math:: P_{invest} \cdot c_{invest,var} + c_{invest,fix} \cdot b_{invest}\\ The total value of all costs of all *InvestmentFlow* can be retrieved calling :meth:`om.InvestmentFlow.investment_costs.expr()`. .. csv-table:: List of Variables (in csv table syntax) :header: "symbol", "attribute", "explanation" :widths: 1, 1, 1 ":math:`P(t)`", ":py:obj:`flow[n, o, t]`", "Actual flow value" ":math:`P_{invest}`", ":py:obj:`invest[i, o]`", "Invested flow capacity" ":math:`b_{invest}`", ":py:obj:`invest_status[i, o]`", "Binary status of investment" List of Variables (in rst table syntax): =================== ============================= ========= symbol attribute explanation =================== ============================= ========= :math:`P(t)` :py:obj:`flow[n, o, t]` Actual flow value :math:`P_{invest}` :py:obj:`invest[i, o]` Invested flow capacity :math:`b_{invest}` :py:obj:`invest_status[i, o]` Binary status of investment =================== ============================= ========= Grid table style: +--------------------+-------------------------------+-----------------------------+ | symbol | attribute | explanation | +====================+===============================+=============================+ | :math:`P(t)` | :py:obj:`flow[n, o, t]` | Actual flow value | +--------------------+-------------------------------+-----------------------------+ | :math:`P_{invest}` | :py:obj:`invest[i, o]` | Invested flow capacity | +--------------------+-------------------------------+-----------------------------+ | :math:`b_{invest}` | :py:obj:`invest_status[i, o]` | Binary status of investment | +--------------------+-------------------------------+-----------------------------+ .. csv-table:: List of Parameters :header: "symbol", "attribute", "explanation" :widths: 1, 1, 1 ":math:`P_{exist}`", ":py:obj:`flows[i, o].investment.existing`", " Existing flow capacity" ":math:`P_{invest,min}`", ":py:obj:`flows[i, o].investment.minimum`", " Minimum investment capacity" ":math:`P_{invest,max}`", ":py:obj:`flows[i, o].investment.maximum`", " Maximum investment capacity" ":math:`c_{invest,var}`", ":py:obj:`flows[i, o].investment.ep_costs` ", "Variable investment costs" ":math:`c_{invest,fix}`", ":py:obj:`flows[i, o].investment.offset`", " Fix investment costs" ":math:`f_{actual}`", ":py:obj:`flows[i, o].fix[t]`", "Normed fixed value for the flow variable" ":math:`f_{max}`", ":py:obj:`flows[i, o].max[t]`", "Normed maximum value of the flow" ":math:`f_{min}`", ":py:obj:`flows[i, o].min[t]`", "Normed minimum value of the flow" ":math:`f_{sum,max}`", ":py:obj:`flows[i, o].summed_max`", "Specific maximum of summed flow values (per installed capacity)" ":math:`f_{sum,min}`", ":py:obj:`flows[i, o].summed_min`", "Specific minimum of summed flow values (per installed capacity)" ":math:`\tau(t)`", ":py:obj:`timeincrement[t]`", "Time step width for each time step" Note ---- In case of a nonconvex investment flow (:attr:`nonconvex=True`), the existing flow capacity :math:`P_{exist}` needs to be zero. At least, it is not tested yet, whether this works out, or makes any sense at all. Note ---- See also :class:`oemof.solph.network.Flow`, :class:`oemof.solph.blocks.Flow` and :class:`oemof.solph.options.Investment` """ # noqa: E501 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _create(self, group=None): r"""Creates sets, variables and constraints for Flow with investment attribute of type class:`.Investment`. Parameters ---------- group : list List containing tuples containing flow (f) objects that have an attribute investment 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 m = self.parent_block() # ######################### SETS ##################################### self.INVESTFLOWS = Set(initialize=[(g[0], g[1]) for g in group]) self.CONVEX_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].investment.nonconvex is False ] ) self.NON_CONVEX_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].investment.nonconvex is True ] ) self.FIXED_INVESTFLOWS = Set( initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is not None] ) self.NON_FIXED_INVESTFLOWS = Set( initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is None] ) self.SUMMED_MAX_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].summed_max is not None ] ) self.SUMMED_MIN_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].summed_min is not None ] ) self.MIN_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if (g[2].min[0] != 0 or len(g[2].min) > 1) ] ) # ######################### VARIABLES ################################# def _investvar_bound_rule(block, i, o): """Rule definition for bounds of invest variable.""" if (i, o) in self.CONVEX_INVESTFLOWS: return ( m.flows[i, o].investment.minimum, m.flows[i, o].investment.maximum, ) elif (i, o) in self.NON_CONVEX_INVESTFLOWS: return 0, m.flows[i, o].investment.maximum # create invest variable for a investment flow self.invest = Var( self.INVESTFLOWS, within=NonNegativeReals, bounds=_investvar_bound_rule, ) # create status variable for a non-convex investment flow self.invest_status = Var(self.NON_CONVEX_INVESTFLOWS, within=Binary) # ######################### CONSTRAINTS ############################### def _min_invest_rule(block, i, o): """Rule definition for applying a minimum investment""" expr = ( m.flows[i, o].investment.minimum * self.invest_status[i, o] <= self.invest[i, o] ) return expr self.minimum_rule = Constraint( self.NON_CONVEX_INVESTFLOWS, rule=_min_invest_rule ) def _max_invest_rule(block, i, o): """Rule definition for applying a minimum investment""" expr = self.invest[i, o] <= ( m.flows[i, o].investment.maximum * self.invest_status[i, o] ) return expr self.maximum_rule = Constraint( self.NON_CONVEX_INVESTFLOWS, rule=_max_invest_rule ) def _investflow_fixed_rule(block, i, o, t): """Rule definition of constraint to fix flow variable of investment flow to (normed) actual value """ expr = m.flow[i, o, t] == ( (m.flows[i, o].investment.existing + self.invest[i, o]) * m.flows[i, o].fix[t] ) return expr self.fixed = Constraint( self.FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_investflow_fixed_rule ) def _max_investflow_rule(block, i, o, t): """Rule definition of constraint setting an upper bound of flow variable in investment case. """ expr = m.flow[i, o, t] <= ( (m.flows[i, o].investment.existing + self.invest[i, o]) * m.flows[i, o].max[t] ) return expr self.max = Constraint( self.NON_FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_max_investflow_rule ) def _min_investflow_rule(block, i, o, t): """Rule definition of constraint setting a lower bound on flow variable in investment case. """ expr = m.flow[i, o, t] >= ( (m.flows[i, o].investment.existing + self.invest[i, o]) * m.flows[i, o].min[t] ) return expr self.min = Constraint( self.MIN_INVESTFLOWS, m.TIMESTEPS, rule=_min_investflow_rule ) def _summed_max_investflow_rule(block, i, o): """Rule definition for build action of max. sum flow constraint in investment case. """ expr = sum( m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS ) <= m.flows[i, o].summed_max * ( self.invest[i, o] + m.flows[i, o].investment.existing ) return expr self.summed_max = Constraint( self.SUMMED_MAX_INVESTFLOWS, rule=_summed_max_investflow_rule ) def _summed_min_investflow_rule(block, i, o): """Rule definition for build action of min. sum flow constraint in investment case. """ expr = sum( m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS ) >= ( (m.flows[i, o].investment.existing + self.invest[i, o]) * m.flows[i, o].summed_min ) return expr self.summed_min = Constraint( self.SUMMED_MIN_INVESTFLOWS, rule=_summed_min_investflow_rule ) def _objective_expression(self): r"""Objective expression for flows with investment attribute of type class:`.Investment`. The returned costs are fixed, variable and investment costs. """ if not hasattr(self, "INVESTFLOWS"): return 0 m = self.parent_block() investment_costs = 0 for i, o in self.CONVEX_INVESTFLOWS: investment_costs += ( self.invest[i, o] * m.flows[i, o].investment.ep_costs ) for i, o in self.NON_CONVEX_INVESTFLOWS: investment_costs += ( self.invest[i, o] * m.flows[i, o].investment.ep_costs + self.invest_status[i, o] * m.flows[i, o].investment.offset ) self.investment_costs = Expression(expr=investment_costs) return investment_costs