Total Complexity | 53 |
Total Lines | 799 |
Duplicated Lines | 6.51 % |
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 | 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): |
|
|
|||
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): |
|
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 |