3d_setVariableLimits.gms 35.5 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
* =============================================================================
23
* --- Node State Boundaries ---------------------------------------------------
24
* =============================================================================
25

26
27
28
29
30

// state limits for normal (not superposed) nodes
loop(node$(not node_superpos(node)),

    // When using constant values and to supplement time series with constant values (time series will override when data available)
31
    // Upper bound
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
    v_state.up(gn_state(grid, node), sft(s, f, t))${p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'useConstant')
                                                    and not df_central(f,t)
                                                    }
            = p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'multiplier')
        ;
    // Lower bound
    v_state.lo(gn_state(grid, node), sft(s, f, t))${p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
                                                    and not df_central(f,t)
                                                    }
            = p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier')
    ;
    // Fixed value
    v_state.fx(gn_state(grid, node), sft(s, f, t))${p_gn(grid, node, 'boundAll')
                                                    and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useConstant')
                                                    and not df_central(f,t)
                                                    }
    = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier')
    ;
    // BoundEnd to a constant value
    v_state.fx(gn_state(grid, node), sft(s, f,t))${   mft_lastSteps(mSolve, f, t)
                                                      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');
Niina Helistö's avatar
Niina Helistö committed
60

61
62
63
64
65
66
67
68
    // When using time series
    // Upper Bound
    v_state.up(gn_state(grid, node), sft(s, f, t))${p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'useTimeSeries')
                                                    and not df_central(f,t)
                                                    }
            = ts_node_(grid, node,   'upwardLimit', s, f, t)
                    * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'multiplier')
    ;
69
    // Lower bound
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
    v_state.lo(gn_state(grid, node), sft(s, f, t))${p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useTimeSeries')
                                                    and not df_central(f,t)
                                                    }
            = ts_node_(grid, node, 'downwardLimit', s, f, t)
                    * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier')
    ;
    // Fixed value
    v_state.fx(gn_state(grid, node), sft(s, f, t))${p_gn(grid, node, 'boundAll')
                                                    and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries')
                                                    and not df_central(f,t)
                                                    }
            = ts_node_(grid, node, 'reference', s, f, t)
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier')
    ;
    // BoundEnd to a timeseries value
    v_state.fx(gn_state(grid, node), sft(s, f,t))${mft_lastSteps(mSolve, f, t)
                                                    and p_gn(grid, node, 'boundEnd')
                                                    and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries')
                                                    }
            = ts_node_(grid, node, 'reference', s, f, t)
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

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



    // Bound also the intervals just before the start of each sample - currently just 'upwardLimit'&'useConstant' and 'downwardLimit'&'useConstant'
    loop(mst_start(mSolve, s, t)$(tSolveFirst = mSettings(mSolve, 't_start')),

        // Upper bound
        v_state.up(gn_state(grid, node), s, f_solve, t+dt(t))${    p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')
                                                                and not df_central(f_solve,t)
                                                                and not node_superpos(node)
                                                                }
            = p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'multiplier');

        // Lower bound
        v_state.lo(gn_state(grid, node), s, f_solve, t+dt(t))${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
                                                                and not df_central(f_solve,t)
                                                                and not node_superpos(node)
                                                                }
            = p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'multiplier');
    ); // END loop(mst_start)


); //end loop node

// Next deal with bounds for the superposed node states
// note that boundstart is handled further below
loop(node_superpos(node),
    //add here the desired bounds for v_state_z
);


* =============================================================================
* --- Spilling of energy from the nodes----------------------------------------
* =============================================================================
Niina Helistö's avatar
Niina Helistö committed
135

136
// Max. & min. spilling, use constant value as base and overwrite with time series if desired
137
v_spill.lo(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'constant')   }
138
139
140
    = p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'multiplier')
;
141
v_spill.lo(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'useTimeSeries') }
142
    = ts_node_(grid, node_spill, 'minSpill', s, f, t)
143
144
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'minSpill', 'multiplier')
;
145
v_spill.up(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'constant') }
146
147
148
    = p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'constant')
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'multiplier')
;
149
v_spill.up(gn(grid, node_spill), sft(s, f, t))${    p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'useTimeSeries')    }
150
    = ts_node_(grid, node_spill, 'maxSpill', s, f, t)
151
152
153
154
        * p_gnBoundaryPropertiesForStates(grid, node_spill, 'maxSpill', 'multiplier')
;

* --- Unit Related Variable Boundaries ----------------------------------------
155

156
// Constant max. energy generation if investments disabled
157
158
159
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))
160
                                          and p_gnu(grid, node, unit, 'capacity')
161
                                    }
162
    = p_gnu(grid, node, unit, 'capacity')
163
164
165
        * p_unit(unit, 'availability')
;
// Time series capacity factor based max. energy generation if investments disabled
166
167
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)) }
168
169
170
    = sum(flow${    flowUnit(flow, unit_flow)
                    and nu(node, unit_flow)
                    },
171
        + ts_cf_(flow, node, s, f, t)
172
            * p_gnu(grid, node, unit_flow, 'capacity')
173
174
175
            * p_unit(unit_flow, 'availability')
      ) // END sum(flow)
;
176
177
178
179
180
181
182

// Maximum generation to zero for input nodes
v_gen.up(gnu_input(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)} = 0;

// Min. generation to zero for output nodes
v_gen.lo(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)} = 0;

183
// Constant max. consumption capacity if investments disabled
184
185
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))}
186
    = - p_gnu(grid, node, unit, 'capacity')
187
188
        * p_unit(unit, 'availability')
;
189
190
191
192
193
194
195

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))
                                          and not p_gnu(grid, node, unit, 'capacity')}
    = - inf
;

196
// Time series capacity factor based max. consumption if investments disabled
197
198
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))}
199
200
201
    = - sum(flow${  flowUnit(flow, unit_flow)
                    and nu(node, unit_flow)
                    },
202
          + ts_cf_(flow, node, s, f, t)
203
            * p_gnu(grid, node, unit_flow, 'capacity')
204
205
206
            * p_unit(unit_flow, 'availability')
      ) // END sum(flow)
;
207
// In the case of negative generation (currently only used for cooling equipment)
208
v_gen.lo(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
209
210
                                          and p_gnu(grid, node, unit, 'conversionCoeff') < 0   }
    = -p_gnu(grid, node, unit, 'capacity')
211
;
212
v_gen.up(gnu_output(grid, node, unit), sft(s, f, t))${gnuft(grid, node, unit, f, t)
213
                                          and p_gnu(grid, node, unit, 'conversionCoeff') < 0}
214
215
    = 0
;
216
217
218
// 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
219
220
221
222
223
224
225
226
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'
                                                    }
227
 = p_gnu(grid, node, unit, 'capacity')
228
229
230
231
232
233
234
235
236
237
        * 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'
                                                    }
238
 = -p_gnu(grid, node, unit, 'capacity')
239
240
        * p_gnu(grid, node, unit, 'maxRampDown')
        * 60;  // Unit conversion from [p.u./min] to [p.u./h]
241

242
243
// v_online cannot exceed unit count if investments disabled
// LP variant
244
v_online_LP.up(unit, sft(s, f, t))${uft_onlineLP(unit, f, t) and not (unit_investLP(unit) or unit_investMIP(unit))}
245
    = p_unit(unit, 'unitCount')
246
     * [1${not active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
247
       + (1 - ts_unit_(unit, 'unavailability', f, t))${active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
248
      ]
249
250
;
// MIP variant
251
v_online_MIP.up(unit, sft(s, f, t))${uft_onlineMIP(unit, f, t) and not (unit_investLP(unit) or unit_investMIP(unit))}
252
    = p_unit(unit, 'unitCount')
253
     * [1${not active(mSolve, 'checkUnavailability')}
254
       + (1 - ts_unit_(unit, 'unavailability', f, t))${active(mSolve, 'checkUnavailability')}
jussi ikäheimo's avatar
jussi ikäheimo committed
255
      ]
256
;
257

258
259
$ontext
// NOTE! These are unnecessary?
260
// Free the upper bound of start-up and shutdown variables (if previously bounded)
261
262
263
264
265
266
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;
267
v_shutdown.up(unit, sft(s, f, t))$uft(unit, f, t) = inf;
268
$offtext
269

270
// v_startup cannot exceed unitCount
271
272
273
274
275
276
277
278
279
280
281
282
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');
283
284

// v_shutdown cannot exceed unitCount
285
286
287
288
289
290
291
292
293
294
295
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');
296

297
298
299
300
// !!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// The following limits are extremely slow, and shouldn't strictly be required.
// Commenting them out for now at least.
$ontext
301
// Cannot start a unit if the time when the unit would become online is outside
302
// the horizon when the unit has an online variable
303
v_startup.up(unitStarttype(unit, starttype), sft(s, f, t))${    uft_online(unit, f, t)
304
                                                            and p_u_runUpTimeIntervals(unit)
305
306
                                                            and not sum(t_active(t_)${ord(t) = ord(t_) + dt_toStartup(unit,t_)}, uft_online(unit, f, t_))
                                                            }
307
    = 0;
308
309
// 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
310
311
312
313
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_))
                                  }
314
    = 0;
315
$offtext
316
317
318
319
320

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

321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
*----------------------------------------------------------------------IC RAMP-------------------------------------------------------------------------------------------------------------------------------------
v_ICramp.up(gn2n_directional(grid, node, node_), sft(s, f, t))${ ord(t) > msStart(mSolve, s) + 1
                                                                 and p_gnn(grid, node, node_, 'ICrampUp')
                                                                 and not p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                 }
 = p_gnn(grid, node, node_, 'transferCap')
       * p_gnn(grid, node, node_, 'ICrampUp')
       * 60;    // Unit conversion from [p.u./min] to [p.u./h]
v_ICramp.lo(gn2n_directional(grid, node, node_), sft(s, f, t))${ ord(t) > msStart(mSolve, s) + 1
                                                                 and p_gnn(grid, node, node_, 'ICrampDown')
                                                                 and not p_gnn(grid, node, node_, 'transferCapInvLimit')
                                                                 }
 = -(p_gnn(grid, node, node_, 'transferCap'))
       * p_gnn(grid, node, node_, 'ICrampDown')
       * 60;    // Unit conversion from [p.u./min] to [p.u./h]

*------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

339

340
* --- Energy Transfer Boundaries ----------------------------------------------
341

342
343
// Restrictions on transferring energy between nodes without investments
// Total transfer variable restricted from both above and below (free variable)
344
v_transfer.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
345
346
    = p_gnn(grid, node, node_, 'transferCap')
;
347
v_transfer.lo(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
348
349
350
    = -p_gnn(grid, node_, node, 'transferCap')
;
// Directional transfer variables only restricted from above (positive variables)
351
v_transferRightward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${ not p_gnn(grid, node, node_, 'transferCapInvLimit') }
352
353
    = p_gnn(grid, node, node_, 'transferCap')
;
354
v_transferLeftward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
355
356
357
358
359
    = p_gnn(grid, node_, node, 'transferCap')
;

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

360
// Loop over the forecasts to minimize confusion regarding the df_reserves forecast displacement
361
loop((restypeDirectionGridNode(restype, up_down, grid, node), sft(s, f, t))${ ord(t) <= tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length') },
362
363
    // Reserve provision limits without investments
    // Reserve provision limits based on resXX_range (or possibly available generation in case of unit_flow)
364
365
    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.
366
            and not (unit_investLP(unit) or unit_investMIP(unit))
367
            and not sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
368
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
369
                        )
370
            }
371
        = min ( p_gnuReserves(grid, node, unit, restype, up_down) * p_gnu(grid, node, unit, 'capacity'),  // Res_range limit
372
                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
373
374
                )${not gnuOfflineRescapable(restype, grid, node, unit)} // END min
            + p_gnuReserves(grid, node, unit, restype, up_down)${gnuOfflineRescapable(restype, grid, node, unit)}
375
              * p_gnu(grid, node, unit, 'capacity')
376
;
377

378
    // Reserve transfer upper bounds based on input p_nnReserves data, if investments are disabled
379
    v_resTransferRightward.up(restypeDirectionGridNodeNode(restype, up_down, grid, node, node_), s, f+df_reserves(grid, node, restype, f, t), t)
380
381
        ${  not p_gnn(grid, node, node_, 'transferCapInvLimit')
            and gn2n_directional(grid, node, node_)
382
            and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
383
                            ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
384
385
                            )  // This set contains the combination of reserve types and time intervals that should be fixed
                        or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
386
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
387
                           ) // Commit reserve transfer as long as either end commits.
388
                        ]
389
            }
390
        =  p_gnn(grid, node, node_, 'transferCap')
391
            * p_gnnReserves(grid, node, node_, restype, up_down);
392

393
    v_resTransferLeftward.up(restypeDirectionGridNodeNode(restype, up_down, grid, node, node_), s, f+df_reserves(grid, node, restype, f, t), t)
394
395
        ${  not p_gnn(grid, node, node_, 'transferCapInvLimit')
            and gn2n_directional(grid, node, node_)
396
            and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
397
                            ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
398
399
                            )  // This set contains the combination of reserve types and time intervals that should be fixed
                        or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
400
                               ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
401
                               ) // Commit reserve transfer as long as either end commits.
402
                        ]
403
            }
404
        = p_gnn(grid, node, node_, 'transferCap')
405
            * p_gnnReserves(grid, node, node_, restype, up_down);
406

407
    // Fix non-flow unit reserves at the gate closure of reserves
408
    v_reserve.fx(gnuRescapable(restype, up_down, grid, node, unit), s, f+df_reserves(grid, node, restype, f, t), t)
409
        $ { sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
410
                ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
411
                )  // This set contains the combination of reserve types and time intervals that should be fixed based on previous solves
412
413
            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)
            }
414
      = r_reserve(restype, up_down, grid, node, unit, f+df_reserves(grid, node, restype, f, t), t);
415

416
    // Fix transfer of reserves at the gate closure of reserves, LOWER BOUND ONLY!
417
    v_resTransferRightward.fx(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
418
        $ { gn2n_directional(grid, node, node_)
419
            and [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
420
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
421
422
                        )  // This set contains the combination of reserve types and time intervals that should be fixed
                    or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
423
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
424
                           ) // Commit reserve transfer as long as either end commits.
425
                    ]
426
          }
427
      = r_resTransferRightward(restype, up_down, grid, node, node_, f+df_reserves(grid, node, restype, f, t), t);
428

429
    v_resTransferLeftward.fx(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
430
        $ { gn2n_directional(grid, node, node_)
431
            and [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
432
                        ft_reservesFixed(group, restype, f+df_reserves(grid, node, restype, f, t), t)
433
434
                        )  // This set contains the combination of reserve types and time intervals that should be fixed
                    or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
435
                           ft_reservesFixed(group, restype, f+df_reserves(grid, node_, restype, f, t), t)
436
                           ) // Commit reserve transfer as long as either end commits.
437
                    ]
438
          }
439
      = r_resTransferLeftward(restype, up_down, grid, node, node_, f+df_reserves(grid, node, restype, f, t), t);
440

441
    // Fix slack variable for reserves that is used before the reserves need to be locked (vq_resMissing is used after this)
442
    vq_resDemand.fx(restype, up_down, group, s, f+df_reserves(grid, node, restype, f, t), t)
443
444
        $ { 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);
445

446
); // END loop(restypeDirectionGridNode, ft)
447

448
449
* --- Investment Variable Boundaries ------------------------------------------

450
// Unit Investments
451
// LP variant
452
v_invest_LP.up(unit)${    unit_investLP(unit) }
453
    = p_unit(unit, 'maxUnitCount')
454
;
455
v_invest_LP.lo(unit)${    unit_investLP(unit) }
456
    = p_unit(unit, 'minUnitCount')
457
458
;
// MIP variant
459
v_invest_MIP.up(unit)${   unit_investMIP(unit)    }
460
    = p_unit(unit, 'maxUnitCount')
461
;
462
v_invest_MIP.lo(unit)${   unit_investMIP(unit)    }
463
    = p_unit(unit, 'minUnitCount')
464
465
466
467
;

// Transfer Capacity Investments
// LP investments
Niina Helistö's avatar
Niina Helistö committed
468
v_investTransfer_LP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investLP(grid, from_node, to_node) }
469
470
471
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
;
// MIP investments
Niina Helistö's avatar
Niina Helistö committed
472
v_investTransfer_MIP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investMIP(grid, from_node, to_node) }
473
474
475
476
477
478
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
        / p_gnn(grid, from_node, to_node, 'unitSize')
;


* =============================================================================
479
* --- Bounds for the first (and last) interval --------------------------------
480
481
* =============================================================================

482
// Loop over the start intervals
483
loop((mft_start(mSolve, f, t), ms_initial(mSolve, s)),
484

485
    // If this is the very first solve, set various initial bounds
Topi Rasku's avatar
Topi Rasku committed
486
487
    if(tSolveFirst = mSettings(mSolve, 't_start'),

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        // state limits for normal (not superposed) nodes
        loop(node$(not node_superpos(node)),

            // Upper bound
            v_state.up(gn_state(grid, node), s, f, t)${    p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')
                                                        and not df_central(f,t)
                                                        }
                = p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'multiplier');

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

            // First solve, state variables (only if boundStart flag is true)
            v_state.fx(gn_state(grid, node), s, f, t)${ p_gn(grid, node, 'boundStart') }
                = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

            // Time series form boundary
            v_state.fx(gn_state(grid, node), s, f, t)${    p_gn(grid, node, 'boundStart')
                                                        and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries') // !!! NOTE !!! The check fails if value is zero
                                                        }
                = ts_node(grid, node, 'reference', f, t) // NOTE!!! ts_node_ doesn't contain initial values so using raw data instead.
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');
        ); //end loop of nodes
517

Topi Rasku's avatar
Topi Rasku committed
518

519
        // Initial online status for units
520
        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
521
522
            = p_unit(unit, 'initialOnlineStatus');

523
        v_online_LP.fx(unit, s, f, t)${p_unit(unit, 'useInitialOnlineStatus') and uft_onlineLP(unit, f, t+1)}
524
525
            = p_unit(unit, 'initialOnlineStatus');

526
        // Initial generation for units
527
        v_gen.fx(grid, node, unit, s, f, t)${p_gnu(grid, node, unit, 'useInitialGeneration')}
528
529
            = p_gnu(grid, node, unit, 'initialGeneration');

530
        // Startup and shutdown variables are not applicable at the first time step
531
532
        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;
533
534
        v_shutdown_LP.fx(unit_online_LP, s, f, t) = 0;
        v_shutdown_MIP.fx(unit_online_LP, s, f, t) = 0;
535

536
    else // For all other solves, fix the initial state values based on previous results.
Topi Rasku's avatar
Topi Rasku committed
537
538

        // State and online variable initial values for the subsequent solves
539
540
        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
541

542
        // Generation initial value (needed at least for ramp constraints)
543
544
        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')));
545

546
    ); // END if(tSolveFirst)
547
) // END loop(mft_start)
548
;
549

550
551
552
553
554
555
556
557
558
559
if(tSolveFirst = mSettings(mSolve, 't_start'),
    // state limits for normal (not superposed) nodes
    loop(node_superpos(node),
        // First solve, state variables (only if boundStart flag is true)
        v_state_z.fx(gn_state(grid, node), z)${ p_gn(grid, node, 'boundStart') }
            = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
                    * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

        ) //END loop node_superpos
); //END if(tSolveFirst)
560
561
562
563

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

565
// Needed for modelling hot and warm start-ups, minimum uptimes and downtimes, and run-up and shutdown phases.
566
if( tSolveFirst <> mSettings(mSolve, 't_start'), // Avoid rewriting the fixes on the first solve handled above
567
568
569
570
571
572
    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
573
        = r_startup(unit, starttype, f, t);
574

575
576
577
578
579
580
    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
581
        = r_shutdown(unit, f, t);
582

583
    v_online_MIP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
584
585
        ${  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
586
587
            }
        = round(r_online(unit, f, t));
588

589
    v_online_LP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
590
591
        ${  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
592
593
594
            }
        = r_online(unit, f, t);
); // END if
595

596
597
598

*v_invest_LP.fx("U75FI_02Es_wind") = 50;

599
600
601
602
* =============================================================================
* --- Fix previously realized investment results ------------------------------
* =============================================================================

603
604
v_invest_LP.fx(unit_investLP(unit))${ p_unit(unit, 'becomeAvailable') <= tSolveFirst }
    = r_invest(unit)
605
;
606
607
v_invest_MIP.fx(unit_investMIP(unit))${ p_unit(unit, 'becomeAvailable') <= tSolveFirst }
    = r_invest(unit)
608
609
610
611
612
613
614
615
616
617
618
619
620
;
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')
;