2b_equations.gms 33.8 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
9
10
11
12
13
14
15
16
17
    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"
    q_conversionDirectInputOutput(effSelector, unit, f, t, effSelector)          "Direct conversion of inputs to outputs (no piece-wise linear part-load efficiencies)"
    q_conversionSOS1InputIntermediate(effSelector, unit, f, t)    "Conversion of input to intermediates restricted by piece-wise linear part-load efficiency represented with SOS1 sections"
    q_conversionSOS1Constraint(effSelector, unit, f, t)   "Constrain piece-wise linear intermediate variables"
    q_conversionSOS1IntermediateOutput(effSelector, unit, f, t)   "Conversion of intermediates to output"
    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"
18
    q_transferLimit(grid, node, node, f, t) "Transfer of energy and capacity reservations are less than the transfer capacity"
19
    q_stateSlack(grid, node, slack, f, t) "Slack variable greater than the difference between v_state and the slack boundary"
20
21
    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"
22
    q_boundState(grid, node, node, mType, f, t) "Node state variables bounded by other nodes"
23
24
25
26
27
28
29
30
;


$setlocal def_penalty 10e6
Scalars
    PENALTY "Default equation violation penalty" / %def_penalty% /
;
Parameters
31
    PENALTY_BALANCE(grid) "Penalty on violating energy balance eq. (/MWh)"
32
    PENALTY_RES(restype, resdirection) "Penalty on violating a reserve (/MW)"
33
;
34
PENALTY_BALANCE(grid) = %def_penalty%;
35
PENALTY_RES(restype, resdirection) =  %def_penalty%;
36
37
38

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

128
* -----------------------------------------------------------------------------
129
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
130
    // The left side of the equation is the change in the state (will be zero if the node doesn't have a state)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
131
132
133
    + p_gn(grid, node, 'energyStoredPerUnitOfState') // 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)$(gn_state(grid, node))                   // The difference between current
            - v_state(grid, node, f+pf(f,t), t+pt(t))$(gn_state(grid, node))     // ... and previous state of the node
134
          )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
135
136
137
    - p_gn(grid, node, 'selfDischargeLoss') // Minus the self discharge out of the model boundaries
        * ( + v_state(grid, node, f, t)$(gn_state(grid, node)) * p_stepLength(m, f+pf(f,t), t+pt(t))                                                // The current state of the node
            $$ifi '%rampSched%' == 'yes' + v_state(grid, node, f+pf(f,t), t+pt(t))$(gn_state(grid, node)) * p_stepLength(m, f+pf(f,t), t+pt(t))     // The previous state of the node
138
          )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
139
        $$ifi '%rampSched%' == 'yes' / 2
140
    =E= // The right side of the equation contains all the changes converted to energy terms
141
    + (
142
        + (
Juha Kiviluoma's avatar
Juha Kiviluoma committed
143
            // Energy diffusion from this node to neighbouring nodes
144
            - sum(to_node$(gnn_state(grid, node, to_node)),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
145
146
147
148
149
150
                + 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
151
152
153
154
            + sum(from_node$(gnn_state(grid, from_node, node)),
                + p_gnn(grid, from_node, node, 'diffCoeff') * (
                    + v_state(grid, from_node, f, t)
                    $$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!
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
                  )
              )
            // 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
Juha Kiviluoma's avatar
Juha Kiviluoma committed
170
            + sum(unit$gnu(grid, node, unit),
171
172
                + v_gen(grid, node, unit, f+pf(f,t), t+pt(t))$gnuft(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)$gnuft(grid, node, unit, f, t)
173
              )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
174
            // Spilling energy out of the endogenous grids in the model
Juha Kiviluoma's avatar
Juha Kiviluoma committed
175
176
            - v_spill(grid, node, f+pf(f,t), t+pt(t))$node_spill(node)
            $$ifi '%rampSched%' == 'yes' - v_spill(grid, node, f+pf(f,t), t+pt(t))$node_spill(node)
177
          )
178
            * p_stepLength(m, f+pf(f,t), t+pt(t))   // Again, multiply by time step to get energy terms
Juha Kiviluoma's avatar
Juha Kiviluoma committed
179
        + ts_absolute_(node, f+pf(f,t), t+pt(t))   // Incoming (positive) and outgoing (negative) absolute value time series
Juha Kiviluoma's avatar
Juha Kiviluoma committed
180
        $$ifi '%rampSched%' == 'yes' + ts_absolute_(node, f, t)
181
182
183
184
185
186
        - ts_energyDemand_(grid, node, f+pf(f,t), t+pt(t))   // Energy demand from the node
        $$ifi '%rampSched%' == 'yes' - ts_energyDemand_(grid, node, f, t)
        + 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+pf(f,t), t+pt(t))
187
      )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
188
    $$ifi '%rampSched%' == 'yes' / 2    // Averaging all the terms on the right side of the equation over the timestep here.
189
190
;
* -----------------------------------------------------------------------------
191
q_resDemand(restypeDirectionNode(restype, resdirection, node), ft(f, t)) ..
192
193
  + sum(nu(node, unit)$nuRescapable(restype, resdirection, node, unit),   // Reserve capable units on this node
        v_reserve(restype, resdirection, node, unit, f, t)$nuft(node, unit, f, t)
194
    )
195
196
197
198
  + sum(gnu_input(grid, node, unit)$nuRescapable(restype, resdirection, node, unit),
        v_reserve(restype, resdirection, node, unit, f, t)$nuft(node, unit, f, t)   // Reserve capable units with input from this node
    )
  + sum(gn2n(grid, from_node, node)$restypeDirectionNode(restype, resdirection, from_node),
199
        (1 - p_gnn(grid, from_node, node, 'transferLoss')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
200
        ) * v_resTransfer(restype, resdirection, from_node, node, f, t)             // Reserves from another node - reduces the need for reserves in the node
201
202
    )
  =G=
203
204
  + ts_reserveDemand_(restype, resdirection, node, f, t)
  - vq_resDemand(restype, resdirection, node, f, t)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
205
206
  + 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)
207
208
209
    )
;
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
210
q_maxDownward(gnuft(grid, node, unit, f, t))${     [unit_minLoad(unit) and p_gnu(grid, node, unit, 'maxGen')]        // generators with min_load
211
                                                  or sum(restype, nuRescapable(restype, 'resDown', node, unit))      // all units with downward reserve provision
Juha Kiviluoma's avatar
Juha Kiviluoma committed
212
                                                  or [p_gnu(grid, node, unit, 'maxCons') and unit_online(unit)]  // consuming units with an online variable
213
                                                }..
214
  + v_gen(grid, node, unit, f, t)                                                                                    // energy generation/consumption
Juha Kiviluoma's avatar
Juha Kiviluoma committed
215
216
217
  + 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
218
        v_reserve(restype, 'resDown', node, unit, f, t)                                                              // (v_reserve can be used only if the unit is capable of providing a particular reserve)
219
    )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
220
221
222
  =G=                                                                                                                // must be greater than minimum load or maximum consumption  (units with min-load and both generation and consumption are not allowed)
  + v_online(unit, f, t) / p_unit(unit, 'unitCount') * p_gnu(grid, node, unit, 'rb00') * p_gnu(grid, node, unit, 'maxGen')$[unit_minload(unit) and p_gnu(grid, node, unit, 'maxGen')]
  + v_gen.lo(grid, node, unit, f, t) * [ (v_online(unit, f, t) / p_unit(unit, 'unitCount'))$unit_minload(unit) + 1$(not unit_minload(unit)) ]         // notice: v_gen.lo for consuming units is negative
223
224
;
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
225
q_maxUpward(gnuft(grid, node, unit, f, t))${      [unit_minLoad(unit) and p_gnu(grid, node, unit, 'maxCons')]    // consuming units with min_load
226
                                                 or sum(restype, nuRescapable(restype, 'resUp', node, unit))         // all units with upward reserve provision
Juha Kiviluoma's avatar
Juha Kiviluoma committed
227
                                                 or [p_gnu(grid, node, unit, 'maxGen') and unit_online(unit)]        // generators with an online variable
228
                                               }..
229
  + v_gen(grid, node, unit, f, t)                                                                                    // energy generation/consumption
Juha Kiviluoma's avatar
Juha Kiviluoma committed
230
231
232
  + 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
233
        v_reserve(restype, 'resUp', node, unit, f, t)                                                                // (v_reserve can be used only if the unit can provide a particular reserve)
234
235
    )
  =L=                                                                         // must be less than available/online capacity
Juha Kiviluoma's avatar
Juha Kiviluoma committed
236
237
  - v_online(unit, f, t) / p_unit(unit, 'unitCount') * p_gnu(grid, node, unit, 'rb00')$[unit_minload(unit) and p_gnu(grid, node, unit, 'maxCons')]
  + v_gen.up(grid, node, unit, f, t) * [ (v_online(unit, f, t) / p_unit(unit, 'unitCount'))$unit_minload(unit) + 1$(not unit_minload(unit)) ]
238
239
;
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
240
241
q_startup(unit_online, ft_dynamic(f, t)) ..
  + v_startup(unit_online, f+pf(f,t), t+pt(t))$uft(unit_online, f+pf(f,t), t+pt(t))
242
  =G=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
243
244
  + v_online(unit_online, f, t)$uft(unit_online, f, t)
  - v_online(unit_online, f+pf(f,t), t+pt(t))$uft(unit_online, f+pf(f,t), t+pt(t))  // This reaches to tFirstSolve when pt = -1
245
;
246
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
247
248
q_bindOnline(unit_online, mftBind(m, f, t)) ..
  + v_online(unit_online, f, t)$uft(unit_online, f, t)
249
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
250
  + v_online(unit_online, f+mft_bind(m,f,t), t+mt_bind(m,t))$uft(unit_online, f+mft_bind(m,f,t), t+mt_bind(m,t))
251
;
252
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
253
254
255
256
257
258
259
q_conversionDirectInputOutput(sufts(effGroup, unit, f, t, effSelector))$effDirect(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)
    )
260
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
  + p_effUnit(effSelector, unit, 'section00')$suft('directOn', unit, f, t)
      * v_online(unit, f, t) / p_unit(unit, 'unitCount')
      * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))  // for some unit types (e.g. backpressure and extraction) only single v_online and therefore single 'section' should exist
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
          * p_effUnit(effSelector, unit, 'slope')
*          * [ + 1
*$(not unit_withConstrainedOutputRatio(unit_noSlope) or nu(node,unit_noSlope))   // not a backpressure or extraction unit, expect for the primary grid (where cV has to be 1)
*              + p_gnu(grid, node, unit, 'cV')$(unit_withConstrainedOutputRatio(unit) and not nu(node, unit)) // for secondary outputs with cV
*            ]
    );
* -----------------------------------------------------------------------------
q_conversionSOS1InputIntermediate(suft(effGroup, unit, f, t))$effSlope(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)
279
    )
Juha Kiviluoma's avatar
Juha Kiviluoma committed
280
281
282
283
  =E=
  + p_unit(unit, 'section00')
      * v_online(unit, f, t) / p_unit(unit, 'unitCount')
      * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))  // for some unit types (e.g. backpressure and extraction) only single v_online and therefore single 'section' should exist
284
  + sum(effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
285
286
287
288
289
290
291
292
      + v_sos1(effGroup, unit, f, t, effSelector)
          * p_effUnit(effSelector, unit, 'slope')
*          * [ + 1$(not unit_withConstrainedOutputRatio(unit))   // not a backpressure or extraction unit, expect for the primary grid (where cV has to be 1)
*              + p_gnu(grid, node, unit, 'cV')$(unit_withConstrainedOutputRatio(unit) and not nu(node, unit)) // for secondary outputs with cV
*            ]
    );
* -----------------------------------------------------------------------------
q_conversionSOS1Constraint(suft(effGroup, unit, f, t))$effSlope(effGroup) ..
293
  + sum(effGroupSelectorUnit(effGroup, unit, effSelector), v_sos1(effGroup, unit, f, t, effSelector))
Juha Kiviluoma's avatar
Juha Kiviluoma committed
294
295
296
297
298
299
*  + sum(effSelector_$effSelectorFirstSlope(effSelector, effSelector_), v_sos1(effSelector_, unit, f, t))
  =L=
  + sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))
;
* -----------------------------------------------------------------------------
q_conversionSOS1IntermediateOutput(suft(effGroup, unit, f, t))$effSlope(effGroup) ..
300
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector), v_sos1(effGroup, unit, f, t, effSelector))
Juha Kiviluoma's avatar
Juha Kiviluoma committed
301
302
303
304
305
306
307
308
309
310
311
312
313
  =E=
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
    );
* -----------------------------------------------------------------------------
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=
314
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
315
316
317
318
      + v_sos2(unit, f, t, effSelector) * p_effUnit(effSelector, unit, 'rb') * p_effUnit(effSelector, unit, 'slope')
    )
  / p_unit(unit, 'unitCount')
  * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))
319
;
320
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
321
q_conversionSOS2Constraint(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
322
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
323
324
      + v_sos2(unit, f, t, effSelector)
    )
325
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
326
  + v_online(unit, f, t)
327
;
328
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
329
q_conversionSOS2IntermediateOutput(suft(effGroup, unit, f, t))$effLambda(effGroup) ..
330
  + sum(effSelector$effGroupSelectorUnit(effGroup, unit, effSelector),
Juha Kiviluoma's avatar
Juha Kiviluoma committed
331
332
333
334
335
      + v_sos2(unit, f, t, effSelector)
      * p_effUnit(effSelector, unit, 'rb')
    )
  / p_unit(unit, 'unitCount')
  * sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'maxGen'))
336
  =E=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
337
338
339
340
341
342
  + sum(gnu_output(grid, node, unit),
      + v_gen(grid, node, unit, f, t)
*      + sum(gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit)$unit_withConstrainedOutputRatio(unit),
*          + p_gnu(grid_, node_, unit, 'cv') * v_gen(grid_, node_, unit, f, t)
*        )
    )
343
;
344
* -----------------------------------------------------------------------------
Juha Kiviluoma's avatar
Juha Kiviluoma committed
345
346
347
348
349
350
351
352
353
354
355
q_outputRatioFixed(gngnu_fixedOutputRatio(grid, node, grid_, node_, unit), ft(f, t)) ..
  + v_gen(grid, node, unit, f, t)$uft(unit, f, t)
      / p_gnu(grid, node, unit, 'cB')
  =E=
  + v_gen(grid_, node_, unit, f, t)$uft(unit, f, t)
      / p_gnu(grid_, node_, unit, 'cB')
;
* -----------------------------------------------------------------------------
q_outputRatioConstrained(gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit), ft(f, t)) ..
  + v_gen(grid, node, unit, f, t)$uft(unit, f, t)
      / p_gnu(grid, node, unit, 'cB')
356
  =G=
Juha Kiviluoma's avatar
Juha Kiviluoma committed
357
358
  + v_gen(grid_, node_, unit, f, t)$uft(unit, f, t)
      / p_gnu(grid_, node_, unit, 'cB')
359
;
360
* -----------------------------------------------------------------------------
361
362
363
q_transferLimit(gn2n(grid, from_node, to_node), ft(f, t)) ..                                    // NOTE! Currently generates identical equations for both directions unnecessarily
  + v_transfer(grid, from_node, to_node, f, t)                                                  // Transfer from this node to the target node
  + v_transfer(grid, to_node, from_node, f, t)                                                  // Transfer from the target node to this one
364
  + sum(restypeDirection(restype, resdirection)$(restypeDirectionNode(restype, resdirection, from_node) and restypeDirectionNode(restype, resdirection, to_node)),
365
        + v_resTransfer(restype, resdirection, from_node, to_node, f, t)
366
        + v_resTransfer(restype, resdirection, to_node, from_node, f, t)
367
    )
368
  =L=
369
  + p_gnn(grid, from_node, to_node, 'transferCap')
370
;
371
* -----------------------------------------------------------------------------
372
373
374
375
376
377
378
q_stateSlack(gn_stateSlack(grid, node), slack, ft(f, t)) ..
  + v_stateSlack(grid, node, slack, f, t) * p_slackDirection(slack)
  =G=
  + 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')
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
379
* -----------------------------------------------------------------------------
380
381
382
q_stateUpwardLimit(gn_state(grid, node), m, ft(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
                                                        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
                                                      ) ..
383
384
  + v_state(grid, node, f, t)
  + p_stepLength(m, f, t) * (
385
386
387
388
      + sum(gn2gnu(grid_, node_input, grid, node, unit),
          + sum(restype$nuRescapable(restype, 'resDown', node_input, unit), // downward reserves from units that output energy from the node
              + v_reserve(restype, 'resDown', node_input, unit, f, t)
                  * p_unit(unit, 'eff00')                                                        // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
389
390
            )
        )
391
392
393
394
      + sum(gn2gnu(grid, node, grid_, node_output, unit),
          + sum(restype$nuRescapable(restype, 'resDown', node_output, unit), // downward reserves from units that use the node as an input energy
              + v_reserve(restype, 'resDown', node_output, unit, f, t)
                  / p_unit(unit, 'eff00')                                                        // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
395
396
            )
        )
397
398
      // 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.
399
400
401
402
403
    )
  =L=
  + 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)
;
Juha Kiviluoma's avatar
Cleanup    
Juha Kiviluoma committed
404
* -----------------------------------------------------------------------------
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
q_stateDownwardLimit(gn_state(grid, node), m, ft(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
                                                          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
                                                        ) ..
  + v_state(grid, node, f, t)
  + p_stepLength(m, f, t) * (
      + sum(gn2gnu(grid_, node_input, grid, node, unit),
          + sum(restype$nuRescapable(restype, 'resUp', node_input, unit), // downward reserves from units that output energy from the node
              + v_reserve(restype, 'resUp', node_input, unit, f, t)
                  * p_unit(unit, 'eff00')                                                        // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
            )
        )
      + sum(gn2gnu(grid, node, grid_, node_output, unit),
          + sum(restype$nuRescapable(restype, 'resUp', node_output, unit), // downward reserves from units that use the node as an input energy
              + v_reserve(restype, 'resUp', node_output, unit, f, t)
                  / p_unit(unit, 'eff00')                                                        // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
            )
        )
      // 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.
    )
  =G=
  + 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)
428
429
;
* -----------------------------------------------------------------------------
430
431
432
433
434
435
q_boundState(gnn_boundState(grid, node, node_), m, ft(f, t)) ..
    + v_state(grid, node, f, t)   // The state of the first node sets the upper limit of the second
    + (
        - sum(nuRescapable(restype, 'resDown', node, unit),
            + v_reserve(restype, 'resDown', node, unit, f+pf(f,t), t+pt(t))                     // Downward reserve provided by units in node
          )
436
        - sum(nuRescapable(restype, 'resUp', node_input, unit)${ sum(grid_, gnu_input(grid_, node_input, unit)) and gnu_output(grid, node, unit) },
437
            + v_reserve(restype, 'resUp', node_input, unit, f+pf(f,t), t+pt(t))                 // Upwards reserve provided by input units
438
                * p_unit(unit, 'eff00')                                                       // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
439
          )
440
441
        - sum(gn2n(grid, from_node, node)${ sum(restypeDirection(restype, resdirection), restypeDirectionNode(restype, resdirection, node)) },
            + sum(restype${ restypeDirectionNode(restype, 'resDown', node) },
442
                + v_resTransfer(restype, 'resDown', from_node, node, f+pf(f,t),t+pt(t))    // Reserved energy to be exported to from_node
443
444
              )
          )
445
446
        - sum(gn2n(grid, node, to_node)${ sum(restypeDirection(restype, resdirection), restypeDirectionNode(restype, resdirection, to_node)) },
            + sum(restype${ restypeDirectionNode(restype, 'resUp', to_node) },
447
                + v_resTransfer(restype, 'resUp', node, to_node, f+pf(f,t),t+pt(t))        // Reserved energy to be exported to to_node
448
449
450
              )
          )
      )
451
        / (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
452
453
454
455
456
457
458
459
        * 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
    + (
        + sum(nuRescapable(restype, 'resUp', node_, unit),                                       // Possible reserve by this node
            + v_reserve(restype, 'resUp', node_, unit, f+pf(f,t), t+pt(t))
          )
460
        + sum(nuRescapable(restype, 'resDown', node_input, unit)${ sum(grid_, gnu_input(grid_, node_input, unit)) and gnu_output(grid, node_, unit) }, // Possible reserve by input node
461
            + 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!
462
                * p_unit(unit, 'eff00')                                                       // NOTE! This is not correct, slope will change depending on the operating point. Maybe use maximum slope...
463
          )
464
465
        + sum(gn2n(grid, from_node, node)${ sum(restypeDirection(restype, resdirection), restypeDirectionNode(restype, resdirection, node)) },
            + sum(restype${ restypeDirectionNode(restype, 'resUp', node) },
466
                + (1 - p_gnn(grid, from_node, node, 'transferLoss')) * v_resTransfer(restype, 'resUp', from_node, node, f+pf(f,t), t+pt(t)) // Reserved transfer capacity for importing energy from from_node
467
468
              )
          )
469
470
        + sum(gn2n(grid, node, to_node)${ sum(restypeDirection(restype, resdirection), restypeDirectionNode(restype, resdirection, to_node)) }, // Reserved transfer capacities from this node to another
            + sum(restype${ restypeDirectionNode(restype, 'resDown', to_node) },
471
                + (1 - p_gnn(grid, node, to_node, 'transferLoss')) * v_resTransfer(restype, 'resDown', node, to_node, f+pf(f,t), t+pt(t)) // Reserved transfer capacity for importing energy from to_node
472
473
474
              )
          )
      )
475
        / (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
476
        * p_stepLength(m, f+pf(f,t), t+pt(t))                                                   // Multiply with time step to obtain change in state over the step
477
478
;