solph.flows._invest_non_convex_flow_block   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 387
Duplicated Lines 12.4 %

Importance

Changes 0
Metric Value
wmc 24
eloc 155
dl 48
loc 387
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A InvestNonConvexFlowBlock._create() 0 16 2
A InvestNonConvexFlowBlock._create_variables() 0 68 3
A InvestNonConvexFlowBlock._linearised_investment_constraint_3() 0 22 1
A InvestNonConvexFlowBlock._linearised_investment_constraint_2() 0 16 1
B InvestNonConvexFlowBlock._objective_expression() 0 49 6
A InvestNonConvexFlowBlock._linearised_investment_constraints() 0 36 1
A InvestNonConvexFlowBlock._minimum_invest_constraint() 24 24 3
A InvestNonConvexFlowBlock._create_constraints() 0 20 1
A InvestNonConvexFlowBlock._linearised_investment_constraint_1() 0 19 1
A InvestNonConvexFlowBlock.__init__() 0 2 1
A InvestNonConvexFlowBlock._maximum_invest_constraint() 24 24 3
A InvestNonConvexFlowBlock._create_sets() 0 32 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
# -*- coding: utf-8 -*-
2
3
"""Creating sets, variables, constraints and parts of the objective function
4
for Flow objects with both Nonconvex and Investment options.
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
SPDX-FileCopyrightText: Johannes Kochems (jokochems)
14
SPDX-FileCopyrightText: Saeed Sayadi
15
SPDX-FileCopyrightText: Pierre-François Duc
16
SPDX-FileCopyrightText: Malte Fritz
17
SPDX-FileCopyrightText: Jonas Freißmann
18
19
SPDX-License-Identifier: MIT
20
21
"""
22
from pyomo.core import Binary
23
from pyomo.core import BuildAction
24
from pyomo.core import Constraint
25
from pyomo.core import Expression
26
from pyomo.core import NonNegativeReals
27
from pyomo.core import Set
28
from pyomo.core import Var
29
from pyomo.core.base.block import ScalarBlock
30
31
from . import _shared
32
33
34
class InvestNonConvexFlowBlock(ScalarBlock):
35
    r"""
36
    .. automethod:: _create_constraints
37
    .. automethod:: _create_variables
38
    .. automethod:: _create_sets
39
40
    .. automethod:: _objective_expression
41
    """
42
43
    def __init__(self, *args, **kwargs):
44
        super().__init__(*args, **kwargs)
45
46
    def _create(self, group=None):
47
        """Creates set, variables, constraints for all flow object with
48
        an attribute flow of type class:`.InvestNonConvexFlowBlock`.
49
50
        Parameters
51
        ----------
52
        group : list
53
            List of oemof.solph.InvestNonConvexFlowBlock objects for which
54
            the constraints are build.
55
        """
56
        if group is None:
57
            return None
58
59
        self._create_sets(group)
60
        self._create_variables()
61
        self._create_constraints()
62
63
    def _create_sets(self, group):
64
        """
65
        Creates all sets for investment non-convex flows.
66
67
        INVEST_NON_CONVEX_FLOWS
68
            A set of flows with the attribute `nonconvex` of type
69
            :class:`.options.NonConvex` and the attribute `invest`
70
            of type :class:`.options.Invest`.
71
72
        Also creates :py:func:`sets_for_non_convex_flows`.
73
        """
74
        self.INVEST_NON_CONVEX_FLOWS = Set(
75
            initialize=[(g[0], g[1]) for g in group]
76
        )
77
78
        self.LINEAR_INVEST_NON_CONVEX_FLOWS = Set(
79
            initialize=[
80
                (g[0], g[1])
81
                for g in group
82
                if g[2].investment.nonconvex is False
83
            ]
84
        )
85
86
        self.OFFSET_INVEST_NON_CONVEX_FLOWS = Set(
87
            initialize=[
88
                (g[0], g[1])
89
                for g in group
90
                if g[2].investment.nonconvex is True
91
            ]
92
        )
93
94
        _shared.sets_for_non_convex_flows(self, group)
95
96
    def _create_variables(self):
97
        r"""
98
        Status variable (binary) `om.InvestNonConvexFlowBlock.status`:
99
            Variable indicating if flow is >= 0 indexed by FLOWS
100
101
        :math::`P_{invest}` `InvestNonConvexFlowBlock.invest`
102
            Value of the investment variable, i.e. equivalent to the nominal
103
            value of the flows after optimization.
104
105
        :math::
106
            `Y_{invest_status}(i,o,p)` `InvestNonConvexFlowBlock.invest_status`
107
            Binary variable representing whether or not an investment is made.
108
109
        :math::`status\_nominal(i,o,t)` (non-negative real number)
110
            New paramater representing the multiplication of `P_{invest}`
111
            (from the <class 'oemof.solph.flows.InvestmentFlow'>) and
112
            `status(i,o,t)` (from the
113
            <class 'oemof.solph.flows.NonConvexFlow'>)
114
            used for the constraints on the minimum and maximum
115
            flow constraints.
116
117
        Also creates :py:func:`variables_for_non_convex_flows`.
118
        """
119
120
        m = self.parent_block()
121
        # Create `status` variable representing the status of the flow
122
        # at each time step
123
        self.status = Var(
124
            self.INVEST_NON_CONVEX_FLOWS, m.TIMESTEPS, within=Binary
125
        )
126
127
        _shared.variables_for_non_convex_flows(self)
128
129
        # Investment-related variable similar to the
130
        # <class 'oemof.solph.flows.InvestmentFlow'> class.
131
132
        def _investvar_bound_rule(block, i, o, p):
133
            """Rule definition for bounds of the invest variable."""
134
            if (i, o) in self.LINEAR_INVEST_NON_CONVEX_FLOWS:
135
                return (
136
                    m.flows[i, o].investment.minimum[p],
137
                    m.flows[i, o].investment.maximum[p],
138
                )
139
            elif (i, o) in self.OFFSET_INVEST_NON_CONVEX_FLOWS:
140
                return 0, m.flows[i, o].investment.maximum[p]
141
142
        # Create the `invest` variable for the nonconvex investment flow.
143
        self.invest = Var(
144
            self.INVEST_NON_CONVEX_FLOWS,
145
            m.PERIODS,
146
            within=NonNegativeReals,
147
            bounds=_investvar_bound_rule,
148
        )
149
150
        # `invest_status` is a parameter which represents whether or not an
151
        # investment is made. This way the investment offset cost only apply
152
        # when the component is installed.
153
        self.invest_status = Var(
154
            self.OFFSET_INVEST_NON_CONVEX_FLOWS,
155
            m.PERIODS,
156
            within=Binary,
157
        )
158
159
        # `status_nominal` is a parameter which represents the
160
        # multiplication of a binary variable (`status`)
161
        # and a continuous variable (`invest` or `nominal_capacity`)
162
        self.status_nominal = Var(
163
            self.INVEST_NON_CONVEX_FLOWS, m.TIMESTEPS, within=NonNegativeReals
164
        )
165
166
    def _create_constraints(self):
167
        r"""
168
        .. automethod:: _minimum_invest_constraint
169
        .. automethod:: _maximum_invest_constraint
170
        .. automethod:: _linearised_investment_constraints
171
172
        Also creates
173
        * :py:func:`shared_constraints_for_non_convex_flows`,
174
        * :py:func:`minimum_flow_constraint`, and
175
        * :py:func:`maximum_flow_constraint`.
176
        """
177
        _shared.shared_constraints_for_non_convex_flows(self)
178
179
        self.minimum_investment = self._minimum_invest_constraint()
180
        self.maximum_investment = self._maximum_invest_constraint()
181
182
        self.min = _shared.minimum_flow_constraint(self)
183
        self.max = _shared.maximum_flow_constraint(self)
184
185
        self._linearised_investment_constraints()
186
187
    def _linearised_investment_constraints(self):
188
        r"""
189
        The resulting constraint is equivalent to
190
191
        .. math::
192
            status\_nominal(i,o,t) = Y_{status}(t) \cdot P_{invest}.
193
194
        However, :math:`status` and :math:`invest` are variables
195
        (binary and continuous, respectively).
196
        Thus, three constraints are created which combination is equivalent.
197
198
199
        .. automethod:: _linearised_investment_constraint_1
200
        .. automethod:: _linearised_investment_constraint_2
201
        .. automethod:: _linearised_investment_constraint_3
202
203
        The following cases may occur:
204
205
        * Case :math:`status = 0`
206
            .. math::
207
                (1) \Rightarrow status\_nominal = 0,\\
208
                (2) \Rightarrow \text{ trivially fulfilled},\\
209
                (3) \Rightarrow \text{ trivially fulfilled}.
210
211
        * Case :math:`status = 1`
212
            .. math::
213
                (1) \Rightarrow \text{ trivially fulfilled},\\
214
                (2) \Rightarrow status\_nominal \leq P_{invest},\\
215
                (3) \Rightarrow status\_nominal \geq P_{invest}.
216
217
            So, in total :math:`status\_nominal = P_{invest}`,
218
            which is the desired result.
219
        """
220
        self.invest_nc_one = self._linearised_investment_constraint_1()
221
        self.invest_nc_two = self._linearised_investment_constraint_2()
222
        self.invest_nc_three = self._linearised_investment_constraint_3()
223
224
    def _linearised_investment_constraint_1(self):
225
        r"""
226
        .. math::
227
            status\_nominal(i,o,t)
228
            \leq Y_{status}(t) \cdot P_{invest, max}\quad (1)
229
        """
230
        m = self.parent_block()
231
232
        def _linearization_rule_invest_non_convex_one(_, i, o, p, t):
233
            expr = (
234
                self.status[i, o, t] * m.flows[i, o].investment.maximum[p]
235
                >= self.status_nominal[i, o, t]
236
            )
237
            return expr
238
239
        return Constraint(
240
            self.MIN_FLOWS,
241
            m.TIMEINDEX,
242
            rule=_linearization_rule_invest_non_convex_one,
243
        )
244
245
    def _linearised_investment_constraint_2(self):
246
        r"""
247
        .. math::
248
            status\_nominal(i,o,t) \leq P_{invest}\quad (2)
249
        """
250
251
        m = self.parent_block()
252
253
        def _linearization_rule_invest_non_convex_two(_, i, o, p, t):
254
            expr = self.invest[i, o, p] >= self.status_nominal[i, o, t]
255
            return expr
256
257
        return Constraint(
258
            self.MIN_FLOWS,
259
            m.TIMEINDEX,
260
            rule=_linearization_rule_invest_non_convex_two,
261
        )
262
263
    def _linearised_investment_constraint_3(self):
264
        r"""
265
        .. math::
266
            status\_nominal(i,o,t) \geq
267
            P_{invest} - (1 - Y_{status}(t)) \cdot P_{invest, max}\quad (3)
268
        """
269
270
        m = self.parent_block()
271
272
        def _linearization_rule_invest_non_convex_three(_, i, o, p, t):
273
            expr = (
274
                self.invest[i, o, p]
275
                - (1 - self.status[i, o, t])
276
                * m.flows[i, o].investment.maximum[p]
277
                <= self.status_nominal[i, o, t]
278
            )
279
            return expr
280
281
        return Constraint(
282
            self.MIN_FLOWS,
283
            m.TIMEINDEX,
284
            rule=_linearization_rule_invest_non_convex_three,
285
        )
286
287
    def _objective_expression(self):
288
        r"""Objective expression for nonconvex investment flows.
289
290
            If `nonconvex.startup_costs` is set by the user:
291
                .. math::
292
                    \sum_{i, o \in STARTUPFLOWS} \sum_t  startup(i, o, t) \
293
                    \cdot c_{startup}
294
295
            If `nonconvex.shutdown_costs` is set by the user:
296
                .. math::
297
                    \sum_{i, o \in SHUTDOWNFLOWS} \sum_t shutdown(i, o, t) \
298
                    \cdot c_{shutdown}
299
300
            .. math::
301
                P_{invest} \cdot c_{ep} + c_{offset} \cdot Y_{invest, status}
302
        """
303
        if not hasattr(self, "INVEST_NON_CONVEX_FLOWS"):
304
            return 0
305
306
        m = self.parent_block()
307
308
        startup_costs = _shared.startup_costs(self)
309
        shutdown_costs = _shared.shutdown_costs(self)
310
        activity_costs = _shared.activity_costs(self)
311
        inactivity_costs = _shared.inactivity_costs(self)
312
        investment_costs = 0
313
314
        for i, o in self.LINEAR_INVEST_NON_CONVEX_FLOWS:
315
            for p in m.PERIODS:
316
                investment_costs += (
317
                    self.invest[i, o, p] * m.flows[i, o].investment.ep_costs[p]
318
                )
319
320
        for i, o in self.OFFSET_INVEST_NON_CONVEX_FLOWS:
321
            for p in m.PERIODS:
322
                investment_costs += (
323
                    self.invest[i, o, p] * m.flows[i, o].investment.ep_costs[p]
324
                    + m.flows[i, o].investment.offset[p]
325
                    * self.invest_status[i, o, p]
326
                )
327
328
        self.investment_costs = Expression(expr=investment_costs)
329
330
        return (
331
            startup_costs
332
            + shutdown_costs
333
            + activity_costs
334
            + inactivity_costs
335
            + investment_costs
336
        )
337
338 View Code Duplication
    def _minimum_invest_constraint(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
339
        r"""
340
        .. math::
341
                P_{invest, min} \cdot Y_{invest, status} \le P_{invest}
342
        """
343
        m = self.parent_block()
344
345
        def _min_invest_rule(_):
346
            """Rule definition for applying a minimum investment"""
347
            for i, o in self.OFFSET_INVEST_NON_CONVEX_FLOWS:
348
                for p in m.PERIODS:
349
                    expr = (
350
                        m.flows[i, o].investment.minimum[p]
351
                        * self.invest_status[i, o, p]
352
                        <= self.invest[i, o, p]
353
                    )
354
                    self.minimum_investment.add((i, o, p), expr)
355
356
        self.minimum_investment = Constraint(
357
            self.INVEST_NON_CONVEX_FLOWS, m.PERIODS, noruleinit=True
358
        )
359
        self.minimum_rule_build = BuildAction(rule=_min_invest_rule)
360
361
        return self.minimum_investment
362
363 View Code Duplication
    def _maximum_invest_constraint(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
364
        r"""
365
        .. math::
366
            P_{invest} \le P_{invest, max} \cdot Y_{invest, status}
367
        """
368
        m = self.parent_block()
369
370
        def _max_invest_rule(_):
371
            """Rule definition for applying a minimum investment"""
372
            for i, o in self.OFFSET_INVEST_NON_CONVEX_FLOWS:
373
                for p in m.PERIODS:
374
                    expr = (
375
                        self.invest[i, o, p]
376
                        <= m.flows[i, o].investment.maximum[p]
377
                        * self.invest_status[i, o, p]
378
                    )
379
                    self.maximum_investment.add((i, o, p), expr)
380
381
        self.maximum_investment = Constraint(
382
            self.INVEST_NON_CONVEX_FLOWS, m.PERIODS, noruleinit=True
383
        )
384
        self.maximum_rule_build = BuildAction(rule=_max_invest_rule)
385
386
        return self.maximum_investment
387