Source code for oemof.solph.flows._investment_flow_block

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

"""Creating sets, variables, constraints and parts of the objective function
for Flow objects with investment but without nonconvex 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 ScalarBlock


[docs]class InvestmentFlowBlock(ScalarBlock): r"""Block for all flows with :attr:`Investment` being not None. .. automethod:: _create_constraints .. automethod:: _create_variables .. automethod:: _create_sets .. automethod:: _objective_expression See :class:`oemof.solph.options.Investment` for all parameters of the *Investment* class. See :class:`oemof.solph.network.SimpleFlowBlock` for all parameters of the *SimpleFlowBlock* class. The total value of all costs of all *InvestmentFlowBlock* can be retrieved calling :meth:`om.InvestmentFlowBlock.investment_costs.expr()`. Note ---- In case of a nonconvex investment flow (:attr:`nonconvex=True`), the existing flow capacity :math:`P_{exist}` needs to be zero. Note ---- See also :class:`~oemof.solph.flows._flow.Flow`, :class:`~oemof.solph.flows._simple_flow_block.SimpleFlowBlock` 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 SimpleFlowBlock 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 self._create_sets(group) self._create_variables(group) self._create_constraints()
[docs] def _create_sets(self, group): """ Creates all sets for investment flows. """ 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.FULL_LOAD_TIME_MAX_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].full_load_time_max is not None ] ) self.FULL_LOAD_TIME_MIN_INVESTFLOWS = Set( initialize=[ (g[0], g[1]) for g in group if g[2].full_load_time_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) ] )
[docs] def _create_variables(self, _): r"""Creates all variables for investment flows. All *InvestmentFlowBlock* 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:`Y_{invest}` Binary variable for the status of the investment, if :attr:`nonconvex` is `True`. """ m = self.parent_block() 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)
[docs] def _create_constraints(self): r"""Creates all constraints for standard flows. Depending on the attributes of the *InvestmentFlowBlock* and *SimpleFlowBlock*, different constraints are created. The following constraint is created for all *InvestmentFlowBlock*: 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 Y_{invest} \le P_{invest}\\ & P_{invest} \le P_{invest, max} \cdot Y_{invest}\\ For all *InvestmentFlowBlock* (independent of the attribute :attr:`nonconvex`), the following additional constraints are created, if the appropriate attribute of the *SimpleFlowBlock* (see :class:`oemof.solph.network.SimpleFlowBlock`) 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:`full_load_time_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 t_{full\_load, min} * :attr:`full_load_time_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 t_{full\_load, min} """ m = self.parent_block() self.minimum_rule = self._minimum_investment_constraint() self.maximum_rule = self._maximum_investment_constraint() 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 _full_load_time_max_investflow_rule(_, 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].full_load_time_max * ( self.invest[i, o] + m.flows[i, o].investment.existing ) return expr self.full_load_time_max = Constraint( self.FULL_LOAD_TIME_MAX_INVESTFLOWS, rule=_full_load_time_max_investflow_rule, ) def _full_load_time_min_investflow_rule(_, 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].full_load_time_min ) return expr self.full_load_time_min = Constraint( self.FULL_LOAD_TIME_MIN_INVESTFLOWS, rule=_full_load_time_min_investflow_rule, )
[docs] 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. The part of the objective function added by the *InvestmentFlowBlock* also depends on whether a convex or nonconvex *InvestmentFlowBlock* 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 Y_{invest}\\ """ 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
def _minimum_investment_constraint(self): """Constraint factory for a minimum investment""" 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_status[i, o] <= self.invest[i, o] ) return expr return Constraint(self.NON_CONVEX_INVESTFLOWS, rule=_min_invest_rule) def _maximum_investment_constraint(self): """Constraint factory for a maximum investment""" 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 * self.invest_status[i, o] ) return expr return Constraint(self.NON_CONVEX_INVESTFLOWS, rule=_max_invest_rule)