3d_setVariableLimits.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
* --- Variable limits ---------------------------------------------------------
20
21
22
* =============================================================================

* --- Node State Boundaries ---------------------------------------------------
23

24
25
// When using constant values and to supplement time series with constant values (time series will override when data available)
// Upper bound
26
v_state.up(gn_state(grid, node), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'useConstant')
27
28
                                                and not df_central(f,t)
                                                }
29
30
31
32
    = p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'multiplier')
;
// Lower bound
33
v_state.lo(gn_state(grid, node), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
34
35
                                                and not df_central(f,t)
                                                }
36
37
38
39
    = p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier')
;
// Fixed value
40
v_state.fx(gn_state(grid, node), sft(s, f, t))${    p_gn(grid, node, 'boundAll')
41
                                                and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useConstant')
42
                                                and not df_central(f,t)
43
44
45
46
                                                }
    = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier')
;
47
// BoundEnd to a constant value
48
v_state.fx(gn_state(grid, node), sft(s, f,t))${   mft_lastSteps(mSolve, f, t)
49
50
51
52
53
54
                                              and p_gn(grid, node, 'boundEnd')
                                              and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useConstant')
                                          }
    = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

55
// When using time series
56
// Upper Bound
57
v_state.up(gn_state(grid, node), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'useTimeSeries')
58
59
                                                and not df_central(f,t)
                                                }
60
    = ts_node_(grid, node,   'upwardLimit', s, f, t)
61
        * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'multiplier')
62
63
;
// Lower bound
64
v_state.lo(gn_state(grid, node), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useTimeSeries')
65
66
                                                and not df_central(f,t)
                                                }
67
    = ts_node_(grid, node, 'downwardLimit', s, f, t)
68
69
70
        * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier')
;
// Fixed value
71
v_state.fx(gn_state(grid, node), sft(s, f, t))${    p_gn(grid, node, 'boundAll')
72
73
74
                                                and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries')
                                                and not df_central(f,t)
                                                    }
75
    = ts_node_(grid, node, 'reference', s, f, t)
76
77
        * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier')
;
78
// BoundEnd to a timeseries value
79
v_state.fx(gn_state(grid, node), sft(s, f,t))${   mft_lastSteps(mSolve, f, t)
80
81
82
                                              and p_gn(grid, node, 'boundEnd')
                                              and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries')
                                          }
83
    = ts_node_(grid, node, 'reference', s, f, t)
84
85
86
        * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

// BoundStartToEnd: bound the last interval in the horizon to the value just before the horizon
87
v_state.fx(gn_state(grid, node), sft(s, f, t))${   mft_lastSteps(mSolve, f, t)
88
89
90
91
92
                                              and p_gn(grid, node, 'boundStartToEnd')
                                          }
    = sum(mf_realization(mSolve, f_),
        + r_state(grid, node, f_, tSolve)
      ); // END sum(mf_realization)
93

94
loop(mst_start(mSolve, s, t)$(tSolveFirst = mSettings(mSolve, 't_start')),
95
96
    // Bound also the intervals just before the start of each sample - currently just 'upwardLimit'&'useConstant' and 'downwardLimit'&'useConstant'
    // Upper bound
97
    v_state.up(gn_state(grid, node), s, f_solve, t+dt(t))${    p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')
98
99
100
101
                                                            and not df_central(f_solve,t)
                                                            }
        = p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'constant')
            * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'multiplier');
Niina Helistö's avatar
Niina Helistö committed
102

103
    // Lower bound
104
    v_state.lo(gn_state(grid, node), s, f_solve, t+dt(t))${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
105
106
107
108
109
                                                            and not df_central(f_solve,t)
                                                            }
        = p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
            * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier');
); // END loop(mst_start)
Niina Helistö's avatar
Niina Helistö committed
110

111
112
// Spilling of energy from the nodes
// Max. & min. spilling, use constant value as base and overwrite with time series if desired
113
v_spill.lo(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'constant')   }
114
115
116
    = p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'multiplier')
;
117
v_spill.lo(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'useTimeSeries') }
118
    = ts_node_(grid, node_spill, 'minSpill', s, f, t)
119
120
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'multiplier')
;
121
v_spill.up(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'constant') }
122
123
124
    = p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'multiplier')
;
125
v_spill.up(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'useTimeSeries')    }
126
    = ts_node_(grid, node_spill, 'maxSpill', s, f, t)
127
128
129
130
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'multiplier')
;

* --- Unit Related Variable Boundaries ----------------------------------------
131

132
// Constant max. energy generation if investments disabled
133
134
135
136
v_gen.up(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and not unit_flow(unit)
                                          and not (unit_investLP(unit) or unit_investMIP(unit))
                                    }
137
138
139
140
    = p_gnu(grid, node, unit, 'maxGen')
        * p_unit(unit, 'availability')
;
// Time series capacity factor based max. energy generation if investments disabled
141
142
v_gen.up(gnu_output(grid, node, unit_flow), sft(s, f, t))${gnuft(grid, node, unit_flow, f, t)
                                                           and not (unit_investLP(unit_flow) or unit_investMIP(unit_flow)) }
143
144
145
    = sum(flow${    flowUnit(flow, unit_flow)
                    and nu(node, unit_flow)
                    },
146
        + ts_cf_(flow, node, s, f, t)
147
148
149
150
151
            * p_gnu(grid, node, unit_flow, 'maxGen')
            * p_unit(unit_flow, 'availability')
      ) // END sum(flow)
;
// Maximum generation to zero to units without generation
152
153
v_gen.up(grid, node, unit, sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and not gnu_output(grid, node, unit)}
154
155
156
    = 0
;
// Min. generation to zero for units without consumption
157
158
v_gen.lo(grid, node, unit, sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and not gnu_input(grid, node, unit) }
159
160
161
    = 0
;
// Constant max. consumption capacity if investments disabled
162
163
v_gen.lo(gnu_input(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and not (unit_investLP(unit) or unit_investMIP(unit))}
164
165
166
167
    = - p_gnu(grid, node, unit, 'maxCons')
        * p_unit(unit, 'availability')
;
// Time series capacity factor based max. consumption if investments disabled
168
169
v_gen.lo(gnu_input(grid, node, unit_flow), sft(s, f, t))${gnuft(grid, node, unit_flow, f, t)
                                          and not (unit_investLP(unit_flow) or unit_investMIP(unit_flow))}
170
171
172
    = - sum(flow${  flowUnit(flow, unit_flow)
                    and nu(node, unit_flow)
                    },
173
          + ts_cf_(flow, node, s, f, t)
174
175
176
177
            * p_gnu(grid, node, unit_flow, 'maxCons')
            * p_unit(unit_flow, 'availability')
      ) // END sum(flow)
;
178
// In the case of negative generation (currently only used for cooling equipment)
179
180
v_gen.lo(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and p_gnu(grid, node, unit, 'maxGen') < 0   }
181
182
    = p_gnu(grid, node, unit, 'maxGen')
;
183
184
v_gen.up(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
                                          and p_gnu(grid, node, unit, 'maxGen') < 0}
185
186
    = 0
;
187
188
189
// Ramping capability of units not part of investment set
// NOTE: Apply the corresponding equations only to units with investment possibility,
// online variable, or reserve provision
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
v_genRamp.up(gnu(grid, node, unit), sft(s, f, t))${ ord(t) > msStart(mSolve, s) + 1
                                                    and gnuft_ramp(grid, node, unit, f, t)
                                                    and p_gnu(grid, node, unit, 'maxRampUp')
                                                    and not uft_online(unit, f, t)
                                                    and not unit_investLP(unit)
                                                    and not unit_investMIP(unit)
                                                    and not uft_startupTrajectory(unit, f, t) // Trajectories require occasional combinations with 'rampSpeedToMinLoad'
                                                    }
 = ( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') )
        * p_gnu(grid, node, unit, 'maxRampUp')
        * 60;  // Unit conversion from [p.u./min] to [p.u./h]
v_genRamp.lo(gnu(grid, node, unit), sft(s, f, t))${ ord(t) > msStart(mSolve, s) + 1
                                                    and gnuft_ramp(grid, node, unit, f, t)
                                                    and p_gnu(grid, node, unit, 'maxRampDown')
                                                    and not uft_online(unit, f, t)
                                                    and not unit_investLP(unit)
                                                    and not unit_investMIP(unit)
                                                    and not uft_shutdownTrajectory(unit, f, t) // Trajectories require occasional combinations with 'rampSpeedFromMinLoad'
                                                    }
 = -( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons'))
        * p_gnu(grid, node, unit, 'maxRampDown')
        * 60;  // Unit conversion from [p.u./min] to [p.u./h]
212

213
214
// v_online cannot exceed unit count if investments disabled
// LP variant
215
v_online_LP.up(unit, sft(s, f, t))${uft_onlineLP(unit, f, t) and not (unit_investLP(unit) or unit_investMIP(unit))}
216
    = p_unit(unit, 'unitCount')
217
     * [1${not active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
218
       + (1 - ts_unit_(unit, 'unavailability', f, t))${active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
219
      ]
220
221
;
// MIP variant
222
v_online_MIP.up(unit, sft(s, f, t))${uft_onlineMIP(unit, f, t) and not (unit_investLP(unit) or unit_investMIP(unit))}
223
    = p_unit(unit, 'unitCount')
224
     * [1${not active(mSolve, 'checkUnavailability')}
225
       + (1 - ts_unit_(unit, 'unavailability', f, t))${active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
226
      ]
227
;
228

229
230
$ontext
// NOTE! These are unnecessary?
231
// Free the upper bound of start-up and shutdown variables (if previously bounded)
232
233
234
235
236
237
v_startup_LP.up(unitStarttype(unit, starttype), sft(s, f, t))
    ${ uft_onlineLP(unit, f, t) }
    = inf;
v_startup_MIP.up(unitStarttype(unit, starttype), sft(s, f, t))
    ${ uft_onlineMIP(unit, f, t) }
    = inf;
238
v_shutdown.up(unit, sft(s, f, t))$uft(unit, f, t) = inf;
239
$offtext
240

241
// v_startup cannot exceed unitCount
242
243
244
245
246
247
248
249
250
251
252
253
v_startup_LP.up(unitStarttype(unit, starttype), sft(s, f, t))
    ${  uft_onlineLP(unit, f, t)
        and not unit_investLP(unit)
        and not unit_investMIP(unit)
        }
    = p_unit(unit, 'unitCount');
v_startup_MIP.up(unitStarttype(unit, starttype), sft(s, f, t))
    ${  uft_onlineMIP(unit, f, t)
        and not unit_investLP(unit)
        and not unit_investMIP(unit)
        }
    = p_unit(unit, 'unitCount');
254
255

// v_shutdown cannot exceed unitCount
256
257
258
259
260
261
262
263
264
265
266
v_shutdown_LP.up(unit, sft(s, f, t))
    ${  uft_onlineLP(unit, f, t)
        and not unit_investLP(unit)
        and not unit_investMIP(unit)}
    = p_unit(unit, 'unitCount');
// v_shutdown cannot exceed unitCount
v_shutdown_MIP.up(unit, sft(s, f, t))
    ${  uft_onlineMIP(unit, f, t)
        and not unit_investLP(unit)
        and not unit_investMIP(unit)}
    = p_unit(unit, 'unitCount');
267

268
269
270
271
// !!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// The following limits are extremely slow, and shouldn't strictly be required.
// Commenting them out for now at least.
$ontext
272
// Cannot start a unit if the time when the unit would become online is outside
273
// the horizon when the unit has an online variable
274
v_startup.up(unitStarttype(unit, starttype), sft(s, f, t))${    uft_online(unit, f, t)
275
                                                            and p_u_runUpTimeIntervals(unit)
276
277
                                                            and not sum(t_active(t_)${ord(t) = ord(t_) + dt_toStartup(unit,t_)}, uft_online(unit, f, t_))
                                                            }
278
    = 0;
279
280
// Cannot shut down a unit if the time when the generation of the unit would become
// zero is outside the horizon when the unit has an online variable
281
282
283
284
v_shutdown.up(unit, sft(s, f, t))${uft_online(unit, f, t)
                                   and p_u_shutdownTimeIntervals(unit)
                                   and not sum(t_active(t_)${ord(t) = ord(t_) + dt_toShutdown(unit,t_)}, uft_online(unit, f, t_))
                                  }
285
    = 0;
286
$offtext
287
288
289
290
291

//These might speed up, but they should be applied only to the new part of the horizon (should be explored)
*v_startup.l(unitStarttype(unit, starttype), f, t)${uft_online(unit, f, t) and  not unit_investLP(unit) } = 0;
*v_shutdown.l(unit, f, t)${sum(starttype, unitStarttype(unit, starttype)) and uft_online(unit, f, t) and  not unit_investLP(unit) } = 0;

292
// Fuel use limitations
293
294
v_fuelUse.up(fuel, unit, sft(s, f, t))${uft(unit, f, t)
                                        and p_uFuel(unit, 'main', fuel, 'maxFuelCons')}
295
296
    = p_uFuel(unit, 'main', fuel, 'maxFuelCons')
;
297

298
* --- Energy Transfer Boundaries ----------------------------------------------
299

300
301
// Restrictions on transferring energy between nodes without investments
// Total transfer variable restricted from both above and below (free variable)
302
v_transfer.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
303
304
    = p_gnn(grid, node, node_, 'transferCap')
;
305
v_transfer.lo(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
306
307
308
    = -p_gnn(grid, node_, node, 'transferCap')
;
// Directional transfer variables only restricted from above (positive variables)
309
v_transferRightward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${ not p_gnn(grid, node, node_, 'transferCapInvLimit') }
310
311
    = p_gnn(grid, node, node_, 'transferCap')
;
312
v_transferLeftward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
313
314
315
316
317
    = p_gnn(grid, node_, node, 'transferCap')
;

* --- Reserve Provision Boundaries --------------------------------------------

318
// Loop over the forecasts to minimize confusion regarding the df_reserves forecast displacement
319
loop((restypeDirectionGridNode(restype, up_down, grid, node), sft(s, f, t))${ ord(t) <= tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length') },
320
321
    // Reserve provision limits without investments
    // Reserve provision limits based on resXX_range (or possibly available generation in case of unit_flow)
322
323
    v_reserve.up(gnuRescapable(restype, up_down, grid, node, unit), s, f+df_reserves(grid, node, restype, f, t), t)
        ${  gnuft(grid, node, unit, f, t) // gnuft is not displaced by df_reserves, as the unit exists on normal ft.
324
            and not (unit_investLP(unit) or unit_investMIP(unit))
325
            and not sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
326
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
327
                        )
328
            }
329
        = min ( p_gnuReserves(grid, node, unit, restype, up_down) * [ p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') ],  // Generator + consuming unit res_range limit
330
                v_gen.up(grid, node, unit, s, f, t) - v_gen.lo(grid, node, unit, s, f, t) // Generator + consuming unit available unit_elec. output delta
331
332
                )${not gnuOfflineRescapable(restype, grid, node, unit)} // END min
            + p_gnuReserves(grid, node, unit, restype, up_down)${gnuOfflineRescapable(restype, grid, node, unit)}
333
              * [ p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') ]
334
;
335

336
    // Reserve transfer upper bounds based on input p_nnReserves data, if investments are disabled
337
    v_resTransferRightward.up(restypeDirectionGridNodeNode(restype, up_down, grid, node, node_), s, f+df_reserves(grid, node, restype, f, t), t)
338
339
        ${  not p_gnn(grid, node, node_, 'transferCapInvLimit')
            and gn2n_directional(grid, node, node_)
340
            and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
341
                            ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
342
343
                            )  // This set contains the combination of reserve types and time intervals that should be fixed
                        or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
344
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
345
                           ) // Commit reserve transfer as long as either end commits.
346
                        ]
347
            }
348
        =  p_gnn(grid, node, node_, 'transferCap')
349
            * p_gnnReserves(grid, node, node_, restype, up_down);
350

351
    v_resTransferLeftward.up(restypeDirectionGridNodeNode(restype, up_down, grid, node, node_), s, f+df_reserves(grid, node, restype, f, t), t)
352
353
        ${  not p_gnn(grid, node, node_, 'transferCapInvLimit')
            and gn2n_directional(grid, node, node_)
354
            and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
355
                            ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
356
357
                            )  // This set contains the combination of reserve types and time intervals that should be fixed
                        or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
358
                               ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
359
                               ) // Commit reserve transfer as long as either end commits.
360
                        ]
361
            }
362
        = p_gnn(grid, node, node_, 'transferCap')
363
            * p_gnnReserves(grid, node, node_, restype, up_down);
364

365
    // Fix non-flow unit reserves at the gate closure of reserves
366
    v_reserve.fx(gnuRescapable(restype, up_down, grid, node, unit), s, f+df_reserves(grid, node, restype, f, t), t)
367
        $ { sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
368
                ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
369
                )  // This set contains the combination of reserve types and time intervals that should be fixed based on previous solves
370
371
            and not unit_flow(unit) // NOTE! Units using flows can change their reserve (they might not have as much available in real time as they had bid)
            }
372
      = r_reserve(restype, up_down, grid, node, unit, f+df_reserves(grid, node, restype, f, t), t);
373

374
    // Fix transfer of reserves at the gate closure of reserves, LOWER BOUND ONLY!
375
    v_resTransferRightward.fx(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
376
        $ { gn2n_directional(grid, node, node_)
377
            and [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
378
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
379
380
                        )  // This set contains the combination of reserve types and time intervals that should be fixed
                    or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
381
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
382
                           ) // Commit reserve transfer as long as either end commits.
383
                    ]
384
          }
385
      = r_resTransferRightward(restype, up_down, grid, node, node_, f+df_reserves(grid, node, restype, f, t), t);
386

387
    v_resTransferLeftward.fx(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
388
        $ { gn2n_directional(grid, node, node_)
389
            and [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
390
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
391
392
                        )  // This set contains the combination of reserve types and time intervals that should be fixed
                    or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
393
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
394
                           ) // Commit reserve transfer as long as either end commits.
395
                    ]
396
          }
397
      = r_resTransferLeftward(restype, up_down, grid, node, node_, f+df_reserves(grid, node, restype, f, t), t);
398

399
    // Fix slack variable for reserves that is used before the reserves need to be locked (vq_resMissing is used after this)
400
    vq_resDemand.fx(restype, up_down, group, s, f+df_reserves(grid, node, restype, f, t), t)
401
402
        $ { ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t) }  // This set contains the combination of reserve types and time intervals that should be fixed
      = r_qResDemand(restype, up_down, group, f+df_reservesGroup(group, restype, f, t), t);
403

404
); // END loop(restypeDirectionGridNode, ft)
405

406
407
* --- Investment Variable Boundaries ------------------------------------------

408
// Unit Investments
409
410
// LP variant
v_invest_LP.up(unit, t_invest)${    unit_investLP(unit) }
411
    = p_unit(unit, 'maxUnitCount')
412
413
;
v_invest_LP.lo(unit, t_invest)${    unit_investLP(unit) }
414
    = p_unit(unit, 'minUnitCount')
415
416
417
;
// MIP variant
v_invest_MIP.up(unit, t_invest)${   unit_investMIP(unit)    }
418
    = p_unit(unit, 'maxUnitCount')
419
420
;
v_invest_MIP.lo(unit, t_invest)${   unit_investMIP(unit)    }
421
    = p_unit(unit, 'minUnitCount')
422
423
424
425
;

// Transfer Capacity Investments
// LP investments
Niina Helistö's avatar
Niina Helistö committed
426
v_investTransfer_LP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investLP(grid, from_node, to_node) }
427
428
429
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
;
// MIP investments
Niina Helistö's avatar
Niina Helistö committed
430
v_investTransfer_MIP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investMIP(grid, from_node, to_node) }
431
432
433
434
435
436
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
        / p_gnn(grid, from_node, to_node, 'unitSize')
;


* =============================================================================
437
* --- Bounds for the first (and last) interval --------------------------------
438
439
* =============================================================================

440
// Loop over the start intervals
441
loop((mft_start(mSolve, f, t), ms_initial(mSolve, s)),
442

Topi Rasku's avatar
Topi Rasku committed
443
444
445
    // If this is the very first solve, set boundStart
    if(tSolveFirst = mSettings(mSolve, 't_start'),

446
        // Upper bound
447
        v_state.up(gn_state(grid, node), s, f, t)${    p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')
448
449
450
451
452
453
                                                    and not df_central(f,t)
                                                    }
            = p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'multiplier');

        // Lower bound
454
        v_state.lo(gn_state(grid, node), s, f, t)${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
455
456
457
458
459
                                                    and not df_central(f,t)
                                                    }
            = p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier');

Topi Rasku's avatar
Topi Rasku committed
460
        // First solve, state variables (only if boundStart flag is true)
461
        v_state.fx(gn_state(grid, node), s, f, t)${ p_gn(grid, node, 'boundStart') }
Topi Rasku's avatar
Topi Rasku committed
462
463
464
465
            = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

        // Time series form boundary
466
        v_state.fx(gn_state(grid, node), s, f, t)${    p_gn(grid, node, 'boundStart')
467
                                                    and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries') // !!! NOTE !!! The check fails if value is zero
Topi Rasku's avatar
Topi Rasku committed
468
                                                    }
469
            = ts_node(grid, node, 'reference', f, t) // NOTE!!! ts_node_ doesn't contain initial values so using raw data instead.
Topi Rasku's avatar
Topi Rasku committed
470
471
                * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

472
        // Initial online status for units
473
        v_online_MIP.fx(unit, s, f, t)${p_unit(unit, 'useInitialOnlineStatus') and uft_onlineMIP(unit, f, t+1)}   //sets online status for one time step before the first solve
474
475
            = p_unit(unit, 'initialOnlineStatus');

476
        v_online_LP.fx(unit, s, f, t)${p_unit(unit, 'useInitialOnlineStatus') and uft_onlineLP(unit, f, t+1)}
477
478
            = p_unit(unit, 'initialOnlineStatus');

479
        // Initial generation for units
480
        v_gen.fx(grid, node, unit, s, f, t)${p_gnu(grid, node, unit, 'useInitialGeneration')}
481
482
            = p_gnu(grid, node, unit, 'initialGeneration');

483
        // Startup and shutdown variables are not applicable at the first time step
484
485
        v_startup_LP.fx(unitStarttype(unit_online_LP, starttype), s, f, t) = 0;
        v_startup_MIP.fx(unitStarttype(unit_online_MIP, starttype), s, f, t) = 0;
486
487
        v_shutdown_LP.fx(unit_online_LP, s, f, t) = 0;
        v_shutdown_MIP.fx(unit_online_LP, s, f, t) = 0;
488

489
    else // For all other solves, fix the initial state values based on previous results.
Topi Rasku's avatar
Topi Rasku committed
490
491

        // State and online variable initial values for the subsequent solves
492
493
        v_state.fx(gn_state(grid, node), s, f, t + (1 - mInterval(mSolve, 'stepsPerInterval', 'c000')))
            = r_state(grid, node, f, t + (1 - mInterval(mSolve, 'stepsPerInterval', 'c000')));
Topi Rasku's avatar
Topi Rasku committed
494

495
        // Generation initial value (needed at least for ramp constraints)
496
497
        v_gen.fx(gnu(grid, node, unit), s, f, t + (1 - mInterval(mSolve, 'stepsPerInterval', 'c000')))
            = r_gen(grid, node, unit, f, t + (1 - mInterval(mSolve, 'stepsPerInterval', 'c000')));
498

499
    ); // END if(tSolveFirst)
500
) // END loop(mft_start)
501
;
502

503
504
505
506

* =============================================================================
* --- Fix previously realized start-ups, shutdowns, and online states ---------
* =============================================================================
507

508
// Needed for modelling hot and warm start-ups, minimum uptimes and downtimes, and run-up and shutdown phases.
509
if( tSolveFirst <> mSettings(mSolve, 't_start'), // Avoid rewriting the fixes on the first solve handled above
510
511
512
513
514
515
    v_startup_LP.fx(unitStarttype(unit_online_LP(unit), starttype), sft_realizedNoReset(s, f, t_active(t)))
        ${ ord(t) <= tSolveFirst } // Only fix previously realized time steps
        = r_startup(unit, starttype, f, t);

    v_startup_MIP.fx(unitStarttype(unit_online_MIP(unit), starttype), sft_realizedNoReset(s, f, t_active(t)))
        ${ ord(t) <= tSolveFirst } // Only fix previously realized time steps
516
        = r_startup(unit, starttype, f, t);
517

518
519
520
521
522
523
    v_shutdown_LP.fx(unit_online_LP(unit), sft_realizedNoReset(s, f, t_active(t)))
        ${  ord(t) <= tSolveFirst } // Only fix previously realized time steps
        = r_shutdown(unit, f, t);

    v_shutdown_MIP.fx(unit_online_MIP(unit), sft_realizedNoReset(s, f, t_active(t)))
        ${  ord(t) <= tSolveFirst } // Only fix previously realized time steps
524
        = r_shutdown(unit, f, t);
525

526
    v_online_MIP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
527
528
        ${  ord(t) <= tSolveFirst // Only fix previously realized time steps
            and unit_online_MIP(unit) // Check if the unit has a MIP online variable on the first effLevel
529
530
            }
        = round(r_online(unit, f, t));
531

532
    v_online_LP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
533
534
        ${  ord(t) <= tSolveFirst // Only fix previously realized time steps
            and unit_online_LP(unit) // Check if the unit has a LP online variable on the first effLevel
535
536
537
            }
        = r_online(unit, f, t);
); // END if
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560

* =============================================================================
* --- Fix previously realized investment results ------------------------------
* =============================================================================

v_invest_LP.fx(unit_investLP(unit), t_invest(t))${ p_unit(unit, 'start') <= tSolveFirst } // Should this be ord(t) <= tSolveFirst?
    = r_invest(unit, t)
;
v_invest_MIP.fx(unit_investMIP(unit), t_invest(t))${ p_unit(unit, 'start') <= tSolveFirst } // Should this be ord(t) <= tSolveFirst?
    = r_invest(unit, t)
;
v_investTransfer_LP.fx(gn2n_directional(grid, node, node_), t_invest(t))${    not p_gnn(grid, node, node_, 'investMIP')
                                                                              and p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                              and ord(t) <= tSolveFirst
                                                                              }
    = r_investTransfer(grid, node, node_, t)
;
v_investTransfer_MIP.fx(gn2n_directional(grid, node, node_), t_invest(t))${   p_gnn(grid, node, node_, 'investMIP')
                                                                              and p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                              and ord(t) <= tSolveFirst
                                                                              }
    = r_investTransfer(grid, node, node_, t) / p_gnn(grid, node, node_, 'unitSize')
;