1e_inputs.gms 32.2 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
20
21
* =============================================================================
* --- Load Input Data ---------------------------------------------------------
* =============================================================================

22
23
* If input_file excel has been set in the command line arguments, then Gdxxrw will be run to convert the Excel into a GDX file
*   using the sheet defined by input_excel_index command line argument (default: 'INDEX').
24
$if set input_file_excel $call 'gdxxrw Input="%input_dir%/%input_file_excel%" Output="%input_dir%/%input_file_gdx%" Index=%input_excel_index%! %input_excel_checkdate%'
25
$ife %system.errorlevel%>0 $abort gdxxrw failed! Check that your input Excel is valid and that your file path and file name are correct.
26

27
28
29
30
* --input_file_gdx=nameOfInputFile.gdx for input_file_gdx in input_dir
$ifthen exist '%input_dir%/%input_file_gdx%'
    $$gdxin  '%input_dir%/%input_file_gdx%'
* --input_file_gdx=ABSOLUTE/PATH/nameOfInputFile.gdx for input_file_gdx not in input_dir
31
$elseif exist '%input_file_gdx%'
32
    $$gdxin  '%input_file_gdx%'
33
34
$else
    $$goto no_input_gdx
35
$endif
36

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
89
90
91
92
93
94
95
96
97
$loaddcm grid
$loaddc node
$loaddc flow
$loaddc unittype
$loaddc unit
$loaddc unitUnittype
$loaddc unit_fail
$loaddc commodity
$loaddc unitUnitEffLevel
$loaddc effLevelGroupUnit
$loaddc group
$loaddc p_gn
$loaddc p_gnn
$loaddc ts_gnn
$loaddc p_gnu_io
$loaddc p_gnuBoundaryProperties
$loaddc p_unit
$loaddc ts_unit
$loaddc p_unitConstraint
$loaddc p_unitConstraintNode
$loaddc restype
$loaddc restypeDirection
$loaddc restypeReleasedForRealization
$loaddc restype_inertia
$loaddc p_groupReserves
$loaddc p_groupReserves3D
$loaddc p_groupReserves4D
$loaddc p_gnuReserves
$loaddc p_gnnReserves
$loaddc p_gnuRes2Res
$loaddc ts_reserveDemand
$loaddc p_gnBoundaryPropertiesForStates
$loaddc p_uStartupfuel
$loaddc flowUnit
$loaddc emission
$loaddc p_nEmission
$loaddc ts_cf
*$loaddc p_price // Disabled for convenience, see line 278-> ("Determine Fuel Price Representation")
$loaddc ts_priceChange
$loaddc ts_influx
$loaddc ts_node
$loaddc p_s_discountFactor
$loaddc t_invest
$loaddc utAvailabilityLimits
$loaddc p_storageValue
$loaddc ts_storageValue
$loaddc uGroup
$loaddc gnuGroup
$loaddc gn2nGroup
$loaddc gnGroup
$loaddc sGroup
$loaddc p_groupPolicy
$loaddc p_groupPolicyUnit
$loaddc p_groupPolicyEmission
$loaddc gnss_bound
$loaddc uss_bound

$gdxin

$label no_input_gdx

98

99
100
101
* Read changes to inputdata through gdx files (e.g. node2.gdx, unit2.gdx, unit3.gdx) - allows scenarios through Sceleton Titan Excel files.
$include 'inc/1e_scenChanges.gms'

102
* Reads changes or additions to the inputdata through changes.inc file.
103
104
105
$ifthen exist '%input_dir%/changes.inc'
   $$include '%input_dir%/changes.inc'
$endif
106

107

108
109
110
111
112
113
114
115
116
117

$ontext
* --- sets with 'empty' to enable GDX imports in Sceleton Titan - currently removed when forming gnu (below) and uft (periodicLoop.gms) sets
* This list can be removed once this has been tested.
node
unit
gngnu_constrainedOutputRatio
restype
restypeReleasedForRealization
p_gnn
118
p_gnnReserves
119
120
121
122
123
124
125
126
127
128
129
130
131
p_gnuBoundaryProperties
ts_node
ts_reserveDemand
ts_unit
p_storageValue
group
gnuGroup
gnGroup
gn2nGroup
gnss_bound
$offtext


132
133
134
135
136
137
138
* =============================================================================
* --- Initialize Unit Related Sets & Parameters Based on Input Data -----------
* =============================================================================


* --- Generate Unit Related Sets ----------------------------------------------

139
140
p_gnu(grid, node, unit, param_gnu) = sum(input_output, p_gnu_io(grid, node, unit, input_output, param_gnu));

141
// Set of all existing gnu
142
gnu(grid, node, unit)${ not sameas(grid, 'empty')
143
144
145
                        and (   p_gnu(grid, node, unit, 'capacity')
                                or p_gnu(grid, node, unit, 'unitSize')
                                or p_gnu(grid, node, unit, 'conversionCoeff')
146
                                )
147
                      }
148
149
150
    = yes;
// Reduce the grid dimension
nu(node, unit) = sum(grid, gnu(grid, node, unit));
151
//p_gnu(grid, node, unit, 'capacity')$(p_gnu(grid, node, unit, 'capacity') = 0) = inf;
152
153

// Separation of gnu into inputs and outputs
154
155
156
gnu_output(gnu(grid, node, unit))${ p_gnu_io(grid, node, unit, 'output', 'capacity')
                                    or p_gnu_io(grid, node, unit, 'output', 'unitSize')
                                    or p_gnu_io(grid, node, unit, 'output', 'conversionCoeff')
157
158
                                    }
    = yes;
159
160
161
gnu_input(gnu(grid, node, unit))${  p_gnu_io(grid, node, unit, 'input', 'capacity')
                                    or p_gnu_io(grid, node, unit, 'input', 'unitSize')
                                    or p_gnu_io(grid, node, unit, 'input', 'conversionCoeff')
162
163
164
165
166
167
168
169
170
171
172
173
174
175
                                    }
    = yes;

// Units connecting gn-gn pairs
gn2gnu(grid, node_input, grid_, node_output, unit)${    gnu_input(grid, node_input, unit)
                                                        and gnu_output(grid_, node_output, unit)
                                                        }
    = yes;

// Units with minimum load requirements
unit_minload(unit)${    p_unit(unit, 'op00') > 0 // If the first defined operating point is between 0 and 1, then the unit is considered to have a min load limit
                        and p_unit(unit, 'op00') < 1
                        }
    = yes;
176

177
// Units with flows/commodities
178
179
unit_flow(unit)${ sum(flow, flowUnit(flow, unit)) }
    = yes;
180
un_commodity(unit, commodity)$sum(grid, gnu(grid, commodity, unit)) = yes;
181
182
un_commodity_in(unit, commodity)$sum(grid, gnu_input(grid, commodity, unit)) = yes;
un_commodity_out(unit, commodity)$sum(grid, gnu_output(grid, commodity, unit)) = yes;
183
unit_commodity(unit)${ sum(node, un_commodity(unit, node)) }
184
185
    = yes;

186
187
188
189
190
191
192
193
194
195
// Units with investment variables
unit_investLP(unit)${  not p_unit(unit, 'investMIP')
                       and p_unit(unit, 'maxUnitCount')
                        }
    = yes;
unit_investMIP(unit)${  p_unit(unit, 'investMIP')
                        and p_unit(unit, 'maxUnitCount')
                        }
    = yes;

196
197
// Units with special startup properties
// All units can cold start (default start category)
198
unitStarttype(unit, 'cold') = yes;
199
// Units with parameters regarding hot/warm starts
200
unitStarttype(unit, starttypeConstrained)${ p_unit(unit, 'startWarmAfterXhours')
201
202
203
204
                                            or p_unit(unit, 'startCostHot')
                                            or p_unit(unit, 'startFuelConsHot')
                                            or p_unit(unit, 'startCostWarm')
                                            or p_unit(unit, 'startFuelConsWarm')
205
                                            or p_unit(unit, 'startColdAfterXhours')
206
207
208
                                            }
    = yes;

209
210
211
// Units with time series data enabled
unit_timeseries(unit)${ p_unit(unit, 'useTimeseries') }
    = yes;
212
213
214
215
216
217
218
219

* --- Unit Related Parameters -------------------------------------------------

// Assume values for critical unit related parameters, if not provided by input data
// If the unit does not have efficiency set, it is 1
p_unit(unit, 'eff00')${ not p_unit(unit, 'eff00') }
    = 1;

220
// In case number of units has not been defined it is 1 except for units with investments allowed.
221
p_unit(unit, 'unitCount')${ not p_unit(unit, 'unitCount')
222
223
                            and not unit_investMIP(unit)
                            and not unit_investLP(unit)
224
225
226
227
228
                            }
    = 1;

// By default add outputs in order to get the total capacity of the unit
p_unit(unit, 'outputCapacityTotal')${ not p_unit(unit, 'outputCapacityTotal') }
229
    = sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'capacity'));
230
p_unit(unit, 'unitOutputCapacityTotal')
231
    = sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
232
233

// Assume unit sizes based on given maximum capacity parameters and unit counts if able
234
235
236
p_gnu(gnu(grid, node, unit), 'unitSize')
    ${  not p_gnu(grid, node, unit, 'unitSize')
        and p_gnu(grid, node, unit, 'capacity')
237
238
        and p_unit(unit, 'unitCount')
        }
239
    = p_gnu(grid, node, unit, 'capacity') / p_unit(unit, 'unitCount'); // If capacity and unitCount are given, calculate unitSize based on them.
240
241
242

// Determine unit startup parameters based on data
// Hot startup parameters
243
p_uNonoperational(unitStarttype(unit, 'hot'), 'min')
244
    = p_unit(unit, 'minShutdownHours');
245
p_uNonoperational(unitStarttype(unit, 'hot'), 'max')
246
    = p_unit(unit, 'startWarmAfterXhours');
247
p_uStartup(unitStarttype(unit, 'hot'), 'cost')
248
    = p_unit(unit, 'startCostHot')
249
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
250
p_uStartup(unitStarttype(unit, 'hot'), 'consumption')
251
    = p_unit(unit, 'startFuelConsHot')
252
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
253
254

// Warm startup parameters
255
p_uNonoperational(unitStarttype(unit, 'warm'), 'min')
256
    = p_unit(unit, 'startWarmAfterXhours');
257
p_uNonoperational(unitStarttype(unit, 'warm'), 'max')
258
    = p_unit(unit, 'startColdAfterXhours');
259
p_uStartup(unitStarttype(unit, 'warm'), 'cost')
260
    = p_unit(unit, 'startCostWarm')
261
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
262
p_uStartup(unitStarttype(unit, 'warm'), 'consumption')
263
    = p_unit(unit, 'startFuelConsWarm')
264
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
265
266

// Cold startup parameters
267
268
p_uNonoperational(unitStarttype(unit, 'cold'), 'min')
    = p_unit(unit, 'startColdAfterXhours');
269
p_uStartup(unit, 'cold', 'cost')
270
    = p_unit(unit, 'startCostCold')
271
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
272
p_uStartup(unit, 'cold', 'consumption')
273
    = p_unit(unit, 'startFuelConsCold')
274
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
275

276
277
278

//shutdown cost parameters
p_uShutdown(unit, 'cost')
279
    = p_unit(unit, 'shutdownCost')
280
        * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSize'));
281

282
// Determine unit emission costs
283
284
p_unitEmissionCost(unit, node, emission)${nu(node, unit) and p_nEmission(node, emission)}
    = p_nEmission(node, emission)
285
        / 1e3 // NOTE!!! Conversion to t/MWh from kg/MWh in data
286
287
        * sum(gnGroup(grid, node, group)$gnu_input(grid, node, unit),
            + p_groupPolicyEmission(group, 'emissionTax', emission)
288
          )
289
;
290

291

292
// Unit lifetime
293
294
loop(utAvailabilityLimits(unit, t, availabilityLimits),
    p_unit(unit, availabilityLimits) = ord(t)
295
296
); // END loop(ut)

297
* =============================================================================
298
* --- Determine Commodity Price Representation -------------------------------------
299
* =============================================================================
300
// Use time series for commodity prices depending on 'ts_priceChange'
301

302
303
// Determine if commodity prices require a time series representation or not
loop(commodity,
304
305
    // Find the steps with changing fuel prices
    option clear = tt;
306
    tt(t)${ ts_priceChange(commodity, t) } = yes;
307
308
309

    // If only up to a single value
    if(sum(tt, 1) <= 1,
310
311
        p_price(commodity, 'useConstant') = 1; // Use a constant for commodity prices
        p_price(commodity, 'price') = sum(tt, ts_priceChange(commodity, tt)) // Determine the price as the only value in the time series
312
313
    // If multiple values found, use time series
    else
314
        p_price(commodity, 'useTimeSeries') = 1;
Topi Rasku's avatar
Topi Rasku committed
315
        ); // END if(sum(tt))
316
317
); // END loop(fuel)

318

319
320
321
322
323
324
325
326
327
328
329
330
* =============================================================================
* --- Generate Node Related Sets Based on Input Data --------------------------
* =============================================================================

* --- Node Connectivity -------------------------------------------------------

// Node pairs connected via transfer links
gn2n(grid, from_node, to_node)${    p_gnn(grid, from_node, to_node, 'transferCap')
                                    or p_gnn(grid, from_node, to_node, 'transferLoss')
                                    or p_gnn(grid, from_node, to_node, 'transferCapBidirectional')
                                    or p_gnn(grid, to_node, from_node, 'transferCapBidirectional')
                                    or p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
331
                                    or p_gnn(grid, from_node, to_node, 'portion_of_transfer_to_reserve')
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
                                    }
    = yes;

// Node pairs with relatively bound states
gnn_boundState(grid, node, node_)${ p_gnn(grid, node, node_, 'boundStateMaxDiff') }
    = yes;

// Node pairs connected via energy diffusion
gnn_state(grid, node, node_)${  p_gnn(grid, node, node_, 'diffCoeff')
                                or gnn_boundState(grid, node, node_)
                                }
    = yes;

// Generate the set for transfer links where the order of the first node must be smaller than the order of the second node
Option clear = gn2n_directional;
gn2n_directional(gn2n(grid, node, node_))${ ord(node) < ord(node_) }
    = yes;
gn2n_directional(gn2n(grid, node, node_))${ ord(node) > ord(node_)
                                            and not gn2n(grid, node_, node)
                                            }
    = yes;

Niina Helistö's avatar
Niina Helistö committed
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// Set for transfer links with investment possibility
Option clear = gn2n_directional_investLP;
Option clear = gn2n_directional_investMIP;
gn2n_directional_investLP(gn2n_directional(grid, node, node_))${ [p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                     or p_gnn(grid, node_, node, 'transferCapInvLimit')]
                                                                 and [not p_gnn(grid, node, node_, 'investMIP')
                                                                     and not p_gnn(grid, node_, node, 'investMIP')]
                                                                 }
    = yes;
gn2n_directional_investMIP(gn2n_directional(grid, node, node_))${ [p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                     or p_gnn(grid, node_, node, 'transferCapInvLimit')]
                                                                 and [p_gnn(grid, node, node_, 'investMIP')
                                                                     or p_gnn(grid, node_, node, 'investMIP')]
                                                                 }
    = yes;

370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
* --- Node States -------------------------------------------------------------

// States with slack variables
gn_stateSlack(grid, node)${ sum((slack, useConstantOrTimeSeries), p_gnBoundaryPropertiesForStates(grid, node, slack, useConstantOrTimeSeries)) }
    = yes;

// Nodes with states
gn_state(grid, node)${  gn_stateSlack(grid, node)
                        or p_gn(grid, node, 'energyStoredPerUnitOfState')
                        or sum((stateLimits, useConstantOrTimeSeries), p_gnBoundaryPropertiesForStates(grid, node, stateLimits, useConstantOrTimeSeries))
                        or sum(useConstantOrTimeSeries, p_gnBoundaryPropertiesForStates(grid, node, 'reference', useConstantOrTimeSeries))
                        }
    = yes;

// Existing grid-node pairs
385
386
387
gn(grid, node)${    sum(unit, gnu(grid, node, unit))
                    or gn_state(grid, node)
                    or sum((f, t), ts_influx(grid, node, f, t))
388
389
390
391
                    or sum(node_, gn2n(grid, node, node_))
                    or sum(node_, gn2n(grid, node_, node))
                    or sum(node_, gnn_state(grid, node, node_))
                    or sum(node_, gnn_state(grid, node_, node))
392
393
394
395
396
397
398
399
400
                    }
    = yes;

// Nodes with spill permitted
node_spill(node)${ sum((grid, spillLimits, useConstantOrTimeSeries), p_gnBoundaryPropertiesForStates(grid, node, spillLimits, useConstantOrTimeSeries)) }
    = yes;

// Assume values for critical node related parameters, if not provided by input data
// Boundary multiplier
401
402
403
p_gnBoundaryPropertiesForStates(gn(grid, node), param_gnBoundaryTypes, 'multiplier')${  not p_gnBoundaryPropertiesForStates(grid, node, param_gnBoundaryTypes, 'multiplier')
                                                                                        and sum(param_gnBoundaryProperties, p_gnBoundaryPropertiesForStates(grid, node, param_gnBoundaryTypes, param_gnBoundaryProperties))
    } = 1; // If multiplier has not been set, set it to 1 by default
404

405
406
407
* --- Other Node Properties ---------------------------------------------------

// Nodes with flows
408
409
410
flowNode(flow, node)${  sum((f, t), ts_cf(flow, node, f, t))
                        and sum(grid, gn(grid, node))
                        }
411
412
    = yes;

413
414
* --- Timeseries parameters for node-node connections -------------------------

415
// Transfer links with time series enabled for certain parameters
416
417
gn2n_timeseries(grid, node, node_, 'availability')${p_gnn(grid, node, node_, 'useTimeseriesAvailability')}
    = yes;
418
419
gn2n_timeseries(grid, node, node_, 'transferLoss')${p_gnn(grid, node, node_, 'useTimeseriesLoss')}
    = yes;
420

421
422
423
* =============================================================================
* --- Reserves Sets & Parameters ----------------------------------------------
* =============================================================================
424
425
426
// NOTE! Reserves can be disabled through the model settings file.
// The sets are disabled in "3a_periodicInit.gms" accordingly.

427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
* --- Correct values for critical reserve related parameters - Part 1 ---------

// Reserve activation duration assumed to be 1 hour if not provided in data
p_groupReserves(group, restype, 'reserve_activation_duration')
    ${  not p_groupReserves(group, restype, 'reserve_activation_duration')
        and p_groupReserves(group, restype, 'reserve_length')
        }
    = 1;
// Reserve reactivation time assumed to be 1 hour if not provided in data
p_groupReserves(group, restype, 'reserve_reactivation_time')
    ${  not p_groupReserves(group, restype, 'reserve_reactivation_time')
        and p_groupReserves(group, restype, 'reserve_length')
        }
    = 1;

* --- Copy reserve data and create necessary sets -----------------------------

444
// Copy data from p_groupReserves to p_gnReserves
445
loop(gnGroup(grid, node, group)${sum(restype, p_groupReserves(group, restype, 'reserve_length'))},
446
    p_gnReserves(grid, node, restype, param_policy) = p_groupReserves(group, restype, param_policy);
447
448
);

449
// Units with reserve provision capabilities
450
451
gnuRescapable(restypeDirection(restype, up_down), gnu(grid, node, unit))
    $ { p_gnuReserves(grid, node, unit, restype, up_down)
452
453
454
      }
  = yes;

Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
455
// Units with offline reserve provision capabilities
456
457
gnuOfflineRescapable(restype, gnu(grid, node, unit))
    $ { p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability')
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
458
459
460
461
462
      }
  = yes;

// Restypes with offline reserve provision possibility
offlineRes(restype)
463
    $ {sum(gnu(grid, node, unit),  p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
464
465
466
467
468
      }
  = yes;

// Units with offline reserve provision possibility
offlineResUnit(unit)
469
    $ {sum((gn(grid, node), restype),  p_gnuReserves(grid, node, unit, restype, 'offlineReserveCapability'))
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
470
471
472
      }
  = yes;

473
// Node-node connections with reserve transfer capabilities
474
475
restypeDirectionGridNodeNode(restypeDirection(restype, up_down), gn2n(grid, node, node_))
    $ { p_gnnReserves(grid, node, node_, restype, up_down)
476
477
478
      }
  = yes;

479
// Nodes with reserve requirements, units capable of providing reserves, or reserve capable connections
480
481
restypeDirectionGridNode(restypeDirection(restype, up_down), gn(grid, node))
    $ { p_gnReserves(grid, node, restype, up_down)
482
        or p_gnReserves(grid, node, restype, 'useTimeSeries')
483
484
485
        or sum(gnu(grid, node, unit), p_gnuReserves(grid, node, unit, restype, 'portion_of_infeed_to_reserve'))
        or sum(gnu(grid, node, unit), gnuRescapable(restype, up_down, grid, node, unit))
        or sum(gn2n(grid, node, to_node), restypeDirectionGridNodeNode(restype, up_down, grid, node, to_node))
486
487
      }
  = yes;
488

489
490
491
492
493
494
495
496
497
498
// Groups with reserve requirements
restypeDirectionGroup(restypeDirection(restype, up_down), group)
    $ { p_groupReserves(group, restype, 'reserve_length')
      }
  = yes;
restypeDirectionGridNodeGroup(restypeDirection(restype, up_down), gnGroup(grid, node, group))
    $ { p_groupReserves(group, restype, 'reserve_length')
      }
  = yes;

499
* --- Correct values for critical reserve related parameters - Part 2 ---------
500

501
// Reserve reliability assumed to be perfect if not provided in data
502
503
504
p_gnuReserves(gnu(grid, node, unit), restype, 'reserveReliability')
    ${  not p_gnuReserves(grid, node, unit, restype, 'reserveReliability')
        and sum(up_down, gnuRescapable(restype, up_down, grid, node, unit))
505
        }
506
507
    = 1;

508
// Reserve provision overlap decreases the capacity of the overlapping category
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
509
loop(restype,
510
511
512
513
514
515
p_gnuReserves(gnu(grid, node, unit), restype, up_down)
    ${ gnuRescapable(restype, up_down, grid, node, unit) }
    = p_gnuReserves(grid, node, unit, restype, up_down)
        - sum(restype_${ p_gnuRes2Res(grid, node, unit, restype_, up_down, restype) },
            + p_gnuReserves(grid, node, unit, restype_, up_down)
                * p_gnuRes2Res(grid, node, unit, restype_, up_down, restype)
516
        ); // END sum(restype_)
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
517
);
518

519
520
521
* =============================================================================
* --- Data Integrity Checks ---------------------------------------------------
* =============================================================================
522

523
524
525
526
527
528
529
530
531
532
* --- Check that nodes aren't assigned to multiple grids ----------------------

loop(node,
    if(sum(gn(grid, node), 1) > 1,
        put log '!!! Error occurred on node ' node.tl:0 /;
        put log '!!! Abort: Nodes cannot be assigned to multiple grids!' /;
        abort "Nodes cannot be assigned to multiple grids!"
    ); // END if(sum(gn))
); // END loop(node)

533
534
* --- Check the integrity of node connection related data ---------------------

535
Option clear = count;
536
537
538
539
540
541
loop(gn2n(grid, node, node_),
    count = count + 1; // Count the gn2n indeces to make finding the errors easier.
    // Check if the bidirectional transfer parameter exists for this link.
    if(p_gnn(grid, node, node_, 'transferCapBidirectional'),
        // Check for conflicting bidirectional transfer capacities.
        if(p_gnn(grid, node, node_, 'transferCapBidirectional') <> p_gnn(grid, node_, node, 'transferCapBidirectional'),
542
            put log '!!! Error occurred on gn2n link ' node.tl:0 '-' node_.tl:0 /;
543
            put log '!!! Abort: Conflicting transferCapBidirectional parameters!' /;
544
545
546
547
            abort "Conflicting 'transferCapBidirectional' parameters!"
        );
        // Check for conflicting one-directional and bidirectional transfer capacities.
        if(p_gnn(grid, node, node_, 'transferCapBidirectional') < p_gnn(grid, node, node_, 'transferCap') OR (p_gnn(grid, node, node_, 'transferCapBidirectional') < p_gnn(grid, node_, node, 'transferCap')),
548
            put log '!!! Error occurred on gn2n link ' node.tl:0 '-' node_.tl:0 /;
549
            put log '!!! Abort: Parameter transferCapBidirectional must be greater than or equal to defined one-directional transfer capacities!' /;
550
551
552
553
554
            abort "Parameter 'transferCapBidirectional' must be greater than or equal to defined one-directional transfer capacities!"
        );
    );
);

555
556
557
558
559
560
* --- Check the integrity of efficiency approximation related data ------------

Option clear = tmp;
// Find the largest effLevel used in the data
tmp = smax(effLevelGroupUnit(effLevel, effSelector, unit), ord(effLevel));

561
562
563
// Expand the effLevelGroupUnit when possible, abort if impossible
loop(effLevel${ord(effLevel)<=tmp},
    effLevelGroupUnit(effLevel, effSelector, unit)
564
        ${not sum(effLevelGroupUnit(effLevel, effSelector_, unit), 1)}
565
        = effLevelGroupUnit(effLevel - 1, effSelector, unit) // Expand previous (effLevel, effSelector) when applicable
566
    loop(unit${not unit_flow(unit) and not sameas(unit, 'empty')},
567
568
569
570
571
572
573
574
        If(not sum(effLevelGroupUnit(effLevel, effSelector, unit), 1),
            put log '!!! Error on unit ' unit.tl:0 /;
            put log '!!! Abort: Insufficient effLevelGroupUnit definitions!' /;
            abort "Insufficient effLevelGroupUnit definitions!"
        );
    );
);

575
loop( unit,
576
    // Check that 'op' is defined correctly
577
    Option clear = count; // Initialize the previous op to zero
578
    loop( op,
579
        if (p_unit(unit, op) + 1${not p_unit(unit, op)} < count,
580
581
582
            put log '!!! Error occurred on unit ' unit.tl:0 /; // Display unit that causes error
            put log '!!! Abort: param_unit op must be defined as zero or positive and increasing!' /;
            abort "param_unit 'op's must be defined as zero or positive and increasing!";
583
        ); // END if(p_unit)
584
        count = p_unit(unit, op);
585
    ); // END loop(op)
586
587
    // Check that efficiency approximations have sufficient data
    loop( effLevelGroupUnit(effLevel, effSelector, unit),
588
589
590
        loop( op__${p_unit(unit, op__) = smax(op, p_unit(unit, op))}, // Loop over the 'op's to find the last defined data point.
            loop( op_${p_unit(unit, op_) = smin(op${p_unit(unit, op)}, p_unit(unit, op))}, // Loop over the 'op's to find the first nonzero 'op' data point.
                if(effDirectOn(effSelector) AND ord(op__) = ord(op_) AND not p_unit(unit, 'section') AND not p_unit(unit, 'opFirstCross'),
591
592
593
                    put log '!!! Error occurred on unit ' unit.tl:0 /; // Display unit that causes error
                    put log '!!! Abort: directOn requires two efficiency data points with nonzero op or section or opFirstCross!' /;
                    abort "directOn requires two efficiency data points with nonzero 'op' or 'section' or 'opFirstCross'!";
594
595
596
597
                ); // END if(effDirectOn)
            ); // END loop(op_)
        ); // END loop(op__)
    ); // END loop(effLevelGroupUnit)
598
599
);

600
* --- Check startupfuel fraction related data ----------------------------------------
601

602
603
loop( unit${sum(commodity$p_uStartupfuel(unit, commodity, 'fixedFuelFraction'), 1)},
    if(sum(commodity, p_uStartupfuel(unit, commodity, 'fixedFuelFraction')) <> 1,
604
        put log '!!! Error occurred on unit ' unit.tl:0 /;
605
        put log '!!! Abort: The sum of fixedFuelFraction over start-up fuels needs to be one for all units using start-up fuels!' /;
606
        abort "The sum of 'fixedFuelFraction' over start-up fuels needs to be one for all units using start-up fuels!"
607
608
    );
);
609

610
611
loop( unit${sum((constraint, node)$p_unitConstraintNode(unit, constraint, node), 1)},
    if(sum((constraint, node)$p_unitConstraintNode(unit, constraint, node), 1) < 2,
612
        put log '!!! Error occurred on unit ' unit.tl:0 /;
613
614
        put log '!!! Abort: constraint requires at least two inputs or outputs!' /;
        abort "a constraint has to have more tha one input or output!"
615
616
617
    );
);

618
619
* --- Check the shutdown time related data ------------------------------------

620
loop( unitStarttype(unit, starttypeConstrained),
621
622
    if(p_unit(unit, 'minShutdownHours') > p_unit(unit, 'startWarmAfterXhours')
        or p_unit(unit, 'startWarmAfterXhours') > p_unit(unit, 'startColdAfterXhours'),
623
        put log '!!! Error occurred on unit ', unit.tl:0 /;
624
        put log '!!! Abort: Units should have p_unit(unit, minShutdownHours) <= p_unit(unit, startWarmAfterXhours) <= p_unit(unit, startColdAfterXhours)!' /;
625
626
627
        abort "Units should have p_unit(unit, 'minShutdownHours') <= p_unit(unit, 'startWarmAfterXhours') <= p_unit(unit, 'startColdAfterXhours')!"
    );
);
628

629
* --- Check reserve related data ----------------------------------------------
630

631
632
633
634
loop( restypeDirectionGroup(restype, up_down, group),
    // Check that reserve_length is long enough for proper commitment of reserves
    if(p_groupReserves(group, restype, 'reserve_length') < p_groupReserves(group, restype, 'update_frequency') + p_groupReserves(group, restype, 'gate_closure'),
        put log '!!! Error occurred on group ', group.tl:0 /;
635
        put log '!!! Abort: The reserve_length parameter should be longer than update_frequency + gate_closure to fix the reserves properly!' /;
636
637
        abort "The 'reserve_length' parameter should be longer than 'update_frequency' + 'gate_closure' to fix the reserves properly!"
    ); // END if
638
639
640
641
642
643
644
645
646
    // Check that the duration of reserve activation is less than the reserve reactivation time
    if(p_groupReserves(group, restype, 'reserve_reactivation_time') < p_groupReserves(group, restype, 'reserve_activation_duration'),
        put log '!!! Error occurred on group ', group.tl:0 /;
        put log '!!! Abort: The reserve_reactivation_time should be greater than or equal to the reserve_activation_duration!' /;
        abort "The reserve_reactivation_time should be greater than or equal to the reserve_activation_duration!"
    ); // END if
); // END loop(restypeDirectionGroup)

loop( restypeDirectionGridNode(restype, up_down, grid, node),
647
648
649
650
651
652
653
654
655
656
657
658
    // Check for each restype that a node does not belong to multiple groups
    if(sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group), 1) > 1,
        put log '!!! Error occurred on node ', node.tl:0 /;
        put log '!!! Abort: For each reserve type, a node can belong to at maximum one reserve node group!' /;
        abort "For each reserve type, a node can belong to at maximum one reserve node group!"
    ); // END if
    // Check if there are units/interconnections connected to a node that does not belong to any restypeDirectionGroup
    if(sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group), 1) < 1,
        put log '!!! Error occurred on node ', node.tl:0 /;
        put log '!!! Abort: A node with reserve provision/transfer capability has to belong to a reserve node group!' /;
        abort "A node with reserve provision/transfer capability has to belong to a reserve node group!"
    ); // END if
659
); // END loop(restypeDirectionGridNode)
660

661
// Check that reserve overlaps are possible
662
663
loop( (gnu(grid, node, unit), restypeDirection(restype, up_down)),
    if( p_gnuReserves(grid, node, unit, restype, up_down) < 0,
664
        put log '!!! Error occurred on unit ', unit.tl:0 /;
665
666
667
668
        put log '!!! Abort: Overlapping reserve capacities in p_gnuRes2Res can result in excess reserve production!' /;
        abort "Overlapping reserve capacities in p_gnuRes2Res can result in excess reserve production!"
    ); // END if(p_gnuReserves)
); // END loop((gnu,restypeDirection))
669

670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
* --- Check investment related data -------------------------------------------

// Check that units with LP investment possibility have unitSize
loop( unit_investLP(unit),
    if(not sum(gnu(grid, node, unit), abs(p_gnu(grid, node, unit, 'unitSize'))),
        put log '!!! Error occurred on unit ', unit.tl:0 /;
        put log '!!! Abort: Unit is listed as an investment option but it has no unitSize!' /;
        abort "All units with investment possibility should have 'unitSize' in p_gnu!"
    ); // END if
); // END loop(unit_investLP)
// Check that units with MIP investment possibility have unitSize
loop( unit_investMIP(unit),
    if(not sum(gnu(grid, node, unit), abs(p_gnu(grid, node, unit, 'unitSize'))),
        put log '!!! Error occurred on unit ', unit.tl:0 /;
        put log '!!! Abort: Unit is listed as an investment option but it has no unitSize!' /;
        abort "All units with investment possibility should have 'unitSize' in p_gnu!"
    ); // END if
); // END loop(unit_investMIP)

689
690
691
692

* =============================================================================
* --- Default values  ---------------------------------------------------------
* =============================================================================
693
694
695
loop(timeseries$(not sameas(timeseries, 'ts_cf')),
    p_tsMinValue(gn, timeseries) = -Inf;
    p_tsMaxValue(gn, timeseries) = Inf;
696
);
697
698
p_tsMinValue(flowNode, 'ts_cf') = 0;
p_tsMaxValue(flowNode, 'ts_cf') = 1;
699
700
701
702
703
704

* By default all nodes use forecasts for all timeseries
gn_forecasts(gn, timeseries) = yes;
gn_forecasts(flowNode, timeseries) = yes;
gn_forecasts(restype, node, 'ts_reserveDemand') = yes;