3b_periodicLoop.gms 27.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ontext
This file is part of Backbone.

Backbone is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Backbone is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with Backbone.  If not, see <http://www.gnu.org/licenses/>.
$offtext

18
* =============================================================================
19
* --- Initialize unnecessary parameters and variables in order to save memory -
20
* =============================================================================
21

22
23
* --- Variables ---------------------------------------------------------------

24
25
26
27
28
29
30
31
32
// Free Variables
Option clear = v_gen;
Option clear = v_state;
Option clear = v_genRamp;
Option clear = v_transfer;
// Integer Variables
Option clear = v_online_MIP;
Option clear = v_invest_MIP;
Option clear = v_investTransfer_MIP;
33
34
// Binary Variables
Option clear = v_help_inc;
35
36
37
38
// SOS2 Variables
Option clear = v_sos2;
// Positive Variables
Option clear = v_fuelUse;
39
40
Option clear = v_startup_LP;
Option clear = v_startup_MIP;
41
42
Option clear = v_shutdown_LP;
Option clear = v_shutdown_MIP;
43
44
45
46
47
48
49
50
51
52
Option clear = v_genRampUpDown;
Option clear = v_spill;
Option clear = v_transferRightward;
Option clear = v_transferLeftward;
Option clear = v_resTransferRightward;
Option clear = v_resTransferLeftward;
Option clear = v_reserve;
Option clear = v_investTransfer_LP;
Option clear = v_online_LP;
Option clear = v_invest_LP;
53
Option clear = v_gen_inc;
54
55
56
57
58
// Feasibility control
Option clear = v_stateSlack;
Option clear = vq_gen;
Option clear = vq_resDemand;
Option clear = vq_resMissing;
59

60
61
* --- Equations ---------------------------------------------------------------

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Objective Function, Energy Balance, and Reserve demand
Option clear = q_obj;
Option clear = q_balance;
Option clear = q_resDemand;

// Unit Operation
Option clear = q_maxDownward;
Option clear = q_maxUpward;
Option clear = q_reserveProvision;
Option clear = q_startshut;
Option clear = q_startuptype;
Option clear = q_onlineLimit;
Option clear = q_onlineMinUptime;
Option clear = q_onlineCyclic;
Option clear = q_onlineOnStartUp;
Option clear = q_offlineAfterShutdown;
Option clear = q_genRamp;
Option clear = q_rampUpLimit;
Option clear = q_rampDownLimit;
Option clear = q_rampUpDown;
Option clear = q_rampSlack;
Option clear = q_outputRatioFixed;
Option clear = q_outputRatioConstrained;
Option clear = q_conversionDirectInputOutput;
Option clear = q_conversionSOS2InputIntermediate;
Option clear = q_conversionSOS2Constraint;
Option clear = q_conversionSOS2IntermediateOutput;
89
90
91
92
93
Option clear = q_conversionIncHR;
Option clear = q_conversionIncHRMaxGen;
Option clear = q_conversionIncHRBounds;
Option clear = q_conversionIncHR_help1;
Option clear = q_conversionIncHR_help2;
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
Option clear = q_fuelUseLimit;

// Energy Transfer
Option clear = q_transfer;
Option clear = q_transferRightwardLimit;
Option clear = q_transferLeftwardLimit;
Option clear = q_resTransferLimitRightward;
Option clear = q_resTransferLimitLeftward;
Option clear = q_reserveProvisionRightward;
Option clear = q_reserveProvisionLeftward;

// State Variables
Option clear = q_stateSlack;
Option clear = q_stateUpwardLimit;
Option clear = q_stateDownwardLimit;
Option clear = q_boundStateMaxDiff;
Option clear = q_boundCyclic;

// Policy
Option clear = q_inertiaMin;
Option clear = q_instantaneousShareMax;
Option clear = q_constrainedOnlineMultiUnit;
Option clear = q_capacityMargin;
Option clear = q_constrainedCapMultiUnit;
Option clear = q_emissioncap;
Option clear = q_energyShareMax;
Option clear = q_energyShareMin;
121
Option clear = q_minCons;
122
123
124

* --- Temporary Time Series ---------------------------------------------------

125
126
127
128
129
130
131
132
133
134
// Initialize temporary time series
Option clear = ts_unit_;
*Option clear = ts_effUnit_;
*Option clear = ts_effGroupUnit_;
Option clear = ts_influx_;
Option clear = ts_cf_;
Option clear = ts_unit_;
Option clear = ts_reserveDemand_;
Option clear = ts_node_;

135

136
* =============================================================================
137
* --- Determine the forecast-intervals included in the current solve ----------
138
* =============================================================================
139

140
// Determine the time steps of the current solve
141
tSolveFirst = ord(tSolve);  // tSolveFirst: the start of the current solve, t0 used only for initial values
142

143
144
145
* --- Build the forecast-time structure using the intervals -------------------

// Initializing forecast-time structure sets
146
Option clear = p_stepLength;
147
Option clear = msft;
148
Option clear = mft;
149
Option clear = ft;
150
Option clear = sft, clear = fts;
151
Option clear = mst_start, clear = mst_end;
152
$ifthen declared scenario
153
if(mSettings(mSolve, 'scenarios'),  // Only clear these if using long-term scenarios
154
155
    Options clear = s_active, clear = s_scenario, clear = ss,
            clear = p_msProbability, clear = ms_central;
156
);
157
$endif
158

159

160
// Initialize the set of active t:s, counters and interval time steps
161
Option clear = t_active;
162
Option clear = dt_active;
163
Option clear = tt_block;
164
Option clear = cc;
165
tCounter = 1;
166
count_sample = 1;
167

168
// Determine the set of active interval counters (or blocks of intervals)
169
cc(counter)${ mInterval(mSolve, 'stepsPerInterval', counter) }
170
    = yes;
171

172
173
174
175
176
177
// Update tForecastNext
tForecastNext(mSolve)
    ${ tSolveFirst >= tForecastNext(mSolve) }
    = tForecastNext(mSolve) + mSettings(mSolve, 't_forecastJump');

// Calculate forecast length
178
currentForecastLength
179
    = max(  mSettings(mSolve, 't_forecastLengthUnchanging'),  // Unchanging forecast length would remain the same
180
            mSettings(mSolve, 't_forecastLengthDecreasesFrom') - [mSettings(mSolve, 't_forecastJump') - {tForecastNext(mSolve) - tSolveFirst}] // While decreasing forecast length has a fixed horizon point and thus gets shorter
181
            );   // Larger forecast horizon is selected
182

183
184
185
// Is there any case where t_forecastLength should be larger than t_horizon? Could happen if one doesn't want to join forecasts at the end of the solve horizon.
// If not, add a check for currentForecastLength <= mSettings(mSolve, 't_horizon')
// and change the line below to 'tSolveLast = ord(tSolve) + mSettings(mSolve, 't_horizon');'
186
tSolveLast = ord(tSolve) + mSettings(mSolve, 't_horizon');  // tSolveLast: the end of the current solve
187
Option clear = t_current;
188
189
190
191
t_current(t_full(t))
    ${  ord(t) >= tSolveFirst
        and ord (t) <= tSolveLast
        }
192
193
    = yes;

194
// Loop over the defined blocks of intervals
195
loop(cc(counter),
196
197
198
199
200
201
202

    // Abort if stepsPerInterval is less than one
    if(mInterval(mSolve, 'stepsPerInterval', counter) < 1,
        abort "stepsPerInterval < 1 is not defined!";
    );  // END IF stepsPerInterval

    // Time steps within the current block
203
204
    option clear = tt;
    tt(t_current(t))
205
        ${ord(t) >= tSolveFirst + tCounter
206
207
          and ord(t) <= min(tSolveFirst
                            + mInterval(mSolve, 'lastStepInIntervalBlock', counter),
208
209
                            tSolveLast)
         } = yes;
210

211
212
213
    // Store the interval time steps for each interval block (counter)
    tt_block(counter, tt) = yes;

Erkka Rinne's avatar
Erkka Rinne committed
214
215
216
    // Initialize tInterval
    Option clear = tt_interval;

Erkka Rinne's avatar
Erkka Rinne committed
217
218
    // If stepsPerInterval equals one, simply use all the steps within the block
    if(mInterval(mSolve, 'stepsPerInterval', counter) = 1,
219
220
        // Include all time steps within the block
        tt_interval(tt(t)) = yes;
Erkka Rinne's avatar
Erkka Rinne committed
221
222
223

    // If stepsPerInterval exceeds 1 (stepsPerInterval < 1 not defined)
    elseif mInterval(mSolve, 'stepsPerInterval', counter) > 1,
224
225
226
227
228
229

        // Calculate the displacement required to reach the corresponding active time step from any time step
        dt_active(tt(t)) = - (mod(ord(t) - tSolveFirst - tCounter, mInterval(mSolve, 'stepsPerInterval', counter)));

        // Select the active time steps within the block
        tt_interval(tt(t))${ not dt_active(t) } = yes;
230

231
    ); // END ELSEIF intervalLenght
232

233
234
235
236
237
238
239
    // Calculate the interval length in hours
    p_stepLength(mf(mSolve, f_solve), tt_interval(t))
      = mInterval(mSolve, 'stepsPerInterval', counter) * mSettings(mSolve, 'stepLengthInHours');
    p_stepLengthNoReset(mf(mSolve, f_solve), tt_interval(t)) = p_stepLength(mSolve, f_solve, t);

    // Determine the combinations of forecasts and intervals
    // Include the t_jump for the realization
240
    ft(f_solve, tt_interval(t))
241
242
243
       ${ord(t) <= tSolveFirst + max(mSettings(mSolve, 't_jump'),
                                     min(mSettings(mSolve, 't_perfectForesight'),
                                         currentForecastLength))
244
245
246
247
         and mf_realization(mSolve, f_solve)
        } = yes;

    // Include the full horizon for the central forecast
248
    ft(f_solve, tt_interval(t))
249
250
251
      ${ord(t) > tSolveFirst + max(mSettings(mSolve, 't_jump'),
                                   min(mSettings(mSolve, 't_perfectForesight'),
                                       currentForecastLength))
252
        and (mf_central(mSolve, f_solve)
253
             or mSettings(mSolve, 'forecasts') = 0)
254
255
256
       } = yes;

    // Include up to forecastLength for remaining forecasts
257
    ft(f_solve, tt_interval(t))
258
259
      ${not mf_central(mSolve, f_solve)
        and not mf_realization(mSolve, f_solve)
260
261
262
        and ord(t) > tSolveFirst + max(mSettings(mSolve, 't_jump'),
                                       min(mSettings(mSolve, 't_perfectForesight'),
                                           currentForecastLength))
263
264
265
266
267
268
        and ord(t) <= tSolveFirst + currentForecastLength
       } = yes;

    // Update tActive
    t_active(tt_interval) = yes;

269
270
271
272
273
274
275
276
277
278
279
    // Update tCounter for the next block of intervals
    tCounter = mInterval(mSolve, 'lastStepInIntervalBlock', counter) + 1;

); // END loop(counter)

// Reset initial sample start and end times if using scenarios
if(mSettings(mSolve, 'scenarios'),
    Option clear = msStart, clear = msEnd;
    msStart(ms_initial) = 1;
    msEnd(ms_initial) = currentForecastLength + 1;
);
280

281
$ifthen defined scenario
282
283
284
285
// Create stochastic programming scenarios
// Select root sample and central forecast
loop((ms_initial(mSolve, s_), mf_central(mSolve, f)),
    s_active(s_) = yes;
286
    p_msProbability(mSolve, s_)$mSettings(mSolve, 'scenarios') = 1;
287
    loop(scenario $p_scenProbability(scenario),
288
289
290
291
292
293
294
295
296
297
298
        s_scenario(s_, scenario) = yes;
        if(mSettings(mSolve, 'scenarios') > 1,
            loop(ft(f, t)$(ord(t) >= msEnd(mSolve, s_) + tSolveFirst),
                loop(s$(ord(s) = mSettings(mSolve, 'samples') + count_sample),
                    s_active(s) = yes;
                    ms_central(mSolve, s) = yes;
                    s_scenario(s, scenario) = yes;
                    p_msProbability(mSolve, s) = p_scenProbability(scenario);
                    msStart(mSolve, s) = ord(t) - tSolveFirst;
                    msEnd(mSolve, s) = ord(t) - tSolveFirst
                                              + p_stepLength(mSolve, f, t);
299
                );
300
301
302
303
304
305
306
307
308
309
310
                count_sample = count_sample + 1;
            );
        elseif mSettings(mSolve, 'scenarios') = 1,
            loop(ms(mSolve, s)$(not sameas(s, s_)),
                s_active(s) = yes;
                ms_central(mSolve, s) = yes;
                p_msProbability(mSolve, s) = 1;
                s_scenario(s, scenario) = yes;
                msStart(mSolve, s) = msEnd(mSolve, s_);
                msEnd(mSolve, s) = msStart(mSolve, s_)
                                   + mSettings(mSolve, 't_horizon');
311
312
313
            );
        );
    );
314
315
316
    ms(ms_central(mSolve, s)) = yes;
    msf(ms_central(mSolve, s), f) = yes;
);
317
318
$endif

319
320
321
322
323
324
325
326
// Loop over defined samples
loop(msf(mSolve, s, f)$msStart(mSolve, s),
                      // Move the samples along with the dispatch
    sft(s, ft(f, t))${ord(t) > msStart(mSolve, s) + tSolveFirst - 1
                      and ord(t) < msEnd(mSolve, s) + tSolveFirst
                     } = yes;
);

327
328
329
330
331
332
// Update the model specific sets and the reversed dimension set
mft(mSolve, ft(f, t)) = yes;
ms(mSolve, s)$ms(mSolve, s) = s_active(s);
msf(mSolve, s, f)$msf(mSolve, s, f) = s_active(s);
msft(mSolve, sft(s, f, t)) = yes;
fts(ft(f, t), s)$sft(s, f, t) = yes;
333

334
* Build stochastic tree by definfing previous samples
335
$ifthen defined scenario
336
Option clear = s_prev;
337
loop(scenario $p_scenProbability(scenario),
338
339
340
341
342
    loop(s_scenario(s, scenario),
        if(not ms_initial(mSolve, s), ss(s, s_prev) = yes);
        Option clear = s_prev; s_prev(s) = yes;
    );
);
343
344
$endif

345
346
347
348
349

* --- Define sample offsets for creating stochastic scenarios -----------------

Option clear = dt_scenarioOffset;

350
$ifthen defined scenario
351
352
353
354
355
356
357
358
359
360
361
loop(s_scenario(s, scenario)$(ord(s) > 1 and ord(scenario) > 1),
    loop(gn_scenarios(grid, node, timeseries),
         dt_scenarioOffset(grid, node, timeseries, s)
             = (ord(scenario) - 1) * mSettings(mSolve, 'scenarioLength');
    );

    loop(gn_scenarios(flow, node, timeseries),
        dt_scenarioOffset(flow, node, timeseries, s)
            = (ord(scenario) - 1) * mSettings(mSolve, 'scenarioLength');
    );
);
362
$endif
363
364


365
366
367
368
369
370
371
* --- Determine various other forecast-time sets required for the model -------

// Set of realized intervals in the current solve
Option clear = ft_realized;
ft_realized(ft(f_solve, t))
    ${  mf_realization(mSolve, f_solve)
        and ord(t) <= tSolveFirst + mSettings(mSolve, 't_jump')
Topi Rasku's avatar
Topi Rasku committed
372
373
        }
    = yes;
374

375
376
377
Option clear = sft_realized;
sft_realized(sft(s, ft_realized(f_solve, t))) = yes;

378
379
// Update the set of realized intervals in the whole simulation so far
ft_realizedNoReset(ft_realized(f, t)) = yes;
380
sft_realizedNoReset(sft_realized(s, f, t)) = yes;
381
382
383
384
385
386
387
388
389
390
391
// Update the set of realized intervals in the whole simulation so far, including model and sample dimensions
msft_realizedNoReset(msft(mSolve, s, ft_realized(f, t))) = yes;

// Include the necessary amount of historical timesteps to the active time step set of the current solve
loop(ft_realizedNoReset(f, t),
    t_active(t)
        ${  ord(t) <= tSolveFirst
            and ord(t) > tSolveFirst + tmp_dt // Strict inequality accounts for tSolvefirst being one step before the first ft step.
            }
        = yes;
); // END loop(ft_realizedNoReset
392

393
// Time step displacement to reach previous time step
394
option clear = dt;
Topi Rasku's avatar
Topi Rasku committed
395
option clear = dt_next;
396
tmp = max(tSolveFirst + tmp_dt, 1); // The ord(t) of the first time step in t_active, cannot decrease below 1 to avoid referencing time steps before t000000
397
398
loop(t_active(t),
    dt(t) = tmp - ord(t);
Topi Rasku's avatar
Topi Rasku committed
399
    dt_next(t+dt(t)) = -dt(t);
400
401
402
    tmp = ord(t);
); // END loop(t_active)

403
404
405
406
407
408
409
410
411
412
413
414
// First model ft
Option clear = mft_start;
mft_start(mf_realization(mSolve, f), tSolve)
    = yes
;
// Last model fts
Option clear = mft_lastSteps;
mft_lastSteps(mSolve, ft(f,t))
    ${ not dt_next(t) }
    = yes
;

415
416
417
418
419
420
421
422
// Sample start and end intervals
loop(ms(mSolve, s),
    tmp = 1;
    tmp_ = 1;
    loop(t_active(t),
        if(tmp and ord(t) - tSolveFirst + 1 > msStart(mSolve, s),
            mst_start(mSolve, s, t) = yes;
            tmp = 0;
Niina Helistö's avatar
Niina Helistö committed
423
        );
424
425
426
427
428
429
430
431
432
433
434
435
        if(tmp_ and ord(t) - tSolveFirst + 1 > msEnd(mSolve, s),
            mst_end(mSolve, s, t+dt(t)) = yes;
            tmp_ = 0;
        );
    ); // END loop(t_active)
    // If the last interval of a sample is in mft_lastSteps, the method above does not work
    if(tmp_,
        mst_end(mSolve, s, t)${sum(f_solve, mft_lastSteps(mSolve, f_solve, t))} = yes;
    );
); // END loop(ms)
// Displacement from the first interval of a sample to the previous interval is always -1,
// except for stochastic samples
436
437
438
dt(t_active(t))
    ${ sum(ms(mSolve, s)$(not ms_central(mSolve, s)), mst_start(mSolve, s, t)) }
    = -1;
Niina Helistö's avatar
Niina Helistö committed
439

Niina Helistö's avatar
Niina Helistö committed
440
// Forecast index displacement between realized and forecasted intervals
441
// NOTE! This set cannot be reset without references to previously solved time steps in the stochastic tree becoming ill-defined!
442
443
444
df(f_solve(f), t_active(t))${ ord(t) <= tSolveFirst + max(mSettings(mSolve, 't_jump'),
                                                          min(mSettings(mSolve, 't_perfectForesight'),
                                                              currentForecastLength))}
445
    = sum(mf_realization(mSolve, f_), ord(f_) - ord(f));
446
// Displacement to reach the realized forecast
447
Option clear = df_realization;
448
loop(mf_realization(mSolve, f_),
449
    df_realization(ft(f, t))$[ord(t) <= tSolveFirst + currentForecastLength]
450
451
      = ord(f_) - ord(f);
);
452
// Central forecast for the long-term scenarios comes from a special forecast label
453
Option clear = df_scenario;
454
if(mSettings(mSolve, 'scenarios') > 1,
455
456
    loop((msft(ms_central(mSolve, s), f, t), mf_scenario(mSolve, f_)),
        df_scenario(ft(f, t)) = ord(f_) - ord(f);
457
458
    );
);
459
460
// Check that df_forecast and df_scenario do not overlap
loop(ft(f, t),
461
  if(df_realization(f, t) <> 0 and df_scenario(f, t) <> 0,
462
463
      put log "!!! Overlapping period of using realization and scenarios"/;
      put log "!!! Check forecast lengths, `gn_scenarios` and `gn_forecasts`"/;
464
      execError = execError + 1;
465
466
  );
);
467

Niina Helistö's avatar
Niina Helistö committed
468
// Forecast displacement between central and forecasted intervals at the end of forecast horizon
469
Option clear = df_central; // This can be reset.
470
471
df_central(ft(f,t))${   ord(t) > tSolveFirst + currentForecastLength - p_stepLength(mSolve, f, t) / mSettings(mSolve, 'stepLengthInHours')
                        and ord(t) <= tSolveFirst + currentForecastLength
472
473
                        and not mf_realization(mSolve, f)
                        }
474
    = sum(mf_central(mSolve, f_), ord(f_) - ord(f));
475

Niina Helistö's avatar
Niina Helistö committed
476
// Forecast index displacement between realized and forecasted intervals, required for locking reserves ahead of (dispatch) time.
477
478
479
480
Option clear = df_reserves;
df_reserves(node, restype, ft(f, t))
    ${  p_nReserves(node, restype, 'update_frequency')
        and p_nReserves(node, restype, 'gate_closure')
481
        and ord(t) <= tSolveFirst + p_nReserves(node, restype, 'gate_closure') + p_nReserves(node, restype, 'update_frequency') - mod(tSolveFirst - 1 + p_nReserves(node, restype, 'gate_closure') + p_nReserves(node, restype, 'update_frequency') - p_nReserves(node, restype, 'update_offset'), p_nReserves(node, restype, 'update_frequency'))
482
483
484
        }
    = sum(f_${ mf_realization(mSolve, f_) }, ord(f_) - ord(f)) + Eps; // The Eps ensures that checks to see if df_reserves exists return positive even if the displacement is zero.

485
// Set of ft-steps where the reserves are locked due to previous commitment
486
487
488
489
490
491
Option clear = ft_reservesFixed;
ft_reservesFixed(node, restype, f_solve(f), t_active(t))
    ${  mf_realization(mSolve, f)
        and not tSolveFirst = mSettings(mSolve, 't_start') // No reserves are locked on the first solve!
        and p_nReserves(node, restype, 'update_frequency')
        and p_nReserves(node, restype, 'gate_closure')
Erkka Rinne's avatar
Erkka Rinne committed
492
493
494
495
496
497
498
499
500
        and ord(t) <= tSolveFirst + p_nReserves(node, restype, 'gate_closure')
                                  + p_nReserves(node, restype, 'update_frequency')
                                  - mod(tSolveFirst - 1
                                          + p_nReserves(node, restype, 'gate_closure')
                                          - mSettings(mSolve, 't_jump')
                                          + p_nReserves(node, restype, 'update_frequency')
                                          - p_nReserves(node, restype, 'update_offset'),
                                        p_nReserves(node, restype, 'update_frequency'))
                                  - mSettings(mSolve, 't_jump')
501
        and not [   restypeReleasedForRealization(restype) // Free desired reserves for the to-be-realized time steps
502
503
                    and ft_realized(f, t)
                    ]
504
505
        }
    = yes;
506

507
508
509
// Form a temporary clone of t_current
option clear = tt;
tt(t_current) = yes;
510
511
// Group each full time step under each active time step for time series aggregation.
option clear = tt_aggregate;
512
tt_aggregate(t_current(t+dt_active(t)), tt(t))
513
514
    = yes;

515
* =============================================================================
516
* --- Defining unit aggregations and ramps ------------------------------------
517
* =============================================================================
518

519
// Units active on each ft
520
Option clear = uft;
521
522
523
uft(unit, ft(f, t))${   (   [
                                ord(t) <= tSolveFirst + p_unit(unit, 'lastStepNotAggregated')
                                and (unit_aggregated(unit) or unit_noAggregate(unit)) // Aggregated and non-aggregate units
524
                            ]
525
526
527
528
                            or
                            [
                                ord(t) > tSolveFirst + p_unit(unit, 'lastStepNotAggregated')
                                and (unit_aggregator(unit) or unit_noAggregate(unit)) // Aggregator and non-aggregate units
529
                            ]
530
531
532
                        )
                        and not sameas(unit, 'empty')
                     }
533
// only units with capacities or investment option
534
    = yes;
535

536
537
538
539
540
541
542
543
544
545
546
547
// First ft:s for each aggregator unit
Option clear = uft_aggregator_first;
loop(unit${unit_aggregator(unit)},
    tmp = card(t);
    loop(uft(unit, f, t),
        if(ord(t) < tmp,
            tmp = ord(t)
        );
    );
    uft_aggregator_first(uft(unit, f, t))${ord(t) = tmp} = yes;
);

548
// Active units in nodes on each ft
549
Option clear = nuft;
550
551
552
nuft(nu(node, unit), ft(f, t))${    uft(unit, f, t) }
    = yes
;
553
// Active (grid, node, unit) on each ft
554
Option clear = gnuft;
555
556
557
gnuft(gn(grid, node), uft(unit, f, t))${    nuft(node, unit, f, t)  }
    = yes
;
558
// Active (grid, node, unit, slack, up_down) on each ft step with ramp restrictions
559
560
561
562
563
Option clear = gnuft_rampCost;
gnuft_rampCost(gnu(grid, node, unit), slack, ft(f, t))${ gnuft(grid, node, unit, f, t)
                                                         and p_gnuBoundaryProperties(grid, node, unit, slack, 'rampCost')
                                                         }
    = yes;
564
// Active (grid, node, unit) on each ft step with ramp restrictions
565
Option clear = gnuft_ramp;
566
567
gnuft_ramp(gnuft(grid, node, unit, f, t))${ p_gnu(grid, node, unit, 'maxRampUp')
                                            OR p_gnu(grid, node, unit, 'maxRampDown')
568
                                            OR sum(slack, gnuft_rampCost(grid, node, unit, slack, f, t))
569
570
571
572
                                            }
    = yes;

* --- Defining unit efficiency groups etc. ------------------------------------
573

574
// Initializing
575
576
Option clear = suft;
Option clear = sufts;
577

578
579
580
// Loop over the defined efficiency groups for units
loop(effLevelGroupUnit(effLevel, effGroup, unit)${ mSettingsEff(mSolve, effLevel) },
    // Determine the used effGroup for each uft
581
582
    suft(effGroup, uft(unit, f, t))${   ord(t) >= tSolveFirst + mSettingsEff(mSolve, effLevel - 1) + 1
                                        and ord(t) <= tSolveFirst + mSettingsEff(mSolve, effLevel) }
583
        = yes;
584
); // END loop(effLevelGroupUnit)
585
586
587

// Determine the efficiency selectors for suft
sufts(suft(effGroup, unit, f, t), effSelector)${    effGroupSelector(effGroup, effSelector) }
588
589
    = yes
;
590

591
// Units with online variables on each ft
592
Option clear = uft_online;
593
594
Option clear = uft_onlineLP;
Option clear = uft_onlineMIP;
595
596
Option clear = uft_onlineLP_withPrevious;
Option clear = uft_onlineMIP_withPrevious;
597

598
// Determine the intervals when units need to have online variables.
599
600
loop(effOnline(effSelector),
    uft_online(uft(unit, f, t))${ suft(effOnline, unit, f, t) }
601
        = yes;
602
603
604
605
); // END loop(effOnline)
uft_onlineLP(uft(unit, f, t))${ suft('directOnLP', unit, f, t) }
    = yes;
uft_onlineMIP(uft_online(unit, f, t)) = uft_online(unit, f, t) - uft_onlineLP(unit, f, t);
606

607
608
609
610
// Units with start-up and shutdown trajectories
Option clear = uft_startupTrajectory;
Option clear = uft_shutdownTrajectory;

611
// Determine the intervals when units need to follow start-up and shutdown trajectories.
612
613
614
loop(runUpCounter(unit, 'c000'), // Loop over units with meaningful run-ups
    uft_startupTrajectory(uft_online(unit, f, t))
        ${ ord(t) <= tSolveFirst + mSettings(mSolve, 't_trajectoryHorizon') }
615
        = yes;
616
617
618
619
); // END loop(runUpCounter)
loop(shutdownCounter(unit, 'c000'), // Loop over units with meaningful shutdowns
    uft_shutdownTrajectory(uft_online(unit, f, t))
        ${ ord(t) <= tSolveFirst + mSettings(mSolve, 't_trajectoryHorizon') }
620
        = yes;
621
); // END loop(shutdownCounter)
622

623
* -----------------------------------------------------------------------------
624
* --- Displacements for start-up and shutdown decisions -----------------------
625
626
* -----------------------------------------------------------------------------

627
628
* --- Start-up decisions ------------------------------------------------------

Niina Helistö's avatar
Niina Helistö committed
629
630
// Calculate dt_toStartup: in case the unit becomes online in the current time interval,
// displacement needed to reach the time interval where the unit was started up
631
Option clear = dt_toStartup;
632
loop(runUpCounter(unit, 'c000'), // Loop over units with meaningful run-ups
633
    dt_toStartup(unit, t_active(t))$(ord(t) <= tSolveFirst + mSettings(mSolve, 't_trajectoryHorizon'))
634
        = - p_u_runUpTimeIntervalsCeil(unit) + dt_active(t - p_u_runUpTimeIntervalsCeil(unit));
635
); // END loop(runUpCounter)
636

637
* --- Shutdown decisions ------------------------------------------------------
638
639

// Calculate dt_toShutdown: in case the generation of the unit becomes zero in
Niina Helistö's avatar
Niina Helistö committed
640
// the current time interval, displacement needed to reach the time interval where
641
642
// the shutdown decisions was made
Option clear = dt_toShutdown;
643
loop(shutdownCounter(unit, 'c000'), // Loop over units with meaningful shutdowns
644
    dt_toShutdown(unit, t_active(t))$(ord(t) <= tSolveFirst + mSettings(mSolve, 't_trajectoryHorizon'))
645
        = - p_u_shutdownTimeIntervalsCeil(unit) + dt_active(t - p_u_shutdownTimeIntervalsCeil(unit))
646
); // END loop(runUpCounter)
647

648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
* --- Historical Unit LP and MIP information ----------------------------------

uft_onlineLP_withPrevious(uft_onlineLP(unit, f, t)) = yes;
uft_onlineMIP_withPrevious(uft_onlineMIP(unit, f, t)) = yes;

// Units with online variables on each active ft starting at t0
loop(mft_start(mSolve, f, t_), // Check the uft_online used on the first time step of the current solve
    uft_onlineLP_withPrevious(unit, f, t_active(t)) // Include all historical t_active
        ${  uft_onlineLP(unit, f, t_+1) // Displace by one to reach the first current time step
            and ord(t) <= tSolveFirst // Include all historical t_active
            }
         = yes;
    uft_onlineMIP_withPrevious(unit, f, t_active(t)) // Include all historical t_active
        ${  uft_onlineMIP(unit, f, t_+1) // Displace by one to reach the first current time step
            and ord(t) <= tSolveFirst // Include all historical t_active
            }
        = yes;
); // END loop(mft_start)

667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
// Historical Unit LP and MIP information for models with multiple samples
// If this is the very first solve
if(tSolveFirst = mSettings(mSolve, 't_start'),
    // Sample start intervals
    loop(mst_start(mSolve, s, t),
        uft_onlineLP_withPrevious(unit, f, t+dt(t)) // Displace by one to reach the time step just before the sample
            ${  uft_onlineLP(unit, f, t)
                }
             = yes;
        uft_onlineMIP_withPrevious(unit, f, t+dt(t)) // Displace by one to reach the time step just before the sample
            ${  uft_onlineMIP(unit, f, t)
                }
            = yes;
    ); // END loop(mst_start)
); // END if(tSolveFirst)