| 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 |