Completed
Push — dev ( 91c66b...5706ca )
by Patrik
22s queued 17s
created

NonConvexFlowBlock._inactivity_costs()   A

Complexity

Conditions 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

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