solph.flows._non_convex_flow_block   C
last analyzed

Complexity

Total Complexity 53

Size/Duplication

Total Lines 799
Duplicated Lines 6.51 %

Importance

Changes 0
Metric Value
wmc 53
eloc 371
dl 52
loc 799
rs 6.96
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A NonConvexFlowBlock._startup_costs() 0 24 4
B NonConvexFlowBlock._sets_for_non_convex_flows() 0 113 1
A NonConvexFlowBlock._objective_expression() 0 32 2
A NonConvexFlowBlock._create_constraints() 0 16 1
A NonConvexFlowBlock._create_variables() 0 30 4
A NonConvexFlowBlock._create_sets() 0 15 1
A NonConvexFlowBlock._variables_for_non_convex_flows() 0 42 5
A NonConvexFlowBlock._create() 0 16 2
A NonConvexFlowBlock.__init__() 0 2 1
A NonConvexFlowBlock._min_uptime_constraint() 0 49 2
C NonConvexFlowBlock._shared_constraints_for_non_convex_flows() 0 124 7
A NonConvexFlowBlock._min_downtime_constraint() 0 53 2
A NonConvexFlowBlock._shutdown_costs() 0 26 4
A NonConvexFlowBlock._max_startup_constraint() 0 15 1
A NonConvexFlowBlock._status_nominal_constraint() 0 18 1
A NonConvexFlowBlock._minimum_flow_constraint() 0 19 1
A NonConvexFlowBlock._inactivity_costs() 26 26 4
A NonConvexFlowBlock._max_shutdown_constraint() 0 15 1
A NonConvexFlowBlock._maximum_flow_constraint() 0 19 1
A NonConvexFlowBlock._activity_costs() 26 26 4
A NonConvexFlowBlock._startup_constraint() 0 25 2
A NonConvexFlowBlock._shutdown_constraint() 0 25 2

How to fix   Duplicated Code    Complexity   

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:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like solph.flows._non_convex_flow_block often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
"""Creating sets, variables, constraints and parts of the objective function
4
for Flow objects with nonconvex but without 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
14
15
SPDX-License-Identifier: MIT
16
17
"""
18
19
from pyomo.core import Binary
20
from pyomo.core import BuildAction
21
from pyomo.core import Constraint
22
from pyomo.core import Expression
23
from pyomo.core import NonNegativeReals
24
from pyomo.core import Set
25
from pyomo.core import Var
26
from pyomo.core.base.block import ScalarBlock
27
28
from oemof.solph._plumbing import valid_sequence
29
30
31
class NonConvexFlowBlock(ScalarBlock):
32
    r"""
33
    .. automethod:: _create_constraints
34
    .. automethod:: _create_variables
35
    .. automethod:: _create_sets
36
37
    .. automethod:: _objective_expression
38
39
    Parameters are defined in :class:`Flow`.
40
    """
41
42
    def __init__(self, *args, **kwargs):
43
        super().__init__(*args, **kwargs)
44
45
    def _create(self, group=None):
46
        """Creates set, variables, constraints for all flow object with
47
        an attribute flow of type class:`.NonConvexFlowBlock`.
48
49
        Parameters
50
        ----------
51
        group : list
52
            List of oemof.solph.NonConvexFlowBlock objects for which
53
            the constraints are build.
54
        """
55
        if group is None:
56
            return None
57
58
        self._create_sets(group)
59
        self._create_variables()
60
        self._create_constraints()
61
62
    def _create_sets(self, group):
63
        r"""
64
        **The following sets are created:** (-> see basic sets at
65
        :class:`.Model` )
66
67
        NONCONVEX_FLOWS
68
            A set of flows with the attribute `nonconvex` of type
69
            :class:`.options.NonConvex`.
70
71
72
        .. automethod:: _sets_for_non_convex_flows
73
        """
74
        self.NONCONVEX_FLOWS = Set(initialize=[(g[0], g[1]) for g in group])
75
76
        self._sets_for_non_convex_flows(group)
77
78
    def _create_variables(self):
79
        r"""
80
        :math:`Y_{status}` (binary) `om.NonConvexFlowBlock.status`:
81
            Variable indicating if flow is >= 0
82
83
        :math:`P_{max,status}` Status_nominal (continuous)
84
            Variable indicating if flow is >= 0
85
86
        .. automethod:: _variables_for_non_convex_flows
87
        """
88
        m = self.parent_block()
89
        self.status = Var(self.NONCONVEX_FLOWS, m.TIMESTEPS, within=Binary)
90
        for o, i in self.NONCONVEX_FLOWS:
91
            if m.flows[o, i].nonconvex.initial_status is not None:
92
                for t in range(
93
                    0, m.flows[o, i].nonconvex.first_flexible_timestep
94
                ):
95
                    self.status[o, i, t] = m.flows[
96
                        o, i
97
                    ].nonconvex.initial_status
98
                    self.status[o, i, t].fix()
99
100
        # `status_nominal` is a parameter which represents the
101
        # multiplication of a binary variable (`status`)
102
        # and a continuous variable (`invest` or `nominal_capacity`)
103
        self.status_nominal = Var(
104
            self.NONCONVEX_FLOWS, m.TIMESTEPS, within=NonNegativeReals
105
        )
106
107
        self._variables_for_non_convex_flows()
108
109
    def _create_constraints(self):
110
        """
111
        The following constraints are created:
112
113
        .. automethod:: _status_nominal_constraint
114
        .. automethod:: _minimum_flow_constraint
115
        .. automethod:: _maximum_flow_constraint
116
        .. automethod:: _shared_constraints_for_non_convex_flows
117
118
        """
119
120
        self.status_nominal_constraint = self._status_nominal_constraint()
121
        self.min = self._minimum_flow_constraint()
122
        self.max = self._maximum_flow_constraint()
123
124
        self._shared_constraints_for_non_convex_flows()
125
126
    def _objective_expression(self):
127
        r"""
128
        The following terms are to the cost function:
129
130
        .. automethod:: _startup_costs
131
        .. automethod:: _shutdown_costs
132
        .. automethod:: _activity_costs
133
        .. automethod:: _inactivity_costs
134
        """
135
        if not hasattr(self, "NONCONVEX_FLOWS"):
136
            return 0
137
138
        startup_costs = self._startup_costs()
139
        shutdown_costs = self._shutdown_costs()
140
        activity_costs = self._activity_costs()
141
        inactivity_costs = self._inactivity_costs()
142
143
        self.activity_costs = Expression(expr=activity_costs)
144
        self.inactivity_costs = Expression(expr=inactivity_costs)
145
        self.startup_costs = Expression(expr=startup_costs)
146
        self.shutdown_costs = Expression(expr=shutdown_costs)
147
148
        self.costs = Expression(
149
            expr=(
150
                startup_costs
151
                + shutdown_costs
152
                + activity_costs
153
                + inactivity_costs
154
            )
155
        )
156
157
        return self.costs
158
159
    def _sets_for_non_convex_flows(self, group):
160
        r"""Creates all sets for non-convex flows.
161
162
        MIN_FLOWS
163
            A subset of set NONCONVEX_FLOWS with the attribute `min`
164
            being not None in the first timestep.
165
        ACTIVITYCOSTFLOWS
166
            A subset of set NONCONVEX_FLOWS with the attribute
167
            `activity_costs` being not None.
168
        INACTIVITYCOSTFLOWS
169
            A subset of set NONCONVEX_FLOWS with the attribute
170
            `inactivity_costs` being not None.
171
        STARTUPFLOWS
172
            A subset of set NONCONVEX_FLOWS with the attribute
173
            `maximum_startups` or `startup_costs`
174
            being not None.
175
        MAXSTARTUPFLOWS
176
            A subset of set STARTUPFLOWS with the attribute
177
            `maximum_startups` being not None.
178
        SHUTDOWNFLOWS
179
            A subset of set NONCONVEX_FLOWS with the attribute
180
            `maximum_shutdowns` or `shutdown_costs`
181
            being not None.
182
        MAXSHUTDOWNFLOWS
183
            A subset of set SHUTDOWNFLOWS with the attribute
184
            `maximum_shutdowns` being not None.
185
        MINUPTIMEFLOWS
186
            A subset of set NONCONVEX_FLOWS with the attribute
187
            `minimum_uptime` being > 0.
188
        MINDOWNTIMEFLOWS
189
            A subset of set NONCONVEX_FLOWS with the attribute
190
            `minimum_downtime` being > 0.
191
        POSITIVE_GRADIENT_FLOWS
192
            A subset of set NONCONVEX_FLOWS with the attribute
193
            `positive_gradient` being not None.
194
        NEGATIVE_GRADIENT_FLOWS
195
            A subset of set NONCONVEX_FLOWS with the attribute
196
            `negative_gradient` being not None.
197
        """
198
        self.MIN_FLOWS = Set(
199
            initialize=[(g[0], g[1]) for g in group if g[2].min[0] is not None]
200
        )
201
        self.STARTUPFLOWS = Set(
202
            initialize=[
203
                (g[0], g[1])
204
                for g in group
205
                if g[2].nonconvex.startup_costs[0] is not None
206
                or g[2].nonconvex.maximum_startups is not None
207
            ]
208
        )
209
        self.MAXSTARTUPFLOWS = Set(
210
            initialize=[
211
                (g[0], g[1])
212
                for g in group
213
                if g[2].nonconvex.maximum_startups is not None
214
            ]
215
        )
216
        self.SHUTDOWNFLOWS = Set(
217
            initialize=[
218
                (g[0], g[1])
219
                for g in group
220
                if g[2].nonconvex.shutdown_costs[0] is not None
221
                or g[2].nonconvex.maximum_shutdowns is not None
222
            ]
223
        )
224
        self.MAXSHUTDOWNFLOWS = Set(
225
            initialize=[
226
                (g[0], g[1])
227
                for g in group
228
                if g[2].nonconvex.maximum_shutdowns is not None
229
            ]
230
        )
231
        self.MINUPTIMEFLOWS = Set(
232
            initialize=[
233
                (g[0], g[1])
234
                for g in group
235
                if g[2].nonconvex.minimum_uptime.max() > 0
236
            ]
237
        )
238
        self.MINDOWNTIMEFLOWS = Set(
239
            initialize=[
240
                (g[0], g[1])
241
                for g in group
242
                if g[2].nonconvex.minimum_downtime.max() > 0
243
            ]
244
        )
245
        self.NEGATIVE_GRADIENT_FLOWS = Set(
246
            initialize=[
247
                (g[0], g[1])
248
                for g in group
249
                if g[2].nonconvex.negative_gradient_limit[0] is not None
250
            ]
251
        )
252
        self.POSITIVE_GRADIENT_FLOWS = Set(
253
            initialize=[
254
                (g[0], g[1])
255
                for g in group
256
                if g[2].nonconvex.positive_gradient_limit[0] is not None
257
            ]
258
        )
259
        self.ACTIVITYCOSTFLOWS = Set(
260
            initialize=[
261
                (g[0], g[1])
262
                for g in group
263
                if g[2].nonconvex.activity_costs[0] is not None
264
            ]
265
        )
266
267
        self.INACTIVITYCOSTFLOWS = Set(
268
            initialize=[
269
                (g[0], g[1])
270
                for g in group
271
                if g[2].nonconvex.inactivity_costs[0] is not None
272
            ]
273
        )
274
275
    def _variables_for_non_convex_flows(self):
276
        r"""
277
        :math:`Y_{startup}` (binary) `NonConvexFlowBlock.startup`:
278
            Variable indicating startup of flow (component) indexed by
279
            STARTUPFLOWS
280
281
        :math:`Y_{shutdown}` (binary) `NonConvexFlowBlock.shutdown`:
282
            Variable indicating shutdown of flow (component) indexed by
283
            SHUTDOWNFLOWS
284
285
        :math:`\dot{P}_{up}` (continuous)
286
            `NonConvexFlowBlock.positive_gradient`:
287
            Variable indicating the positive gradient, i.e. the load increase
288
            between two consecutive timesteps, indexed by
289
            POSITIVE_GRADIENT_FLOWS
290
291
        :math:`\dot{P}_{down}` (continuous)
292
            `NonConvexFlowBlock.negative_gradient`:
293
            Variable indicating the negative gradient, i.e. the load decrease
294
            between two consecutive timesteps, indexed by
295
            NEGATIVE_GRADIENT_FLOWS
296
        """
297
        m = self.parent_block()
298
299
        if self.STARTUPFLOWS:
300
            self.startup = Var(self.STARTUPFLOWS, m.TIMESTEPS, within=Binary)
301
302
        if self.SHUTDOWNFLOWS:
303
            self.shutdown = Var(self.SHUTDOWNFLOWS, m.TIMESTEPS, within=Binary)
304
305
        if self.POSITIVE_GRADIENT_FLOWS:
306
            self.positive_gradient = Var(
307
                self.POSITIVE_GRADIENT_FLOWS,
308
                m.TIMESTEPS,
309
                within=NonNegativeReals,
310
            )
311
312
        if self.NEGATIVE_GRADIENT_FLOWS:
313
            self.negative_gradient = Var(
314
                self.NEGATIVE_GRADIENT_FLOWS,
315
                m.TIMESTEPS,
316
                within=NonNegativeReals,
317
            )
318
319
    def _startup_costs(self):
320
        r"""
321
        .. math::
322
            \sum_{i, o \in STARTUPFLOWS} \sum_t  Y_{startup}(t) \
323
            \cdot c_{startup}
324
        """
325
        startup_costs = 0
326
327
        if self.STARTUPFLOWS:
328
            m = self.parent_block()
329
330
            for i, o in self.STARTUPFLOWS:
331
                if valid_sequence(
332
                    m.flows[i, o].nonconvex.startup_costs, len(m.TIMESTEPS)
333
                ):
334
                    startup_costs += sum(
335
                        self.startup[i, o, t]
336
                        * m.flows[i, o].nonconvex.startup_costs[t]
337
                        for t in m.TIMESTEPS
338
                    )
339
340
            self.startup_costs = Expression(expr=startup_costs)
341
342
        return startup_costs
343
344
    def _shutdown_costs(self):
345
        r"""
346
        .. math::
347
            \sum_{SHUTDOWNFLOWS} \sum_t Y_{shutdown}(t) \
348
            \cdot c_{shutdown}
349
        """
350
        shutdown_costs = 0
351
352
        if self.SHUTDOWNFLOWS:
353
            m = self.parent_block()
354
355
            for i, o in self.SHUTDOWNFLOWS:
356
                if valid_sequence(
357
                    m.flows[i, o].nonconvex.shutdown_costs,
358
                    len(m.TIMESTEPS),
359
                ):
360
                    shutdown_costs += sum(
361
                        self.shutdown[i, o, t]
362
                        * m.flows[i, o].nonconvex.shutdown_costs[t]
363
                        * m.tsam_weighting[t]
364
                        for t in m.TIMESTEPS
365
                    )
366
367
            self.shutdown_costs = Expression(expr=shutdown_costs)
368
369
        return shutdown_costs
370
371 View Code Duplication
    def _activity_costs(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
372
        r"""
373
        .. math::
374
            \sum_{ACTIVITYCOSTFLOWS} \sum_t Y_{status}(t) \
375
            \cdot c_{activity}
376
        """
377
        activity_costs = 0
378
379
        if self.ACTIVITYCOSTFLOWS:
380
            m = self.parent_block()
381
382
            for i, o in self.ACTIVITYCOSTFLOWS:
383
                if valid_sequence(
384
                    m.flows[i, o].nonconvex.activity_costs,
385
                    len(m.TIMESTEPS),
386
                ):
387
                    activity_costs += sum(
388
                        self.status[i, o, t]
389
                        * m.flows[i, o].nonconvex.activity_costs[t]
390
                        * m.tsam_weighting[t]
391
                        for t in m.TIMESTEPS
392
                    )
393
394
            self.activity_costs = Expression(expr=activity_costs)
395
396
        return activity_costs
397
398 View Code Duplication
    def _inactivity_costs(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
399
        r"""
400
        .. math::
401
            \sum_{INACTIVITYCOSTFLOWS} \sum_t (1 - Y_{status}(t)) \
402
            \cdot c_{inactivity}
403
        """
404
        inactivity_costs = 0
405
406
        if self.INACTIVITYCOSTFLOWS:
407
            m = self.parent_block()
408
409
            for i, o in self.INACTIVITYCOSTFLOWS:
410
                if valid_sequence(
411
                    m.flows[i, o].nonconvex.inactivity_costs,
412
                    len(m.TIMESTEPS),
413
                ):
414
                    inactivity_costs += sum(
415
                        (1 - self.status[i, o, t])
416
                        * m.flows[i, o].nonconvex.inactivity_costs[t]
417
                        * m.tsam_weighting[t]
418
                        for t in m.TIMESTEPS
419
                    )
420
421
            self.inactivity_costs = Expression(expr=inactivity_costs)
422
423
        return inactivity_costs
424
425
    def _min_downtime_constraint(self):
426
        r"""
427
        .. math::
428
            (Y_{status}(t-1) - Y_{status}(t)) \
429
            \cdot t_{down,minimum} \\
430
            \leq t_{down,minimum} \
431
            - \sum_{n=0}^{t_{down,minimum}-1} Y_{status}(t+n) \\
432
            \forall t \in \textrm{TIMESTEPS} | \\
433
            t \neq \{0..t_{down,minimum}\} \cup \
434
            \{t\_max-t_{down,minimum}..t\_max\} , \\
435
            \forall (i,o) \in \textrm{MINDOWNTIMEFLOWS}.
436
            \\ \\
437
            Y_{status}(t) = Y_{status,0} \\
438
            \forall t \in \textrm{TIMESTEPS} | \\
439
            t = \{0..t_{down,minimum}\} \cup \
440
            \{t\_max-t_{down,minimum}..t\_max\} , \\
441
            \forall (i,o) \in \textrm{MINDOWNTIMEFLOWS}.
442
        """
443
        m = self.parent_block()
444
445
        def min_downtime_rule(_, i, o, t):
446
            """
447
            Rule definition for min-downtime constraints of non-convex flows.
448
            """
449
            if (
450
                m.flows[i, o].nonconvex.first_flexible_timestep
451
                < t
452
                < m.TIMESTEPS.at(-1)
453
            ):
454
                # We have a 2D matrix of constraints,
455
                # so testing is easier then just calling the rule for valid t.
456
457
                expr = 0
458
                expr += (
459
                    self.status[i, o, t - 1] - self.status[i, o, t]
460
                ) * m.flows[i, o].nonconvex.minimum_downtime[t]
461
                expr += -m.flows[i, o].nonconvex.minimum_downtime[t]
462
                expr += sum(
463
                    self.status[i, o, d]
464
                    for d in range(
465
                        t,
466
                        min(
467
                            t + m.flows[i, o].nonconvex.minimum_downtime[t],
468
                            len(m.TIMESTEPS),
469
                        ),
470
                    )
471
                )
472
                return expr <= 0
473
            else:
474
                return Constraint.Skip
475
476
        return Constraint(
477
            self.MINDOWNTIMEFLOWS, m.TIMESTEPS, rule=min_downtime_rule
478
        )
479
480
    def _min_uptime_constraint(self):
481
        r"""
482
        .. math::
483
            (Y_{status}(t)-Y_{status}(t-1)) \cdot t_{up,minimum} \\
484
            \leq \sum_{n=0}^{t_{up,minimum}-1} Y_{status}(t+n) \\
485
            \forall t \in \textrm{TIMESTEPS} | \\
486
            t \neq \{0..t_{up,minimum}\} \cup \
487
            \{t\_max-t_{up,minimum}..t\_max\} , \\
488
            \forall (i,o) \in \textrm{MINUPTIMEFLOWS}.
489
            \\ \\
490
            Y_{status}(t) = Y_{status,0} \\
491
            \forall t \in \textrm{TIMESTEPS} | \\
492
            t = \{0..t_{up,minimum}\} \cup \
493
            \{t\_max-t_{up,minimum}..t\_max\} , \\
494
            \forall (i,o) \in \textrm{MINUPTIMEFLOWS}.
495
        """
496
        m = self.parent_block()
497
498
        def _min_uptime_rule(_, i, o, t):
499
            """
500
            Rule definition for min-uptime constraints of non-convex flows.
501
            """
502
            if (
503
                m.flows[i, o].nonconvex.first_flexible_timestep
504
                < t
505
                < m.TIMESTEPS.at(-1)
506
            ):
507
                # We have a 2D matrix of constraints,
508
                # so testing is easier then just calling the rule for valid t.
509
                expr = 0
510
                expr += (
511
                    self.status[i, o, t] - self.status[i, o, t - 1]
512
                ) * m.flows[i, o].nonconvex.minimum_uptime[t]
513
                expr += -sum(
514
                    self.status[i, o, u]
515
                    for u in range(
516
                        t,
517
                        min(
518
                            t + m.flows[i, o].nonconvex.minimum_uptime[t],
519
                            len(m.TIMESTEPS),
520
                        ),
521
                    )
522
                )
523
                return expr <= 0
524
            else:
525
                return Constraint.Skip
526
527
        return Constraint(
528
            self.MINUPTIMEFLOWS, m.TIMESTEPS, rule=_min_uptime_rule
529
        )
530
531
    def _shutdown_constraint(self):
532
        r"""
533
        .. math::
534
            Y_{shutdown}(t) \geq Y_{status}(t-1) - Y_{status}(t) \\
535
            \forall t \in \textrm{TIMESTEPS}, \\
536
            \forall \textrm{SHUTDOWNFLOWS}.
537
        """
538
        m = self.parent_block()
539
540
        def _shutdown_rule(_, i, o, t):
541
            """Rule definition for shutdown constraints of non-convex flows."""
542
            if t > m.TIMESTEPS.at(1):
543
                expr = (
544
                    self.shutdown[i, o, t]
545
                    >= self.status[i, o, t - 1] - self.status[i, o, t]
546
                )
547
            else:
548
                expr = (
549
                    self.shutdown[i, o, t]
550
                    >= m.flows[i, o].nonconvex.initial_status
551
                    - self.status[i, o, t]
552
                )
553
            return expr
554
555
        return Constraint(self.SHUTDOWNFLOWS, m.TIMESTEPS, rule=_shutdown_rule)
556
557
    def _startup_constraint(self):
558
        r"""
559
        .. math::
560
            Y_{startup}(t) \geq Y_{status}(t) - Y_{status}(t-1) \\
561
            \forall t \in \textrm{TIMESTEPS}, \\
562
            \forall \textrm{STARTUPFLOWS}.
563
        """
564
        m = self.parent_block()
565
566
        def _startup_rule(_, i, o, t):
567
            """Rule definition for startup constraint of nonconvex flows."""
568
            if t > m.TIMESTEPS.at(1):
569
                expr = (
570
                    self.startup[i, o, t]
571
                    >= self.status[i, o, t] - self.status[i, o, t - 1]
572
                )
573
            else:
574
                expr = (
575
                    self.startup[i, o, t]
576
                    >= self.status[i, o, t]
577
                    - m.flows[i, o].nonconvex.initial_status
578
                )
579
            return expr
580
581
        return Constraint(self.STARTUPFLOWS, m.TIMESTEPS, rule=_startup_rule)
582
583
    def _max_startup_constraint(self):
584
        r"""
585
        .. math::
586
            \sum_{t \in \textrm{TIMESTEPS}} Y_{startup}(t) \leq \
587
                N_{start}(i,o)\\
588
            \forall (i,o) \in \textrm{MAXSTARTUPFLOWS}.
589
        """
590
        m = self.parent_block()
591
592
        def _max_startup_rule(_, i, o):
593
            """Rule definition for maximum number of start-ups."""
594
            lhs = sum(self.startup[i, o, t] for t in m.TIMESTEPS)
595
            return lhs <= m.flows[i, o].nonconvex.maximum_startups
596
597
        return Constraint(self.MAXSTARTUPFLOWS, rule=_max_startup_rule)
598
599
    def _max_shutdown_constraint(self):
600
        r"""
601
        .. math::
602
            \sum_{t \in \textrm{TIMESTEPS}} Y_{startup}(t) \leq \
603
                N_{shutdown}(i,o)\\
604
            \forall (i,o) \in \textrm{MAXSHUTDOWNFLOWS}.
605
        """
606
        m = self.parent_block()
607
608
        def _max_shutdown_rule(_, i, o):
609
            """Rule definition for maximum number of start-ups."""
610
            lhs = sum(self.shutdown[i, o, t] for t in m.TIMESTEPS)
611
            return lhs <= m.flows[i, o].nonconvex.maximum_shutdowns
612
613
        return Constraint(self.MAXSHUTDOWNFLOWS, rule=_max_shutdown_rule)
614
615
    def _maximum_flow_constraint(self):
616
        r"""
617
        .. math::
618
            P(t) \leq max(i, o, t) \cdot P_{nom} \
619
                \cdot status(t), \\
620
            \forall t \in \textrm{TIMESTEPS}, \\
621
            \forall (i, o) \in \textrm{NONCONVEX_FLOWS}.
622
        """
623
        m = self.parent_block()
624
625
        def _maximum_flow_rule(_, i, o, t):
626
            """Rule definition for MILP maximum flow constraints."""
627
            expr = (
628
                self.status_nominal[i, o, t] * m.flows[i, o].max[t]
629
                >= m.flow[i, o, t]
630
            )
631
            return expr
632
633
        return Constraint(self.MIN_FLOWS, m.TIMESTEPS, rule=_maximum_flow_rule)
634
635
    def _minimum_flow_constraint(self):
636
        r"""
637
        .. math::
638
            P(t) \geq min(i, o, t) \cdot P_{nom} \
639
                \cdot Y_{status}(t), \\
640
            \forall (i, o) \in \textrm{NONCONVEX_FLOWS}, \\
641
            \forall t \in \textrm{TIMESTEPS}.
642
        """
643
        m = self.parent_block()
644
645
        def _minimum_flow_rule(_, i, o, t):
646
            """Rule definition for MILP minimum flow constraints."""
647
            expr = (
648
                self.status_nominal[i, o, t] * m.flows[i, o].min[t]
649
                <= m.flow[i, o, t]
650
            )
651
            return expr
652
653
        return Constraint(self.MIN_FLOWS, m.TIMESTEPS, rule=_minimum_flow_rule)
654
655
    def _status_nominal_constraint(self):
656
        r"""
657
        .. math::
658
            P_{max,status}(t) =  Y_{status}(t) \cdot P_{nom}, \\
659
            \forall t \in \textrm{TIMESTEPS}.
660
        """
661
        m = self.parent_block()
662
663
        def _status_nominal_rule(_, i, o, t):
664
            """Rule definition for status_nominal"""
665
            expr = (
666
                self.status_nominal[i, o, t]
667
                == self.status[i, o, t] * m.flows[i, o].nominal_capacity
668
            )
669
            return expr
670
671
        return Constraint(
672
            self.NONCONVEX_FLOWS, m.TIMESTEPS, rule=_status_nominal_rule
673
        )
674
675
    def _shared_constraints_for_non_convex_flows(self):
676
        r"""
677
678
        .. automethod:: _startup_constraint
679
        .. automethod:: _max_startup_constraint
680
        .. automethod:: _shutdown_constraint
681
        .. automethod:: _max_shutdown_constraint
682
        .. automethod:: _min_uptime_constraint
683
        .. automethod:: _min_downtime_constraint
684
685
        positive_gradient_constraint
686
            .. math::
687
688
                P(t) \cdot Y_{status}(t)
689
                - P(t-1) \cdot Y_{status}(t-1)  \leq \
690
                \dot{P}_{up}(t), \\
691
                \forall t \in \textrm{TIMESTEPS}.
692
693
        negative_gradient_constraint
694
            .. math::
695
                P(t-1) \cdot Y_{status}(t-1)
696
                - P(t) \cdot Y_{status}(t) \leq \
697
                \dot{P}_{down}(t), \\
698
                \forall t \in \textrm{TIMESTEPS}.
699
        """
700
        m = self.parent_block()
701
702
        self.startup_constr = self._startup_constraint()
703
        self.max_startup_constr = self._max_startup_constraint()
704
        self.shutdown_constr = self._shutdown_constraint()
705
        self.max_shutdown_constr = self._max_shutdown_constraint()
706
        self.min_uptime_constr = self._min_uptime_constraint()
707
        self.min_downtime_constr = self._min_downtime_constraint()
708
709
        def _positive_gradient_flow_constraint(_):
710
            r"""Rule definition for positive gradient constraint."""
711
            for i, o in self.POSITIVE_GRADIENT_FLOWS:
712
                for index in range(1, len(m.TIMEINDEX) + 1):
713
                    if m.TIMEINDEX[index][1] > 0:
714
                        lhs = (
715
                            m.flow[
716
                                i,
717
                                o,
718
                                m.TIMESTEPS[index],
719
                            ]
720
                            * self.status[i, o, m.TIMESTEPS[index]]
721
                            - m.flow[i, o, m.TIMESTEPS[index - 1]]
722
                            * self.status[i, o, m.TIMESTEPS[index - 1]]
723
                        )
724
                        rhs = self.positive_gradient[
725
                            i, o, m.TIMEINDEX[index][1]
726
                        ]
727
                        self.positive_gradient_constr.add(
728
                            (
729
                                i,
730
                                o,
731
                                m.TIMESTEPS[index],
732
                            ),
733
                            lhs <= rhs,
734
                        )
735
                    else:
736
                        lhs = self.positive_gradient[i, o, 0]
737
                        rhs = 0
738
                        self.positive_gradient_constr.add(
739
                            (
740
                                i,
741
                                o,
742
                                m.TIMESTEPS[index],
743
                            ),
744
                            lhs == rhs,
745
                        )
746
747
        self.positive_gradient_constr = Constraint(
748
            self.POSITIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True
749
        )
750
        self.positive_gradient_build = BuildAction(
751
            rule=_positive_gradient_flow_constraint
752
        )
753
754
        def _negative_gradient_flow_constraint(_):
755
            r"""Rule definition for negative gradient constraint."""
756
            for i, o in self.NEGATIVE_GRADIENT_FLOWS:
757
                for index in range(1, len(m.TIMESTEPS) + 1):
758
                    if m.TIMESTEPS[index] > 0:
759
                        lhs = (
760
                            m.flow[
761
                                i,
762
                                o,
763
                                m.TIMESTEPS[index - 1],
764
                            ]
765
                            * self.status[i, o, m.TIMESTEPS[index - 1]]
766
                            - m.flow[
767
                                i,
768
                                o,
769
                                m.TIMESTEPS[index],
770
                            ]
771
                            * self.status[i, o, m.TIMESTEPS[index]]
772
                        )
773
                        rhs = self.negative_gradient[i, o, m.TIMESTEPS[index]]
774
                        self.negative_gradient_constr.add(
775
                            (
776
                                i,
777
                                o,
778
                                m.TIMESTEPS[index],
779
                            ),
780
                            lhs <= rhs,
781
                        )
782
                    else:
783
                        lhs = self.negative_gradient[i, o, 0]
784
                        rhs = 0
785
                        self.negative_gradient_constr.add(
786
                            (
787
                                i,
788
                                o,
789
                                m.TIMESTEPS[index],
790
                            ),
791
                            lhs == rhs,
792
                        )
793
794
        self.negative_gradient_constr = Constraint(
795
            self.NEGATIVE_GRADIENT_FLOWS, m.TIMESTEPS, noruleinit=True
796
        )
797
        self.negative_gradient_build = BuildAction(
798
            rule=_negative_gradient_flow_constraint
799
        )
800