3d_setVariableLimits.gms 28.9 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
61
    = ts_node_(grid, node,   'upwardLimit', f, t, s)
        * 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', f, t, s)
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', f, t, s)
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', f, t, s)
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
95
96
loop(mst_start(mSolve, s, t),
    // 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', f, t, s)
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', f, t, s)
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, f, t, s)
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, f, t, s)
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
loop(ms(mSolve, s),
191
192
193
194
195
196
    v_genRamp.up(grid, node, unit, sft(s, f, t))${gnuft_ramp(grid, node, unit, f, t)
                                                  and ord(t) > msStart(mSolve, s) + 1
                                                  and msft(mSolve, s, f, t)
                                                  and p_gnu(grid, node, unit, 'maxRampUp')
                                                  and not unit_investLP(unit)
                                                  and not unit_investMIP(unit)
197
                                                  and not uft_startupTrajectory(unit, f, t) // Trajectories require occasional combinations with 'rampSpeedToMinLoad'
198
199
                                                 }
     = ( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') )
200
            * p_gnu(grid, node, unit, 'maxRampUp')
201
            * 60;  // Unit conversion from [p.u./min] to [p.u./h]
202
203
204
205
206
207
    v_genRamp.lo(grid, node, unit, sft(s, f, t))${gnuft_ramp(grid, node, unit, f, t)
                                                  and ord(t) > msStart(mSolve, s) + 1
                                                  and msft(mSolve, s, f, t)
                                                  and p_gnu(grid, node, unit, 'maxRampDown')
                                                  and not unit_investLP(unit)
                                                  and not unit_investMIP(unit)
208
                                                  and not uft_startupTrajectory(unit, f, t) // Trajectories require occasional combinations with 'rampSpeedFromMinLoad'
209
210
                                                 }
     = -( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons'))
211
            * p_gnu(grid, node, unit, 'maxRampDown')
212
            * 60;  // Unit conversion from [p.u./min] to [p.u./h]
213
214
);

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

// Free the upper bound of start-up and shutdown variables (if previously bounded)
232
233
v_startup.up(unitStarttype(unit, starttype), sft(s, f, t)) = inf;
v_shutdown.up(unit, sft(s, f, t))$uft(unit, f, t) = inf;
234

235
// v_startup cannot exceed unitCount
236
v_startup.up(unitStarttype(unit, starttype), sft(s, f, t))${    uft_online(unit, f, t)
237
238
239
                                                            and not unit_investLP(unit)
                                                            and not unit_investMIP(unit)
                                                            }
240
241
    = p_unit(unit, 'unitCount')
;
242
243

// v_shutdown cannot exceed unitCount
244
245
246
v_shutdown.up(unit, sft(s, f, t))${uft_online(unit, f, t)
                                   and not unit_investLP(unit)
                                   and not unit_investMIP(unit)}
247
248
249
    = p_unit(unit, 'unitCount')
;

250
// Cannot start a unit if the time when the unit would become online is outside
251
// the horizon when the unit has an online variable
252
v_startup.up(unitStarttype(unit, starttype), sft(s, f, t))${    uft_online(unit, f, t)
253
                                                            and p_u_runUpTimeIntervals(unit)
254
255
                                                            and not sum(t_active(t_)${ord(t) = ord(t_) + dt_toStartup(unit,t_)}, uft_online(unit, f, t_))
                                                            }
256
    = 0;
257
258
// 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
259
260
261
262
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_))
                                  }
263
264
    = 0;

265
266
267
268
269

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

270
// Fuel use limitations
271
272
v_fuelUse.up(fuel, unit, sft(s, f, t))${uft(unit, f, t)
                                        and p_uFuel(unit, 'main', fuel, 'maxFuelCons')}
273
274
    = p_uFuel(unit, 'main', fuel, 'maxFuelCons')
;
275

276
* --- Energy Transfer Boundaries ----------------------------------------------
277

278
279
// Restrictions on transferring energy between nodes without investments
// Total transfer variable restricted from both above and below (free variable)
280
v_transfer.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
281
282
    = p_gnn(grid, node, node_, 'transferCap')
;
283
v_transfer.lo(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
284
285
286
    = -p_gnn(grid, node_, node, 'transferCap')
;
// Directional transfer variables only restricted from above (positive variables)
287
v_transferRightward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${ not p_gnn(grid, node, node_, 'transferCapInvLimit') }
288
289
    = p_gnn(grid, node, node_, 'transferCap')
;
290
v_transferLeftward.up(gn2n_directional(grid, node, node_), sft(s, f, t))${  not p_gnn(grid, node, node_, 'transferCapInvLimit') }
291
292
293
294
295
    = p_gnn(grid, node_, node, 'transferCap')
;

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

296
// Loop over the forecasts to minimize confusion regarding the df_reserves forecast displacement
297
loop((restypeDirectionNode(restype, up_down, node), sft(s, f, t))${ ord(t) <= tSolveFirst + p_nReserves(node, restype, 'reserve_length') },
298
299
    // Reserve provision limits without investments
    // Reserve provision limits based on resXX_range (or possibly available generation in case of unit_flow)
300
    v_reserve.up(nuRescapable(restype, up_down, node, unit), s, f+df_reserves(node, restype, f, t), t)
301
302
        ${  nuft(node, unit, f, t) // nuft is not displaced by df_reserves, as the unit exists on normal ft.
            and not (unit_investLP(unit) or unit_investMIP(unit))
303
            and not ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)
304
            }
305
        = min ( p_nuReserves(node, unit, restype, up_down) * [ p_gnu('elec', node, unit, 'maxGen') + p_gnu('elec', node, unit, 'maxCons') ],  // Generator + consuming unit res_range limit
306
                v_gen.up('elec', node, unit, s, f, t) - v_gen.lo('elec', node, unit, s, f, t) // Generator + consuming unit available unit_elec. output delta
307
                ) // END min
308
;
309

310
    // Reserve transfer upper bounds based on input p_nnReserves data, if investments are disabled
311
    v_resTransferRightward.up(restypeDirectionNodeNode(restype, up_down, node, node_), s, f+df_reserves(node, restype, f, t), t)
312
        ${  not sum(grid, p_gnn(grid, node, node_, 'transferCapInvLimit')) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
313
            and sum(grid, gn2n_directional(grid, node, node_)) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
314
315
316
            and not [   ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)  // This set contains the combination of reserve types and time intervals that should be fixed
                        or ft_reservesFixed(node_, restype, f+df_reserves(node_, restype, f, t), t) // Commit reserve transfer as long as either end commits.
                        ]
317
318
            }
        = sum(grid, // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
319
            + p_gnn(grid, node, node_, 'transferCap')
320
            ) // END sum(grid)
321
            * p_nnReserves(node, node_, restype, up_down);
322

323
    v_resTransferLeftward.up(restypeDirectionNodeNode(restype, up_down, node, node_), s, f+df_reserves(node, restype, f, t), t)
324
        ${  not sum(grid, p_gnn(grid, node, node_, 'transferCapInvLimit')) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
325
            and sum(grid, gn2n_directional(grid, node, node_)) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
326
327
328
            and not [   ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)  // This set contains the combination of reserve types and time intervals that should be fixed
                        or ft_reservesFixed(node_, restype, f+df_reserves(node_, restype, f, t), t) // Commit reserve transfer as long as either end commits.
                        ]
329
330
            }
        = sum(grid, // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
331
            + p_gnn(grid, node, node_, 'transferCap')
332
            ) // END sum(grid)
333
            * p_nnReserves(node, node_, restype, up_down);
334

335
    // Fix non-flow unit reserves at the gate closure of reserves
336
    v_reserve.fx(nuRescapable(restype, up_down, node, unit), s, f+df_reserves(node, restype, f, t), t)
337
        $ { ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)  // This set contains the combination of reserve types and time intervals that should be fixed based on previous solves
338
339
            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)
            }
340
      = r_reserve(restype, up_down, node, unit, f+df_reserves(node, restype, f, t), t);
341
342

    // Fix transfer of reserves at the gate closure of reserves
343
    v_resTransferRightward.fx(restype, up_down, node, node_, s, f+df_reserves(node, restype, f, t), t)
344
        $ { sum(grid, gn2n_directional(grid, node, node_)) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
345
346
347
            and [   ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)  // This set contains the combination of reserve types and time intervals that should be fixed
                    or ft_reservesFixed(node_, restype, f+df_reserves(node_, restype, f, t), t) // Commit reserve transfer as long as either end commits.
                    ]
348
          }
349
      = r_resTransferRightward(restype, up_down, node, node_, f+df_reserves(node, restype, f, t), t);
350

351
    v_resTransferLeftward.fx(restype, up_down, node, node_, s, f+df_reserves(node, restype, f, t), t)
352
        $ { sum(grid, gn2n_directional(grid, node, node_)) // NOTE! This is not ideal, but the reserve sets and variables are currently lacking the grid dimension...
353
354
355
            and [   ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t)  // This set contains the combination of reserve types and time intervals that should be fixed
                    or ft_reservesFixed(node_, restype, f+df_reserves(node_, restype, f, t), t) // Commit reserve transfer as long as either end commits.
                    ]
356
          }
357
      = r_resTransferLeftward(restype, up_down, node, node_, f+df_reserves(node, restype, f, t), t);
358

359
    // Fix slack variable for reserves that is used before the reserves need to be locked (vq_resMissing is used after this)
360
    vq_resDemand.fx(restype, up_down, node, s, f+df_reserves(node, restype, f, t), t)
361
362
        $ { ft_reservesFixed(node, restype, f+df_reserves(node, restype, f, t), t) }  // This set contains the combination of reserve types and time intervals that should be fixed
      = r_qResDemand(restype, up_down, node, f+df_reserves(node, restype, f, t), t);
363

364
); // END loop(restypeDirectionNode, ft)
365

366
367
* --- Investment Variable Boundaries ------------------------------------------

368
// Unit Investments
369
370
// LP variant
v_invest_LP.up(unit, t_invest)${    unit_investLP(unit) }
371
    = p_unit(unit, 'maxUnitCount')
372
373
;
v_invest_LP.lo(unit, t_invest)${    unit_investLP(unit) }
374
    = p_unit(unit, 'minUnitCount')
375
376
377
;
// MIP variant
v_invest_MIP.up(unit, t_invest)${   unit_investMIP(unit)    }
378
    = p_unit(unit, 'maxUnitCount')
379
380
;
v_invest_MIP.lo(unit, t_invest)${   unit_investMIP(unit)    }
381
    = p_unit(unit, 'minUnitCount')
382
383
384
385
;

// Transfer Capacity Investments
// LP investments
Niina Helistö's avatar
Niina Helistö committed
386
v_investTransfer_LP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investLP(grid, from_node, to_node) }
387
388
389
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
;
// MIP investments
Niina Helistö's avatar
Niina Helistö committed
390
v_investTransfer_MIP.up(gn2n_directional(grid, from_node, to_node), t_invest)${ gn2n_directional_investMIP(grid, from_node, to_node) }
391
392
393
394
395
396
    = p_gnn(grid, from_node, to_node, 'transferCapInvLimit')
        / p_gnn(grid, from_node, to_node, 'unitSize')
;


* =============================================================================
397
* --- Bounds for the first (and last) interval --------------------------------
398
399
* =============================================================================

400
// Loop over the start intervals
401
loop((mft_start(mSolve, f, t), ms_initial(mSolve, s)),
402

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

406
        // Upper bound
407
        v_state.up(gn_state(grid, node), s, f, t)${    p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')
408
409
410
411
412
413
                                                    and not df_central(f,t)
                                                    }
            = p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node,   'upwardLimit', 'multiplier');

        // Lower bound
414
        v_state.lo(gn_state(grid, node), s, f, t)${    p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')
415
416
417
418
419
                                                    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
420
        // First solve, state variables (only if boundStart flag is true)
421
        v_state.fx(gn_state(grid, node), s, f, t)${ p_gn(grid, node, 'boundStart') }
Topi Rasku's avatar
Topi Rasku committed
422
423
424
425
            = p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'constant')
                * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

        // Time series form boundary
426
        v_state.fx(gn_state(grid, node), s, f, t)${    p_gn(grid, node, 'boundStart')
427
                                                    and p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'useTimeSeries') // !!! NOTE !!! The check fails if value is zero
Topi Rasku's avatar
Topi Rasku committed
428
                                                    }
429
            = 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
430
431
                * p_gnBoundaryPropertiesForStates(grid, node, 'reference', 'multiplier');

432
        // Initial online status for units
433
        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
434
435
            = p_unit(unit, 'initialOnlineStatus');

436
        v_online_LP.fx(unit, s, f, t)${p_unit(unit, 'useInitialOnlineStatus') and uft_onlineLP(unit, f, t+1)}
437
438
            = p_unit(unit, 'initialOnlineStatus');

439
        // Initial generation for units
440
        v_gen.fx(grid, node, unit, s, f, t)${p_gnu(grid, node, unit, 'useInitialGeneration')}
441
442
            = p_gnu(grid, node, unit, 'initialGeneration');

443
444
445
        // Startup and shutdown variables are not applicable at the first time step
        v_startup.fx(unitStarttype(unit, starttype), s, f, t) = 0;
        v_shutdown.fx(unit, s, f, t) = 0;
446

447
    else // For all other solves, fix the initial state values based on previous results.
Topi Rasku's avatar
Topi Rasku committed
448
449

        // State and online variable initial values for the subsequent solves
450
451
        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
452

453
        // Generation initial value (needed at least for ramp constraints)
454
455
        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')));
456

457
    ); // END if(tSolveFirst)
458
) // END loop(mft_start)
459
;
460

461
462
463
464

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

466
// Needed for modelling hot and warm start-ups, minimum uptimes and downtimes, and run-up and shutdown phases.
467
if( tSolveFirst <> mSettings(mSolve, 't_start'), // Avoid rewriting the fixes on the first solve handled above
468
    v_startup.fx(unitStarttype(unit, starttype), sft_realizedNoReset(s, f, t_active(t)))
469
470
        ${  ord(t) <= tSolveFirst // Only fix previously realized time steps
            and unit_online(unit) // Check if the unit has an online variable on the first effLevel
471
472
            }
        = r_startup(unit, starttype, f, t);
473

474
    v_shutdown.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
475
476
        ${  ord(t) <= tSolveFirst // Only fix previously realized time steps
            and unit_online(unit) // Check if the unit has an online variable on the first effLevel
477
478
            }
        = r_shutdown(unit, f, t);
479

480
    v_online_MIP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
481
482
        ${  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
483
484
            }
        = round(r_online(unit, f, t));
485

486
    v_online_LP.fx(unit, sft_realizedNoReset(s, f, t_active(t)))
487
488
        ${  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
489
490
491
            }
        = r_online(unit, f, t);
); // END if