2b_equations.gms 34.9 KB
Newer Older
1
2
equations
    q_obj "Objective function"
3
    q_balance(grid, node, mType, f, t) "Energy demand must be satisfied at each node"
4
    q_resDemand(restype, resdirection, node, f, t) "Procurement for each reserve type is greater than demand"
5
    q_maxDownward(grid, node, unit, f, t) "Downward commitments will not undercut power plant minimum load constraints or maximum elec. consumption"
6
    q_maxUpward(grid, node, unit, f, t) "Upward commitments will not exceed maximum available capacity or consumed power"
Juha Kiviluoma's avatar
Juha Kiviluoma committed
7
8
    q_bindOnline(unit, mType, f, t) "Couple online variable when joining forecasts or when joining sample time periods"
    q_startup(unit, f, t) "Capacity started up is greater than the difference of online cap. now and in the previous time step"
9
    q_conversionDirectInputOutput(effSelector, unit, f, t) "Direct conversion of inputs to outputs (no piece-wise linear part-load efficiencies)"
Juha Kiviluoma's avatar
Juha Kiviluoma committed
10
11
12
13
14
    q_conversionSOS2InputIntermediate(effSelector, unit, f, t)   "Intermediate output when using SOS2 variable based part-load piece-wise linearization"
    q_conversionSOS2Constraint(effSelector, unit, f, t)          "Sum of v_sos2 has to equal v_online"
    q_conversionSOS2IntermediateOutput(effSelector, unit, f, t)  "Output is forced equal with v_sos2 output"
    q_outputRatioFixed(grid, node, grid, node, unit, f, t) "Force fixed ratio between two energy outputs into different energy grids"
    q_outputRatioConstrained(grid, node, grid, node, unit, f, t) "Constrained ratio between two grids of energy output; e.g. electricity generation is greater than cV times unit_heat generation in extraction plants"
15
    q_transferLimit(grid, node, node, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity"
16
    q_stateSlack(grid, node, slack, f, t) "Slack variable greater than the difference between v_state and the slack boundary"
17
18
    q_stateUpwardLimit(grid, node, mType, f, t) "Limit the commitments of a node with a state variable to the available headrooms"
    q_stateDownwardLimit(grid, node, mType, f, t) "Limit the commitments of a node with a state variable to the available headrooms"
19
    q_boundState(grid, node, node, mType, f, t) "Node state variables bounded by other nodes"
20
    q_boundCyclic(grid, node, mType, f, t, t_) "Cyclic bound for the first and the last state"
21
22
23
;


24
$setlocal def_penalty 1e9
25
26
27
28
Scalars
    PENALTY "Default equation violation penalty" / %def_penalty% /
;
Parameters
29
    PENALTY_BALANCE(grid) "Penalty on violating energy balance eq. (/MWh)"
30
    PENALTY_RES(restype, resdirection) "Penalty on violating a reserve (/MW)"
31
;
32
PENALTY_BALANCE(grid) = %def_penalty%;
33
PENALTY_RES(restype, resdirection) =  1e-3*%def_penalty%;
34
35
36

* -----------------------------------------------------------------------------
q_obj ..
Juha Kiviluoma's avatar
Juha Kiviluoma committed
37
  + v_obj * 1000000
38
39
40
41
42
43
  =E=
  + sum(msft(m, s, f, t),
        p_sProbability(s) *
        p_fProbability(f) *
        (
           // Variable O&M costs
Juha Kiviluoma's avatar
Juha Kiviluoma committed
44
45
           sum(gnu_output(grid, node, unit),  // Calculated only for output energy
                p_unit(unit, 'omCosts') *
46
47
                $$ifi not '%rampSched%' == 'yes' p_stepLength(m, f, t) *
                $$ifi '%rampSched%' == 'yes' (p_stepLength(m, f, t) + p_stepLength(m, f, t+1))/2 *
48
                     v_gen(grid, node, unit, f, t)$nuft(node, unit, f, t)
49
50
           )
           // Fuel and emission costs
Juha Kiviluoma's avatar
Juha Kiviluoma committed
51
52
53
54
55
56
57
58
59
60
61
         + sum((node, unit_fuel, fuel)$(nu(node, unit_fuel) and uFuel(unit_fuel, 'main', fuel)),
              + p_stepLength(m, f, t)
              * v_fuelUse(fuel, unit_fuel, f, t)$nuft(node, unit_fuel, f, t)
                  * ( + sum{tFuel$[ord(tFuel) <= ord(t)],
                            ts_fuelPriceChangenode(fuel, node, tFuel) }  // Fuel costs, sum initial fuel price plus all subsequent changes to the fuelprice
                      + sum{emission,         // Emission taxes
                            p_fuelEmission(fuel, emission) / 1e3
                              * sum(grid$gnu_output(grid, node, unit_fuel), p_gnPolicy(grid, node, 'emissionTax', emission))  // Sum emission costs from different output energy types
                        }
                     )
           )
62
           // Start-up costs
63
         + sum(uft_online(unit, f, t),
64
             + {
65
66
67
68
                 + v_startup(unit, f, t) // Cost of starting up
                 - sum(t_${mftStart(m, f, t_) and uft_online(unit, f, t_)}, 0.5 * v_online(unit, f, t_))     // minus value of avoiding startup costs before
                 - sum(t_${mftLastSteps(m, f, t_) and uft_online(unit, f, t_)}, 0.5 * v_online(unit, f, t_)) // or after the model solve
               } / p_unit(unit, 'unitCount')
69
70
             * {
                  // Startup variable costs
71
72
                 + p_unit(unit, 'startupCost')
                 * p_unit(unit, 'unitCapacity')
73
                  // Start-up fuel and emission costs
Juha Kiviluoma's avatar
Juha Kiviluoma committed
74
                 + sum(uFuel(unit_fuel, 'startup', fuel),
75
76
77
                     + p_unit(unit, 'startupFuelCons')
                     * p_unit(unit, 'unitCapacity')
                     * sum(gnu_output(grid, node, unit),
78
                           // Fuel costs for start-up fuel use
Juha Kiviluoma's avatar
Juha Kiviluoma committed
79
80
81
82
83
84
85
                         + ( + sum{tFuel$[ord(tFuel) <= ord(t)],
                                   ts_fuelPriceChangenode(fuel, node, tFuel) }
                               // Emission taxes of startup fuel use
                             + sum(emission,
                                p_fuelEmission(fuel, emission) / 1e3
                                  * p_gnPolicy(grid, node, 'emissionTax', emission)  // Sum emission costs from different output energy types
                               )
86
87
                           ) / p_gnu(grid, node, unit, 'maxGen')  // Calculate these in relation to maximum output ratios between multiple outputs
                       ) * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))  // see line above
88
89
90
91
92
93
                   )
               }
           )
        )  // p_sProbability(s) & p_fProbability(f)
    ) // msft(m, s, f, t)
    // Value of energy storage change
Juha Kiviluoma's avatar
Juha Kiviluoma committed
94
95
96
97
98
  - sum((mftLastSteps(m, f, t), mftStart(m, f_, t_)) $(active('storageValue')),
        p_fProbability(f) *
          sum(gn_state(grid, node),
              p_storageValue(grid, node, t) *
                  (v_state(grid, node, f, t) - v_state(grid, node, f_, t_))
99
          )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
100
101
102
103
    )
    // Dummy variables
  + sum(msft(m, s, f, t), p_sProbability(s) * p_fProbability(f) * (
        sum(inc_dec,
104
            sum( gn(grid, node), vq_gen(inc_dec, grid, node, f, t) * (p_stepLength(m, f, t) + p_stepLength(m, f+pf(f,t), t+pt(t))${not p_stepLength(m, f, t)}) * PENALTY_BALANCE(grid) )
105
        )
106
        + sum(restypeDirectionNode(restype, resdirection, node),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
107
108
109
              vq_resDemand(restype, resdirection, node, f, t)
            * p_stepLength(m, f, t)
            * PENALTY_RES(restype, resdirection)
110
111
          )
      )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
112
113
    )
    // Node state slack variable penalties
114
  + sum(gn_stateSlack(grid, node),
115
      + sum(msft(m, s, f, t),
116
          + sum(upwardSlack$p_gnBoundaryPropertiesForStates(grid, node, upwardSlack, 'slackCost'),
117
118
              + p_sProbability(s) * p_fProbability(f) * ( p_gnBoundaryPropertiesForStates(grid, node,   upwardSlack, 'slackCost') * v_stateSlack(grid, node,   upwardSlack, f, t) )
            )
119
          + sum(downwardSlack$p_gnBoundaryPropertiesForStates(grid, node, downwardSlack, 'slackCost'),
120
121
              + p_sProbability(s) * p_fProbability(f) * ( p_gnBoundaryPropertiesForStates(grid, node, downwardSlack, 'slackCost') * v_stateSlack(grid, node, downwardSlack, f, t) )
            )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
122
123
        )
    )
124
;
125

126
* -----------------------------------------------------------------------------
127
q_balance(gn(grid, node), m, ft_dynamic(f, t))$(p_stepLength(m, f+pf(f,t), t+pt(t)) and not p_gn(grid, node, 'boundAll')) ..   // Energy/power balance dynamics solved using implicit Euler discretization
128
  // The left side of the equation is the change in the state (will be zero if the node doesn't have a state)
129
130
131
  + p_gn(grid, node, 'energyStoredPerUnitOfState')$gn_state(grid, node) // Unit conversion between v_state of a particular node and energy variables (defaults to 1, but can have node based values if e.g. v_state is in Kelvins and each node has a different heat storage capacity)
      * ( + v_state(grid, node, f, t)                                   // The difference between current
          - v_state(grid, node, f+pf(f,t), t+pt(t))                     // ... and previous state of the node
132
133
134
135
136
137
        )
  =E=
  // The right side of the equation contains all the changes converted to energy terms
  + (
      + (
          // Self discharge out of the model boundaries
138
139
140
          - p_gn(grid, node, 'selfDischargeLoss')$gn_state(grid, node) * (
              + v_state(grid, node, f, t)                                               // The current state of the node
              $$ifi '%rampSched%' == 'yes' + v_state(grid, node, f+pf(f,t), t+pt(t))    // and possibly averaging with the previous state of the node
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
            )
          // Energy diffusion from this node to neighbouring nodes
          - sum(to_node$(gnn_state(grid, node, to_node)),
              + p_gnn(grid, node, to_node, 'diffCoeff') * (
                  + v_state(grid, node, f, t)
                  $$ifi '%rampSched%' == 'yes' + v_state(grid, node, f+pf(f,t), t+pt(t))
                )
            )
          // Energy diffusion from neighbouring nodes to this node
          + sum(from_node$(gnn_state(grid, from_node, node)),
              + p_gnn(grid, from_node, node, 'diffCoeff') * (
                  + v_state(grid, from_node, f, t)                                             // Incoming diffusion based on the state of the neighbouring node
                  $$ifi '%rampSched%' == 'yes' + v_state(grid, from_node, f+pf(f,t), t+pt(t))  // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
            )
          // Controlled energy transfer from other nodes to this one
          + sum(from_node$(gn2n(grid, from_node, node)),
              + (1 - p_gnn(grid, from_node, node, 'transferLoss')) * (    // Include transfer losses
                  + v_transfer(grid, from_node, node, f+pf(f,t), t+pt(t))
                  $$ifi '%rampSched%' == 'yes' + v_transfer(grid, from_node, node, f, t)    // Ramp schedule averaging, NOTE! State and other terms use different indeces for non-ramp-schedule!
                )
            )
          // Controlled energy transfer to other nodes from this one
          - sum(to_node$(gn2n(grid, node, to_node)),
              + v_transfer(grid, node, to_node, f+pf(f,t), t+pt(t))   // Transfer losses accounted for in the previous term
              $$ifi '%rampSched%' == 'yes' + v_transfer(grid, node, to_node, f, t)    // Ramp schedule averaging
            )
          // Interactions between the node and its units
169
170
171
          + sum(gnuft(grid, node, unit, f+pf(f,t), t+pt(t)),
              + v_gen(grid, node, unit, f+pf(f,t), t+pt(t)) // Unit energy generation and consumption
              $$ifi '%rampSched%' == 'yes' + v_gen(grid, node, unit, f, t)
172
173
174
            )
          // Spilling energy out of the endogenous grids in the model
          - v_spill(grid, node, f+pf(f,t), t+pt(t))$node_spill(node)
175
          $$ifi '%rampSched%' == 'yes' - v_spill(grid, node, f, t)$node_spill(node)
176
          // Power inflow and outflow timeseries to/from the node
177
178
179
          + ts_influx_(grid, node, f+pf(f,t), t+pt(t))   // Incoming (positive) and outgoing (negative) absolute value time series
          $$ifi '%rampSched%' == 'yes' + ts_influx_(grid, node, f, t)

180
181
182
183
184
          // Dummy generation variables, for feasibility purposes
          + vq_gen('increase', grid, node, f+pf(f,t), t+pt(t)) // Note! When stateSlack is permitted, have to take caution with the penalties so that it will be used first
          $$ifi '%rampSched%' == 'yes' + vq_gen('increase', grid, node, f, t)
          - vq_gen('decrease', grid, node, f+pf(f,t), t+pt(t)) // Note! When stateSlack is permitted, have to take caution with the penalties so that it will be used first
          $$ifi '%rampSched%' == 'yes' - vq_gen('decrease', grid, node, f, t)
185
186
187
        ) * p_stepLength(m, f+pf(f,t), t+pt(t))   // Multiply by time step to get energy terms
    )
  $$ifi '%rampSched%' == 'yes' / 2    // Averaging all the terms on the right side of the equation over the timestep here.
188
189
;
* -----------------------------------------------------------------------------
190
q_resDemand(restypeDirectionNode(restype, resdirection, node), ft(f, t)) ..
191
192
  + sum(nuft(node, unit, f, t)$nuRescapable(restype, resdirection, node, unit),   // Reserve capable units on this node
        v_reserve(restype, resdirection, node, unit, f, t)
193
    )
194
195
  + sum(gnu_input(grid, node, unit)${gnuft(grid, node, unit, f, t) AND nuRescapable(restype, resdirection, node, unit)},
        v_reserve(restype, resdirection, node, unit, f, t) // Reserve capable units with input from this node
196
197
    )
  + sum(gn2n(grid, from_node, node)$restypeDirectionNode(restype, resdirection, from_node),
198
        (1 - p_gnn(grid, from_node, node, 'transferLoss')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
199
        ) * v_resTransfer(restype, resdirection, from_node, node, f, t)             // Reserves from another node - reduces the need for reserves in the node
200
201
    )
  =G=
202
203
  + ts_reserveDemand_(restype, resdirection, node, f, t)
  - vq_resDemand(restype, resdirection, node, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
204
205
  + sum(gn2n(grid, node, to_node)$restypeDirectionNode(restype, resdirection, to_node),   // If trasferring reserves to another node, increase your own reserves by same amount
        v_resTransfer(restype, resdirection, node, to_node, f, t)
206
207
208
    )
;
* -----------------------------------------------------------------------------
209
q_maxDownward(gnuft(grid, node, unit, f, t))${     [uft_online(unit, f, t) and p_gnu(grid, node, unit, 'maxGen')]    // generators with online variables
210
                                                  or sum(restype, nuRescapable(restype, 'resDown', node, unit))      // all units with downward reserve provision
211
                                                  or [p_gnu(grid, node, unit, 'maxCons') and uft_online(unit, f, t)] // consuming units with an online variable
212
                                                }..
213
  + v_gen(grid, node, unit, f, t)                                                                                    // energy generation/consumption
Juha Kiviluoma's avatar
Juha Kiviluoma committed
214
215
216
  + sum( gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit),
        p_gnu(grid_, node_, unit, 'cV') * v_gen(grid_, node_, unit, f, t) )                              // considering output constraints (e.g. cV line)
  - sum(nuRescapable(restype, 'resDown', node, unit),                                                                // minus downward reserve participation
217
        v_reserve(restype, 'resDown', node, unit, f, t)                                                              // (v_reserve can be used only if the unit is capable of providing a particular reserve)
218
    )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
219
  =G=                                                                                                                // must be greater than minimum load or maximum consumption  (units with min-load and both generation and consumption are not allowed)
220
  + v_online(unit, f, t)${uft_online(unit, f, t)} // Online variables should only be generated for units with restrictions
221
    / p_unit(unit, 'unitCount')
222
223
224
225
    * p_gnu(grid, node, unit, 'maxGen')
    * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
        + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
      )
226
  + v_gen.lo(grid, node, unit, f, t) * [ (v_online(unit, f, t) / p_unit(unit, 'unitCount'))$uft_online(unit, f, t) + 1$(not uft_online(unit, f, t)) ]         // notice: v_gen.lo for consuming units is negative
227
228
;
* -----------------------------------------------------------------------------
229
q_maxUpward(gnuft(grid, node, unit, f, t))${      [uft_online(unit, f, t) and p_gnu(grid, node, unit, 'maxCons')]    // consuming units with online variables
230
                                                 or sum(restype, nuRescapable(restype, 'resUp', node, unit))         // all units with upward reserve provision
231
                                                 or [p_gnu(grid, node, unit, 'maxGen') and uft_online(unit, f, t)]   // generators with an online variable
232
                                               }..
233
  + v_gen(grid, node, unit, f, t)                                                                                    // energy generation/consumption
Juha Kiviluoma's avatar
Juha Kiviluoma committed
234
235
236
  + sum( gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit),
         p_gnu(grid_, node_, unit, 'cV') * v_gen(grid_, node_, unit, f, t) )                             // considering output constraints (e.g. cV line)
  + sum(nuRescapable(restype, 'resUp', node, unit),                                                                  // plus upward reserve participation
237
        v_reserve(restype, 'resUp', node, unit, f, t)                                                                // (v_reserve can be used only if the unit can provide a particular reserve)
238
239
    )
  =L=                                                                         // must be less than available/online capacity
240
241
242
243
244
245
  - v_online(unit, f, t)${uft_online(unit, f, t)} // Online variables should only be generated for units with restrictions
    / p_unit(unit, 'unitCount')
    * p_gnu(grid, node, unit, 'maxCons')
    * sum(effGroup, // Uses the minimum 'lb' for the current efficiency approximation
        + (p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)} + ts_effGroupUnit(effGroup, unit, 'lb', f, t))
      )
246
  + v_gen.up(grid, node, unit, f, t) * [ (v_online(unit, f, t) / p_unit(unit, 'unitCount'))$uft_online(unit, f, t) + 1$(not uft_online(unit, f, t)) ]
247
248
;
* -----------------------------------------------------------------------------
249
250
q_startup(uft_online(unit, ft_dynamic(f, t))) ..
  + v_startup(unit, f+pf(f,t), t+pt(t))
251
  =G=
252
253
  + v_online(unit, f, t)
  - v_online(unit, f+pf(f,t), t+pt(t)) // This reaches to tFirstSolve when pt = -1
254
;
255
* -----------------------------------------------------------------------------
256
257
q_bindOnline(unit, mftBind(m, f, t))${uft_online(unit, f, t)} ..
  + v_online(unit, f, t)
258
  =E=
259
  + v_online(unit, f+mft_bind(m,f,t), t+mt_bind(m,t))$uft_online(unit, f+mft_bind(m,f,t), t+mt_bind(m,t))
260
;
261
* -----------------------------------------------------------------------------
262
q_conversionDirectInputOutput(suft(effDirect, unit, f, t)) ..
Juha Kiviluoma's avatar
Juha Kiviluoma committed
263
264
265
266
267
268
  - sum(gnu_input(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
  + sum(uFuel(unit, 'main', fuel),
      + v_fuelUse(fuel, unit, f, t)
    )
269
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
270
271
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
272
          * (p_effUnit(effDirect, unit, effDirect, 'slope')${not ts_effUnit(effDirect, unit, effDirect, 'slope', f, t)} + ts_effUnit(effDirect, unit, effDirect, 'slope', f, t))
273
274
275
276
277
278
    )
  + v_online(unit, f, t)${uft_online(unit, f, t)}
    / p_unit(unit, 'unitCount')
    * sum(gnu_output(grid, node, unit),
        + p_gnu(grid, node, unit, 'maxGen')
      )
279
    * (p_effUnit(effDirect, unit, effDirect, 'section')${not ts_effUnit(effDirect, unit, effDirect, 'section', f, t)} + ts_effUnit(effDirect, unit, effDirect, 'section', f, t))
280
;
Juha Kiviluoma's avatar
Juha Kiviluoma committed
281
282
283
284
285
286
287
288
289
* -----------------------------------------------------------------------------
q_conversionSOS2InputIntermediate(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
  - sum(gnu_input(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
  + sum(uFuel(unit, 'main', fuel),
      + v_fuelUse(fuel, unit, f, t)
    )
  =E=
290
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
291
      + v_sos2(unit, f, t, effSelector)
292
293
        * (p_effUnit(effGroup, unit, effSelector, 'rb')${not ts_effUnit(effGroup, unit, effSelector, 'rb', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'rb', f, t))
        * (p_effUnit(effGroup, unit, effSelector, 'slope')${not ts_effUnit(effGroup, unit, effSelector, 'slope', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'slope', f, t))
Juha Kiviluoma's avatar
Juha Kiviluoma committed
294
    )
295
  / p_unit(unit, 'unitCount')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
296
  * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))
297
;
298
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
299
q_conversionSOS2Constraint(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
300
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
301
302
      + v_sos2(unit, f, t, effSelector)
    )
303
  =E=
304
305
  + v_online(unit, f, t)${uft_online(unit, f, t)}
  + 1${not uft_online(unit, f, t)}
306
;
307
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
308
q_conversionSOS2IntermediateOutput(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
309
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
310
      + v_sos2(unit, f, t, effSelector)
311
      * (p_effUnit(effGroup, unit, effSelector, 'rb')${not ts_effUnit(effGroup, unit, effSelector, 'rb', f, t)} + ts_effUnit(effGroup, unit, effSelector, 'rb', f, t))
Juha Kiviluoma's avatar
Juha Kiviluoma committed
312
    )
313
  / p_unit(unit, 'unitCount')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
314
  * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))
315
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
316
317
318
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    )
319
;
320
* -----------------------------------------------------------------------------
321
q_outputRatioFixed(gngnu_fixedOutputRatio(grid, node, grid_, node_, unit), ft(f, t))${uft(unit, f, t)} ..
322
  + v_gen(grid, node, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
323
324
      / p_gnu(grid, node, unit, 'cB')
  =E=
325
  + v_gen(grid_, node_, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
326
327
328
      / p_gnu(grid_, node_, unit, 'cB')
;
* -----------------------------------------------------------------------------
329
330
q_outputRatioConstrained(gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit), ft(f, t))${uft(unit, f, t)} ..
  + v_gen(grid, node, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
331
      / p_gnu(grid, node, unit, 'cB')
332
  =G=
333
  + v_gen(grid_, node_, unit, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
334
      / p_gnu(grid_, node_, unit, 'cB')
335
;
336
* -----------------------------------------------------------------------------
337
q_transferLimit(gn2n(grid, from_node, to_node), ft(f, t)) ..                                    // NOTE! This needs to be thought over, whether it needs to account for transfers in the other direction as well, if permitted
338
  + v_transfer(grid, from_node, to_node, f, t)                                                  // Transfer from this node to the target node
339
  + sum(restypeDirection(restype, resdirection)$(restypeDirectionNode(restype, resdirection, from_node) and restypeDirectionNode(restype, resdirection, to_node)),
340
        + v_resTransfer(restype, resdirection, from_node, to_node, f, t)
341
    )
342
  =L=
343
  + p_gnn(grid, from_node, to_node, 'transferCap')
344
;
345
* -----------------------------------------------------------------------------
346
347
q_stateSlack(gn_stateSlack(grid, node), slack, ft(f, t))$p_gnBoundaryPropertiesForStates(grid, node, slack, 'slackCost') ..
  + v_stateSlack(grid, node, slack, f, t)
348
  =G=
349
350
351
352
353
  + p_slackDirection(slack) * (
      + v_state(grid, node, f, t)
      - p_gnBoundaryPropertiesForStates(grid, node, slack, 'constant')$p_gnBoundaryPropertiesForStates(grid, node, slack, 'useConstant')
      - ts_nodeState(grid, node, slack, f, t)$p_gnBoundaryPropertiesForStates(grid, node, slack, 'useTimeSeries')
    )
354
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
355
* -----------------------------------------------------------------------------
356
q_stateUpwardLimit(gn_state(grid, node), m, ft_dynamic(f, t))$(    sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, nuRescapable(restype, 'resDown', node_output, unit))), 1)  // nodes that have units with endogenous output with possible reserve provision
357
358
                                                        or sum(gn2gnu(grid_, node_input, grid, node, unit) $(sum(restype, nuRescapable(restype, 'resDown', node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
                                                      ) ..
359
360
361
362
363
364
365
366
367
368
369
370
  ( // Utilizable headroom in the state variable
      + p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')   * p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'constant')
      + p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useTimeSeries') * ts_nodeState(grid, node, 'upwardLimit', f, t)
      - v_state(grid, node, f, t)
  )
      * ( // Accounting for the energyStoredPerUnitOfState ...
          + p_gn(grid, node, 'energyStoredPerUnitOfState')
          + p_stepLength(m , f+pf(f,t), t+pt(t))
              * ( // ... and the change in energy losses from the node
                  + p_gn(grid, node, 'selfDischargeLoss')
                  + sum(to_node, p_gnn(grid, node, to_node, 'diffCoeff'))
                )
371
        )
372
373
374
  =G=
  + p_stepLength(m, f+pf(f,t), t+pt(t))
      * ( // Reserve provision from units that have output to this node
375
          + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f+pf(f,t), t+pt(t))},
376
377
              + sum(restype$nuRescapable(restype, 'resDown', node_input, unit), // Downward reserves from units that output energy to the node
                  + v_reserve(restype, 'resDown', node_input, unit, f+pf(f,t), t+pt(t))
378
                      / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
379
380
381
                )
            )
          // Reserve provision from units that take input from this node
382
          + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f+pf(f,t), t+pt(t))},
383
384
              + sum(restype$nuRescapable(restype, 'resDown', node_output, unit), // Downward reserves from units that use the node as energy input
                  + v_reserve(restype, 'resDown', node_output, unit, f+pf(f,t), t+pt(t))
385
                      * sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
386
                )
387
            )
388
389
      // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
      // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (29.11.2016) contains a draft of those terms.
390
        )
391
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
392
* -----------------------------------------------------------------------------
393
q_stateDownwardLimit(gn_state(grid, node), m, ft_dynamic(f, t))$(    sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, nuRescapable(restype, 'resUp', node_output, unit))), 1)  // nodes that have units with endogenous output with possible reserve provision
394
395
                                                          or sum(gn2gnu(grid_, node_input, grid, node, unit) $(sum(restype, nuRescapable(restype, 'resUp', node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
                                                        ) ..
396
397
398
399
400
401
402
403
404
405
406
407
  ( // Utilizable headroom in the state variable
      + v_state(grid, node, f, t)
      - p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')   * p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')
      - p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useTimeSeries') * ts_nodeState(grid, node, 'downwardLimit', f, t)
  )
      * ( // Accounting for the energyStoredPerUnitOfState ...
          + p_gn(grid, node, 'energyStoredPerUnitOfState')
          + p_stepLength(m , f+pf(f,t), t+pt(t))
              * ( // ... and the change in energy losses from the node
                  + p_gn(grid, node, 'selfDischargeLoss')
                  + sum(to_node, p_gnn(grid, node, to_node, 'diffCoeff'))
                )
408
        )
409
410
411
  =G=
  + p_stepLength(m, f+pf(f,t), t+pt(t))
      * ( // Reserve provision from units that have output to this node
412
          + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f+pf(f,t), t+pt(t))},
413
414
              + sum(restype$nuRescapable(restype, 'resUp', node_input, unit), // Upward reserves from units that output energy to the node
                  + v_reserve(restype, 'resUp', node_input, unit, f+pf(f,t), t+pt(t))
415
                      / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
416
417
418
                )
            )
          // Reserve provision from units that take input from this node
419
          + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f+pf(f,t), t+pt(t))},
420
421
              + sum(restype$nuRescapable(restype, 'resUp', node_output, unit), // Upward reserves from units that use the node as energy input
                  + v_reserve(restype, 'resUp', node_output, unit, f+pf(f,t), t+pt(t))
422
                      * sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
423
                )
424
425
426
            )
      // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
      // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (29.11.2016) contains a draft of those terms.
427
        )
428
429
;
* -----------------------------------------------------------------------------
430
q_boundState(gnn_boundState(grid, node, node_), m, ft_dynamic(f, t)) ..
431
    + v_state(grid, node, f, t)   // The state of the first node sets the upper limit of the second
432
433
434
435
436
437
438
    + ( // Downward reserve provided by units in node
*        - sum(nuRescapable(restype, 'resDown', node, unit)${nuft(unit, f+pf(f,t), t+pt(t))},
*            + v_reserve(restype, 'resDown', node, unit, f+pf(f,t), t+pt(t))
*          )
        // Upwards reserve provided by input units
        - sum(nuRescapable(restype, 'resUp', node_input, unit)${sum(grid_, gn2gnu(grid_, node_input, grid, node, unit)) AND uft(unit, f+pf(f,t), t+pt(t))},
            + v_reserve(restype, 'resUp', node_input, unit, f+pf(f,t), t+pt(t))
439
                / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
440
          )
441
442
443
        // Upwards reserve providewd by output units
        - sum(nuRescapable(restype, 'resUp', node_output, unit)${sum(grid_, gn2gnu(grid, node, grid_, node_output, unit)) AND uft(unit, f+pf(f,t), t+pt(t))},
            + v_reserve(restype, 'resUp', node_output, unit, f+pf(f,t), t+pt(t))
444
                / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
445
          )
446
447
        // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
        // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (16.2.2017) contains a draft of those terms.
448
      )
449
        / (p_gn(grid, node, 'energyStoredPerUnitOfState') )                                     // Divide by the stored energy in the node per unit of v_state to obtain same unit as the v_state
450
451
452
453
        * p_stepLength(m, f+pf(f,t), t+pt(t))                                                   // Multiply with time step to obtain change in state over the step
    =G=
    + v_state(grid, node_, f, t)
    + p_gnn(grid, node, node_, 'boundStateOffset')                                              // Affected by the offset parameter
454
455
456
457
458
459
    + (  // Possible reserve by this node
*        + sum(nuRescapable(restype, 'resUp', node_, unit)${nuft(node, unit, f+pf(f,t), t+pt(t))},
*            + v_reserve(restype, 'resUp', node_, unit, f+pf(f,t), t+pt(t))
*          )
        // Possible reserve by input node
        + sum(nuRescapable(restype, 'resDown', node_input, unit)${sum(grid_, gn2gnu(grid_, node_input, grid, node_, unit)) AND uft(unit, f+pf(f,t), t+pt(t))},
460
            + v_reserve(restype, 'resDown', node_input, unit, f+pf(f,t), t+pt(t))               // NOTE! If elec-elec conversion, this might result in weird reserve requirements!
461
                / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
462
          )
463
464
465
        // Possible reserve by output node
        + sum(nuRescapable(restype, 'resDown', node_output, unit)${sum(grid_, gn2gnu(grid, node_, grid_, node_output, unit)) AND uft(unit, f+pf(f,t), t+pt(t))},
            + v_reserve(restype, 'resDown', node_output, unit, f+pf(f,t), t+pt(t))               // NOTE! If elec-elec conversion, this might result in weird reserve requirements!
466
                / sum(effGroup${suft(effGroup, unit, f+pf(f,t), t+pt(t))}, (p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t))} + ts_effGroupUnit(effGroup, unit, 'slope', f+pf(f,t), t+pt(t)))) // Efficiency approximated using maximum slope of effGroup?
467
          )
468
469
        // Here we could have a term for using the energy in the node to offer reserves as well as imports and exports of reserves, but as long as reserves are only
        // considered in power grids that do not have state variables, these terms are not needed. Earlier commit (16.2.2017) contains a draft of those terms.
470
      )
471
        / (p_gn(grid, node_, 'energyStoredPerUnitOfState') )                                    // Divide by the stored energy in the node per unit of v_state to obtain same unit as the v_state
472
        * p_stepLength(m, f+pf(f,t), t+pt(t))                                                   // Multiply with time step to obtain change in state over the step
473
;
474
* -----------------------------------------------------------------------------
475
476
477
478
479
q_boundCyclic(gn_state(grid, node), mf(m, f), t, t_)${  p_gn(grid, node, 'boundCyclic')         // Bind variables if parameter found
                                                        AND tSolveFirst = mSettings(m, 't_start') // For the very first model solve only
                                                        AND mftStart(m, f, t)                   // Use only the starting time step of the model solve
                                                        AND mftLastSteps(m, f, t_)              // Use only the ending time step of the model solve
                                                        }..
480
481
482
483
    + v_state(grid, node, f, t)
    =E=
    + v_state(grid, node, f, t_)
;
484