Passed
Pull Request — dev (#815)
by Uwe
01:21
created

solph.flows._investment_flow   A

Complexity

Total Complexity 10

Size/Duplication

Total Lines 443
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 10
eloc 129
dl 0
loc 443
rs 10
c 0
b 0
f 0

4 Methods

Rating   Name   Duplication   Size   Complexity  
A InvestmentFlow.__init__() 0 2 1
A InvestmentFlowBlock.__init__() 0 2 1
B InvestmentFlowBlock._create() 0 186 4
A InvestmentFlowBlock._objective_expression() 0 23 4
1
# -*- coding: utf-8 -*-
2
3
"""Creating sets, variables, constraints and parts of the objective function
4
for FlowBlock objects with investment option.
5
6
SPDX-FileCopyrightText: Uwe Krien <[email protected]>
7
SPDX-FileCopyrightText: Simon Hilpert
8
SPDX-FileCopyrightText: Cord Kaldemeyer
9
SPDX-FileCopyrightText: Patrik Schönfeldt
10
SPDX-FileCopyrightText: Birgit Schachler
11
SPDX-FileCopyrightText: jnnr
12
SPDX-FileCopyrightText: jmloenneberga
13
14
SPDX-License-Identifier: MIT
15
16
"""
17
18
from pyomo.core import Binary
19
from pyomo.core import Constraint
20
from pyomo.core import Expression
21
from pyomo.core import NonNegativeReals
22
from pyomo.core import Set
23
from pyomo.core import Var
24
from pyomo.core.base.block import SimpleBlock
25
26
from ._flow import Flow
27
28
29
class InvestmentFlow(Flow):
30
    r"""
31
    Wrapper class to prepare separation of flow classes.
32
    """
33
34
    def __init__(self, **kwargs):
35
        super().__init__(**kwargs)
36
37
38
class InvestmentFlowBlock(SimpleBlock):
39
    r"""Block for all flows with :attr:`Investment` being not None.
40
41
    See :class:`oemof.solph.options.Investment` for all parameters of the
42
    *Investment* class.
43
44
    See :class:`oemof.solph.network.FlowBlock` for all parameters of the *FlowBlock*
45
    class.
46
47
    **Variables**
48
49
    All *InvestmentFlowBlock* are indexed by a starting and ending node
50
    :math:`(i, o)`, which is omitted in the following for the sake
51
    of convenience. The following variables are created:
52
53
    * :math:`P(t)`
54
55
        Actual flow value (created in :class:`oemof.solph.models.BaseModel`).
56
57
    * :math:`P_{invest}`
58
59
        Value of the investment variable, i.e. equivalent to the nominal
60
        value of the flows after optimization.
61
62
    * :math:`b_{invest}`
63
64
        Binary variable for the status of the investment, if
65
        :attr:`nonconvex` is `True`.
66
67
    **Constraints**
68
69
    Depending on the attributes of the *InvestmentFlowBlock* and *FlowBlock*, different
70
    constraints are created. The following constraint is created for all
71
    *InvestmentFlowBlock*:\
72
73
            Upper bound for the flow value
74
75
        .. math::
76
            P(t) \le ( P_{invest} + P_{exist} ) \cdot f_{max}(t)
77
78
    Depeding on the attribute :attr:`nonconvex`, the constraints for the bounds
79
    of the decision variable :math:`P_{invest}` are different:\
80
81
        * :attr:`nonconvex = False`
82
83
        .. math::
84
            P_{invest, min} \le P_{invest} \le P_{invest, max}
85
86
        * :attr:`nonconvex = True`
87
88
        .. math::
89
            &
90
            P_{invest, min} \cdot b_{invest} \le P_{invest}\\
91
            &
92
            P_{invest} \le P_{invest, max} \cdot b_{invest}\\
93
94
    For all *InvestmentFlowBlock* (independent of the attribute :attr:`nonconvex`),
95
    the following additional constraints are created, if the appropriate
96
    attribute of the *FlowBlock* (see :class:`oemof.solph.network.FlowBlock`) is set:
97
98
        * :attr:`fix` is not None
99
100
            Actual value constraint for investments with fixed flow values
101
102
        .. math::
103
            P(t) = ( P_{invest} + P_{exist} ) \cdot f_{fix}(t)
104
105
        * :attr:`min != 0`
106
107
            Lower bound for the flow values
108
109
        .. math::
110
            P(t) \geq ( P_{invest} + P_{exist} ) \cdot f_{min}(t)
111
112
        * :attr:`max_capacity_factor is not None`
113
114
            Upper bound for the sum of all flow values (e.g. maximum full load
115
            hours)
116
117
        .. math::
118
            \sum_t P(t) \cdot \tau(t) \leq ( P_{invest} + P_{exist} )
119
            \cdot f_{sum, min}
120
121
        * :attr:`min_capacity_factor is not None`
122
123
            Lower bound for the sum of all flow values (e.g. minimum full load
124
            hours)
125
126
        .. math::
127
            \sum_t P(t) \cdot \tau(t) \geq ( P_{invest} + P_{exist} )
128
            \cdot f_{sum, min}
129
130
131
    **Objective function**
132
133
    The part of the objective function added by the *InvestmentFlowBlock*
134
    also depends on whether a convex or nonconvex
135
    *InvestmentFlowBlock* is selected. The following parts of the objective function
136
    are created:
137
138
        * :attr:`nonconvex = False`
139
140
            .. math::
141
                P_{invest} \cdot c_{invest,var}
142
143
        * :attr:`nonconvex = True`
144
145
            .. math::
146
                P_{invest} \cdot c_{invest,var}
147
                + c_{invest,fix} \cdot b_{invest}\\
148
149
    The total value of all costs of all *InvestmentFlowBlock* can be retrieved
150
    calling :meth:`om.InvestmentFlowBlock.investment_costs.expr()`.
151
152
    .. csv-table:: List of Variables (in csv table syntax)
153
        :header: "symbol", "attribute", "explanation"
154
        :widths: 1, 1, 1
155
156
        ":math:`P(t)`", ":py:obj:`flow[n, o, t]`", "Actual flow value"
157
        ":math:`P_{invest}`", ":py:obj:`invest[i, o]`", "Invested flow
158
        capacity"
159
        ":math:`b_{invest}`", ":py:obj:`invest_status[i, o]`", "Binary status
160
        of investment"
161
162
    List of Variables (in rst table syntax):
163
164
    ===================  =============================  =========
165
    symbol               attribute                      explanation
166
    ===================  =============================  =========
167
    :math:`P(t)`         :py:obj:`flow[n, o, t]`         Actual flow value
168
169
    :math:`P_{invest}`   :py:obj:`invest[i, o]`          Invested flow capacity
170
171
    :math:`b_{invest}`   :py:obj:`invest_status[i, o]`   Binary status of investment
172
173
    ===================  =============================  =========
174
175
    Grid table style:
176
177
    +--------------------+-------------------------------+-----------------------------+
178
    | symbol             | attribute                     | explanation                 |
179
    +====================+===============================+=============================+
180
    | :math:`P(t)`       | :py:obj:`flow[n, o, t]`       | Actual flow value           |
181
    +--------------------+-------------------------------+-----------------------------+
182
    | :math:`P_{invest}` | :py:obj:`invest[i, o]`        | Invested flow capacity      |
183
    +--------------------+-------------------------------+-----------------------------+
184
    | :math:`b_{invest}` | :py:obj:`invest_status[i, o]` | Binary status of investment |
185
    +--------------------+-------------------------------+-----------------------------+
186
187
    .. csv-table:: List of Parameters
188
        :header: "symbol", "attribute", "explanation"
189
        :widths: 1, 1, 1
190
191
        ":math:`P_{exist}`", ":py:obj:`flows[i, o].investment.existing`", "
192
        Existing flow capacity"
193
        ":math:`P_{invest,min}`", ":py:obj:`flows[i, o].investment.minimum`", "
194
        Minimum investment capacity"
195
        ":math:`P_{invest,max}`", ":py:obj:`flows[i, o].investment.maximum`", "
196
        Maximum investment capacity"
197
        ":math:`c_{invest,var}`", ":py:obj:`flows[i, o].investment.ep_costs`
198
        ", "Variable investment costs"
199
        ":math:`c_{invest,fix}`", ":py:obj:`flows[i, o].investment.offset`", "
200
        Fix investment costs"
201
        ":math:`f_{actual}`", ":py:obj:`flows[i, o].fix[t]`", "Normed
202
        fixed value for the flow variable"
203
        ":math:`f_{max}`", ":py:obj:`flows[i, o].max[t]`", "Normed maximum
204
        value of the flow"
205
        ":math:`f_{min}`", ":py:obj:`flows[i, o].min[t]`", "Normed minimum
206
        value of the flow"
207
        ":math:`f_{sum,max}`", ":py:obj:`flows[i, o].max_capacity_factor`", "Specific
208
        maximum of summed flow values (per installed capacity)"
209
        ":math:`f_{sum,min}`", ":py:obj:`flows[i, o].min_capacity_factor`", "Specific
210
        minimum of summed flow values (per installed capacity)"
211
        ":math:`\tau(t)`", ":py:obj:`timeincrement[t]`", "Time step width for
212
        each time step"
213
214
    Note
215
    ----
216
    In case of a nonconvex investment flow (:attr:`nonconvex=True`),
217
    the existing flow capacity :math:`P_{exist}` needs to be zero.
218
    At least, it is not tested yet, whether this works out, or makes any sense
219
    at all.
220
221
    Note
222
    ----
223
    See also :class:`oemof.solph.network.FlowBlock`,
224
    :class:`oemof.solph.blocks.FlowBlock` and
225
    :class:`oemof.solph.options.Investment`
226
227
    """  # noqa: E501
228
229
    def __init__(self, *args, **kwargs):
230
        super().__init__(*args, **kwargs)
231
232
    def _create(self, group=None):
233
        r"""Creates sets, variables and constraints for FlowBlock
234
        with investment attribute of type class:`.Investment`.
235
236
        Parameters
237
        ----------
238
        group : list
239
            List containing tuples containing flow (f) objects that have an
240
            attribute investment and the associated source (s) and target (t)
241
            of flow e.g. groups=[(s1, t1, f1), (s2, t2, f2),..]
242
        """
243
        if group is None:
244
            return None
245
246
        m = self.parent_block()
247
248
        # ######################### SETS #####################################
249
        self.INVESTFLOWS = Set(initialize=[(g[0], g[1]) for g in group])
250
251
        self.CONVEX_INVESTFLOWS = Set(
252
            initialize=[
253
                (g[0], g[1])
254
                for g in group
255
                if g[2].investment.nonconvex is False
256
            ]
257
        )
258
259
        self.NON_CONVEX_INVESTFLOWS = Set(
260
            initialize=[
261
                (g[0], g[1])
262
                for g in group
263
                if g[2].investment.nonconvex is True
264
            ]
265
        )
266
267
        self.FIXED_INVESTFLOWS = Set(
268
            initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is not None]
269
        )
270
271
        self.NON_FIXED_INVESTFLOWS = Set(
272
            initialize=[(g[0], g[1]) for g in group if g[2].fix[0] is None]
273
        )
274
275
        self.MAX_CAPACITY_FACTOR_INVESTFLOWS = Set(
276
            initialize=[
277
                (g[0], g[1])
278
                for g in group
279
                if g[2].max_capacity_factor is not None
280
            ]
281
        )
282
283
        self.MIN_CAPACITY_FACTOR_INVESTFLOWS = Set(
284
            initialize=[
285
                (g[0], g[1])
286
                for g in group
287
                if g[2].min_capacity_factor is not None
288
            ]
289
        )
290
291
        self.MIN_INVESTFLOWS = Set(
292
            initialize=[
293
                (g[0], g[1])
294
                for g in group
295
                if (g[2].min[0] != 0 or len(g[2].min) > 1)
296
            ]
297
        )
298
299
        # ######################### VARIABLES #################################
300
        def _investvar_bound_rule(block, i, o):
301
            """Rule definition for bounds of invest variable."""
302
            if (i, o) in self.CONVEX_INVESTFLOWS:
303
                return (
304
                    m.flows[i, o].investment.minimum,
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
305
                    m.flows[i, o].investment.maximum,
306
                )
307
            elif (i, o) in self.NON_CONVEX_INVESTFLOWS:
308
                return 0, m.flows[i, o].investment.maximum
309
310
        # create invest variable for a investment flow
311
        self.invest = Var(
312
            self.INVESTFLOWS,
313
            within=NonNegativeReals,
314
            bounds=_investvar_bound_rule,
315
        )
316
317
        # create status variable for a non-convex investment flow
318
        self.invest_status = Var(self.NON_CONVEX_INVESTFLOWS, within=Binary)
319
        # ######################### CONSTRAINTS ###############################
320
321
        def _min_invest_rule(block, i, o):
322
            """Rule definition for applying a minimum investment"""
323
            expr = (
324
                m.flows[i, o].investment.minimum * self.invest_status[i, o]
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
325
                <= self.invest[i, o]
326
            )
327
            return expr
328
329
        self.minimum_rule = Constraint(
330
            self.NON_CONVEX_INVESTFLOWS, rule=_min_invest_rule
331
        )
332
333
        def _max_invest_rule(block, i, o):
334
            """Rule definition for applying a minimum investment"""
335
            expr = self.invest[i, o] <= (
336
                m.flows[i, o].investment.maximum * self.invest_status[i, o]
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
337
            )
338
            return expr
339
340
        self.maximum_rule = Constraint(
341
            self.NON_CONVEX_INVESTFLOWS, rule=_max_invest_rule
342
        )
343
344
        def _investflow_fixed_rule(block, i, o, t):
345
            """Rule definition of constraint to fix flow variable
346
            of investment flow to (normed) actual value
347
            """
348
            expr = m.flow[i, o, t] == (
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
349
                (m.flows[i, o].investment.existing + self.invest[i, o])
350
                * m.flows[i, o].fix[t]
351
            )
352
353
            return expr
354
355
        self.fixed = Constraint(
356
            self.FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_investflow_fixed_rule
357
        )
358
359
        def _max_investflow_rule(block, i, o, t):
360
            """Rule definition of constraint setting an upper bound of flow
361
            variable in investment case.
362
            """
363
            expr = m.flow[i, o, t] <= (
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
364
                (m.flows[i, o].investment.existing + self.invest[i, o])
365
                * m.flows[i, o].max[t]
366
            )
367
            return expr
368
369
        self.max = Constraint(
370
            self.NON_FIXED_INVESTFLOWS, m.TIMESTEPS, rule=_max_investflow_rule
371
        )
372
373
        def _min_investflow_rule(block, i, o, t):
374
            """Rule definition of constraint setting a lower bound on flow
375
            variable in investment case.
376
            """
377
            expr = m.flow[i, o, t] >= (
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
378
                (m.flows[i, o].investment.existing + self.invest[i, o])
379
                * m.flows[i, o].min[t]
380
            )
381
            return expr
382
383
        self.min = Constraint(
384
            self.MIN_INVESTFLOWS, m.TIMESTEPS, rule=_min_investflow_rule
385
        )
386
387
        def _max_capacity_factor_investflow_rule(block, i, o):
388
            """Rule definition for build action of max. sum flow constraint
389
            in investment case.
390
            """
391
            expr = sum(
392
                m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
393
            ) <= m.flows[i, o].max_capacity_factor * (
394
                self.invest[i, o] + m.flows[i, o].investment.existing
395
            )
396
            return expr
397
398
        self.max_capacity_factor = Constraint(
399
            self.MAX_CAPACITY_FACTOR_INVESTFLOWS,
400
            rule=_max_capacity_factor_investflow_rule,
401
        )
402
403
        def _min_capacity_factor_investflow_rule(block, i, o):
404
            """Rule definition for build action of min. sum flow constraint
405
            in investment case.
406
            """
407
            expr = sum(
408
                m.flow[i, o, t] * m.timeincrement[t] for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
409
            ) >= (
410
                (m.flows[i, o].investment.existing + self.invest[i, o])
411
                * m.flows[i, o].min_capacity_factor
412
            )
413
            return expr
414
415
        self.min_capacity_factor = Constraint(
416
            self.MIN_CAPACITY_FACTOR_INVESTFLOWS,
417
            rule=_min_capacity_factor_investflow_rule,
418
        )
419
420
    def _objective_expression(self):
421
        r"""Objective expression for flows with investment attribute of type
422
        class:`.Investment`. The returned costs are fixed, variable and
423
        investment costs.
424
        """
425
        if not hasattr(self, "INVESTFLOWS"):
426
            return 0
427
428
        m = self.parent_block()
429
        investment_costs = 0
430
431
        for i, o in self.CONVEX_INVESTFLOWS:
432
            investment_costs += (
433
                self.invest[i, o] * m.flows[i, o].investment.ep_costs
434
            )
435
        for i, o in self.NON_CONVEX_INVESTFLOWS:
436
            investment_costs += (
437
                self.invest[i, o] * m.flows[i, o].investment.ep_costs
438
                + self.invest_status[i, o] * m.flows[i, o].investment.offset
439
            )
440
441
        self.investment_costs = Expression(expr=investment_costs)
442
        return investment_costs
443