|
19 | 19 | 'switch_model.generators.core.build', 'switch_model.generators.core.dispatch' |
20 | 20 | ) |
21 | 21 |
|
| 22 | +def _min_up_tp_window(m, g, tp): |
| 23 | + """ |
| 24 | + how many timepoints must the project stay on once it's started? |
| 25 | + note: StartupGenCapacity and ShutdownGenCapacity are assumed to occur |
| 26 | + at the start of the timepoint |
| 27 | + """ |
| 28 | + return int(round(m.gen_min_uptime[g] / m.ts_duration_of_tp[m.tp_ts[tp]])) |
| 29 | +def _min_down_tp_window(m, g, tp): |
| 30 | + """ |
| 31 | + how many timepoints must the project stay off once it's shutdown? |
| 32 | + note: StartupGenCapacity and ShutdownGenCapacity are assumed to occur |
| 33 | + at the start of the timepoint |
| 34 | + """ |
| 35 | + return int(round(m.gen_min_downtime[g] / m.ts_duration_of_tp[m.tp_ts[tp]])) |
| 36 | + |
| 37 | + |
22 | 38 | def define_components(mod): |
23 | 39 | """ |
24 | 40 |
|
@@ -291,73 +307,45 @@ def define_components(mod): |
291 | 307 | default=0.0) |
292 | 308 | mod.UPTIME_CONSTRAINED_GEN_TPS = Set(dimen=2, initialize=lambda m: [ |
293 | 309 | (g, tp) |
294 | | - for g in m.GENERATION_PROJECTS if m.gen_min_uptime[g] > 0.0 |
295 | | - for tp in m.TPS_FOR_GEN[g] |
| 310 | + for g in m.GENERATION_PROJECTS |
| 311 | + if m.gen_min_uptime[g] > 0.0 |
| 312 | + for tp in m.TPS_FOR_GEN[g] |
| 313 | + if _min_up_tp_window(m, g ,tp) >= 0 |
296 | 314 | ]) |
297 | 315 | mod.DOWNTIME_CONSTRAINED_GEN_TPS = Set(dimen=2, initialize=lambda m: [ |
298 | 316 | (g, tp) |
299 | | - for g in m.GENERATION_PROJECTS if m.gen_min_downtime[g] > 0.0 |
300 | | - for tp in m.TPS_FOR_GEN[g] |
| 317 | + for g in m.GENERATION_PROJECTS |
| 318 | + if m.gen_min_downtime[g] > 0.0 |
| 319 | + for tp in m.TPS_FOR_GEN[g] |
| 320 | + if _min_down_tp_window(m, g, tp) >= 0 |
301 | 321 | ]) |
302 | 322 |
|
303 | 323 | def tp_prev(m, tp, n=1): |
304 | 324 | # find nth previous timepoint, wrapping from start to end of day |
305 | 325 | return m.TPS_IN_TS[m.tp_ts[tp]].prevw(tp, n) |
306 | | - # min_time_projects = set() |
307 | | - def min_time_rule(m, g, tp, up): |
308 | | - """ This uses a simple rule: all capacity turned on in the last x |
309 | | - hours must still be on now (or all capacity recently turned off |
310 | | - must still be off).""" |
311 | | - |
312 | | - # how many timepoints must the project stay on/off once it's |
313 | | - # started/shutdown? |
314 | | - # note: StartupGenCapacity and ShutdownGenCapacity are assumed to occur at the start of |
315 | | - # the timepoint |
316 | | - n_tp = int(round( |
317 | | - (m.gen_min_uptime[g] if up else m.gen_min_downtime[g]) |
318 | | - / m.ts_duration_of_tp[m.tp_ts[tp]] |
319 | | - )) |
320 | | - if n_tp == 0: |
321 | | - # project can be shutdown and restarted in the same timepoint |
322 | | - rule = Constraint.Skip |
323 | | - else: |
324 | | - # note: this rule stops one short of n_tp steps back (normal |
325 | | - # behavior of range()), because the current timepoint is |
326 | | - # included in the duration when the capacity will be on/off. |
327 | | - if up: |
328 | | - rule = ( |
329 | | - # online capacity >= recent startups |
330 | | - # (all recent startups are still online) |
331 | | - m.CommitGen[g, tp] |
332 | | - >= |
333 | | - sum(m.StartupGenCapacity[g, tp_prev(m, tp, i)] for i in range(n_tp)) |
334 | | - ) |
335 | | - else: |
336 | | - # Find the largest fraction of capacity that could have |
337 | | - # been committed in the last x hours, including the |
338 | | - # current hour. We assume that everything above this band |
339 | | - # must remain turned off (e.g., on maintenance outage). |
340 | | - # Note: this band extends one step prior to the first |
341 | | - # relevant shutdown, since that capacity could have been |
342 | | - # online in the prior step. |
343 | | - committable_fraction = m.gen_availability[g] * max( |
344 | | - m.gen_max_commit_fraction[g, tp_prev(m, tp, i)] |
345 | | - for i in range(n_tp+1) |
346 | | - ) |
347 | | - rule = ( |
348 | | - # committable capacity - committed >= recent shutdowns |
349 | | - # (all recent shutdowns are still offline) |
350 | | - m.GenCapacityInTP[g, tp] * committable_fraction |
351 | | - - m.CommitGen[g, tp] |
352 | | - >= |
353 | | - sum(m.ShutdownGenCapacity[g, tp_prev(m, tp, i)] for i in range(n_tp)) |
354 | | - ) |
355 | | - return rule |
356 | 326 | mod.Enforce_Min_Uptime = Constraint( |
357 | | - mod.UPTIME_CONSTRAINED_GEN_TPS, rule=lambda *a: min_time_rule(*a, up=True) |
| 327 | + mod.UPTIME_CONSTRAINED_GEN_TPS, |
| 328 | + doc="All capacity turned on in the last x hours must still be on now", |
| 329 | + rule=lambda m, g, tp: ( |
| 330 | + m.CommitGen[g, tp] |
| 331 | + >= |
| 332 | + sum(m.StartupGenCapacity[g, tp_prev(m, tp, i)] |
| 333 | + for i in range(_min_up_tp_window(m, g ,tp)) |
| 334 | + ) |
| 335 | + ) |
358 | 336 | ) |
359 | 337 | mod.Enforce_Min_Downtime = Constraint( |
360 | | - mod.DOWNTIME_CONSTRAINED_GEN_TPS, rule=lambda *a: min_time_rule(*a, up=False) |
| 338 | + mod.DOWNTIME_CONSTRAINED_GEN_TPS, |
| 339 | + doc=("All recently shutdown capacity remains offline: " |
| 340 | + "committed <= committable capacity - recent shutdowns"), |
| 341 | + rule=lambda m, g, tp: ( |
| 342 | + m.CommitGen[g, tp] |
| 343 | + <= |
| 344 | + m.CommitUpperLimit[g, tp] |
| 345 | + - sum(m.ShutdownGenCapacity[g, tp_prev(m, tp, i)] |
| 346 | + for i in range(_min_down_tp_window(m, g, tp)) |
| 347 | + ) |
| 348 | + ) |
361 | 349 | ) |
362 | 350 |
|
363 | 351 | # Dispatch limits relative to committed capacity. |
|
0 commit comments