Total Complexity | 53 |
Total Lines | 783 |
Duplicated Lines | 4.98 % |
Changes | 0 |
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:
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 | |||
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): |
|
|
|||
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): |
|
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 |