2d_constraints.gms 175 KB
Newer Older
2001
    + v_gen(grid, node, unit, s, f, t)
2002
2003
2004
2005

    =E=

    // Sum over heat rate segments
2006
2007
2008
    + sum(hr$(p_unit(unit, hr)),
        + v_gen_inc(grid, node, unit, hr, s, f, t)
        )// END sum (hr)
2009
2010
;

2011
* --- Incremental Heat Rate Conversion ------------------------------------------
2012

2013
2014
2015
2016
2017
q_conversionIncHRBounds(gn(grid, node), s_active(s), hr, suft(effIncHR(effGroup), unit, f, t))
    ${  sft(s, f, t)
        and gnu_output(grid, node, unit)
        and p_unit(unit, hr)
        } ..
2018
2019
2020
2021
2022
2023
2024
2025
2026

    + v_gen_inc(grid, node, unit, hr, s, f, t)

    =L=

    + (
        + sum(hrop${ord(hrop) = ord(hr)}, p_unit(unit, hrop))
        - sum(hrop${ord(hrop) = ord(hr) - 1}, p_unit(unit, hrop))
        )
2027
        *  p_gnu(grid, node, unit, 'unitSizeGen')
Topi Rasku's avatar
Topi Rasku committed
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
        * [ // Unit online state
            + v_online_MIP(unit, s, f+df_central(f,t), t)${uft_onlineMIP(unit, f, t)}

            // Run-up and shutdown phase efficiency correction
            // Run-up 'online state'
            + sum(unitStarttype(unit, starttype)${uft_startupTrajectory(unit, f, t)},
                + sum(runUpCounter(unit, counter)${t_active(t+dt_trajectory(counter))}, // Sum over the run-up intervals
                    + [
                        + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                            ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                        + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                            ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                        ]
                        * p_uCounter_runUpMin(unit, counter)
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_runUp using minload
                    ) // END sum(runUpCounter)
                ) // END sum(unitStarttype)
            // Shutdown 'online state'
            + sum(shutdownCounter(unit, counter)${  t_active(t+dt_trajectory(counter))
                                                    and uft_shutdownTrajectory(unit, f, t)
                                                    }, // Sum over the shutdown intervals
                + [
                    + v_shutdown_LP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                        ${  uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                    + v_shutdown_MIP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                        ${  uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                    ]
                    * p_uCounter_shutdownMin(unit, counter)
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_shutdown using minload
                ) // END sum(shutdownCounter)
            ] // END * p_gnu('unitSizeGen')
2059
2060
;

2061
2062
* --- Incremental Heat Rate Conversion (First Segments First) -----------------

2063
q_conversionIncHR_help1(gn(grid, node), s_active(s), hr, suft(effIncHR(effGroup), unit_incHRAdditionalConstraints(unit), f, t))
2064
2065
2066
2067
2068
    ${  sft(s, f, t)
        and gnu_output(grid, node, unit)
        and p_unit(unit, hr)
        and p_unit(unit, hr+1)
        } ..
2069
2070
2071
2072
2073
2074
2075

    + v_gen_inc(grid, node, unit, hr, s, f, t)
    - (
        + sum(hrop${ord(hrop) = ord(hr)}, p_unit(unit, hrop))
        - sum(hrop${ord(hrop) = ord(hr) - 1}, p_unit(unit, hrop))
        )
        *  p_gnu(grid, node, unit, 'unitSizeGen')
Topi Rasku's avatar
Topi Rasku committed
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
        * [ // Unit online state
            + v_online_MIP(unit, s, f+df_central(f,t), t)${uft_onlineMIP(unit, f, t)}

            // Run-up and shutdown phase efficiency correction
            // Run-up 'online state'
            + sum(unitStarttype(unit, starttype)${uft_startupTrajectory(unit, f, t)},
                + sum(runUpCounter(unit, counter)${t_active(t+dt_trajectory(counter))}, // Sum over the run-up intervals
                    + [
                        + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                            ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                        + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                            ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                        ]
                        * p_uCounter_runUpMin(unit, counter)
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_runUp using minload
                    ) // END sum(runUpCounter)
                ) // END sum(unitStarttype)
            // Shutdown 'online state'
            + sum(shutdownCounter(unit, counter)${  t_active(t+dt_trajectory(counter))
                                                    and uft_shutdownTrajectory(unit, f, t)
                                                    }, // Sum over the shutdown intervals
                + [
                    + v_shutdown_LP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                        ${  uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                    + v_shutdown_MIP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                        ${  uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
                    ]
                    * p_uCounter_shutdownMin(unit, counter)
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_shutdown using minload
                ) // END sum(shutdownCounter)
            ] // END * p_gnu('unitSizeGen')
2107
2108
2109

    =G=

2110
    - BIG_M
2111
2112
2113
        * (1 - v_help_inc(grid, node, unit, hr, s, f, t))
;

2114
q_conversionIncHR_help2(gn(grid, node), s_active(s), hr, suft(effIncHR(effGroup), unit_incHRAdditionalConstraints(unit), f, t))
2115
2116
2117
2118
2119
    ${  sft(s, f, t)
        and gnu_output(grid, node, unit)
        and p_unit(unit, hr)
        and p_unit(unit, hr-1)
        } ..
2120
2121
2122
2123
2124

    + v_gen_inc(grid, node, unit, hr, s, f, t)

    =L=

2125
    + BIG_M
2126
2127
        * v_help_inc(grid, node, unit, hr-1, s, f, t)
;
2128
2129
2130

* --- SOS2 Efficiency Approximation -------------------------------------------

2131
2132
2133
q_conversionSOS2InputIntermediate(s_active(s), suft(effLambda(effGroup), unit, f, t))
    ${  sft(s, f, t)
        }..
2134
2135

    // Sum over endogenous energy inputs
2136
    - sum(gnu_input(grid, node, unit)${not p_gnu(grid, node, unit, 'doNotOutput')},
2137
        + v_gen(grid, node, unit, s, f, t)
2138
2139
2140
2141
        ) // END sum(gnu_input)

    // Sum over fuel energy inputs
    + sum(uFuel(unit, 'main', fuel),
2142
        + v_fuelUse(fuel, unit, s, f, t)
2143
2144
        ) // END sum(uFuel)

2145
    =E=
2146
2147
2148
2149
2150

    // Sum over the endogenous outputs of the unit
    + sum(gnu_output(grid, node, unit), p_gnu(grid, node, unit, 'unitSizeGen'))
        * [
            // Consumption of generation
2151
            + sum(effGroupSelectorUnit(effGroup, unit, effSelector),
2152
                + v_sos2(unit, s, f, t, effSelector)
2153
2154
2155
2156
2157
2158
2159
2160
2161
                    * [ // Operation points convert the v_sos2 variables into share of capacity used for generation
                        + p_effUnit(effGroup, unit, effSelector, 'op')${not ts_effUnit(effGroup, unit, effSelector, 'op', f, t)}
                        + ts_effUnit(effGroup, unit, effSelector, 'op', f, t)
                        ] // END * v_sos2
                    * [ // Heat rate
                        + p_effUnit(effGroup, unit, effSelector, 'slope')${not ts_effUnit(effGroup, unit, effSelector, 'slope', f, t)}
                        + ts_effUnit(effGroup, unit, effSelector, 'slope', f, t)
                        ] // END * v_sos2
                ) // END sum(effSelector)
2162
           ]
2163
;
2164
2165
2166

* --- SOS 2 Efficiency Approximation Online Variables -------------------------

2167
2168
2169
q_conversionSOS2Constraint(s_active(s), suft(effLambda(effGroup), unit, f, t))
    ${  sft(s, f, t)
        }..
2170
2171

    // Total value of the v_sos2 equals the number of online units
2172
    + sum(effGroupSelectorUnit(effGroup, unit, effSelector),
2173
        + v_sos2(unit, s, f, t, effSelector)
2174
2175
2176
2177
2178
        ) // END sum(effSelector)

    =E=

    // Number of units online
2179
    + v_online_MIP(unit, s, f+df_central(f,t), t)${uft_onlineMIP(unit, f, t)}
2180
2181
2182
2183
2184

    // Run-up and shutdown phase efficiency approximation
    // Run-up 'online state'
    + sum(unitStarttype(unit, starttype)${uft_startupTrajectory(unit, f, t)},
        + sum(runUpCounter(unit, counter)${t_active(t+dt_trajectory(counter))}, // Sum over the run-up intervals
2185
2186
            + [
                + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
2187
                    ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
2188
                + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
2189
                    ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
2190
                ]
2191
                * p_uCounter_runUpMin(unit, counter)
2192
2193
2194
2195
2196
                / p_unit(unit, 'op00') // Scaling the p_uCounter_runUp using minload
            ) // END sum(runUpCounter)
        ) // END sum(unitStarttype)
    // Shutdown 'online state'
    + sum(shutdownCounter(unit, counter)${t_active(t+dt_trajectory(counter)) and uft_shutdownTrajectory(unit, f, t)}, // Sum over the shutdown intervals
2197
2198
2199
2200
2201
2202
        + [
            + v_shutdown_LP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
            + v_shutdown_MIP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
                ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
            ]
2203
            * p_uCounter_shutdownMin(unit, counter)
2204
2205
            / p_unit(unit, 'op00') // Scaling the p_uCounter_shutdown using minload
        ) // END sum(shutdownCounter)
2206
;
2207
2208
2209

* --- SOS 2 Efficiency Approximation Output Generation ------------------------

2210
2211
2212
q_conversionSOS2IntermediateOutput(s_active(s), suft(effLambda(effGroup), unit, f, t))
    ${  sft(s, f, t)
        }..
2213
2214
2215
2216

    // Endogenous energy output
    + sum(gnu_output(grid, node, unit),
        + p_gnu(grid, node, unit, 'unitSizeGen')
2217
      ) // END sum(gnu_output)
2218
        * sum(effGroupSelectorUnit(effGroup, unit, effSelector),
2219
            + v_sos2(unit, s, f, t, effSelector)
2220
2221
2222
            * [ // Operation points convert v_sos2 into share of capacity used for generation
                + p_effUnit(effGroup, unit, effSelector, 'op')${not ts_effUnit(effGroup, unit, effSelector, 'op', f, t)}
                + ts_effUnit(effGroup, unit, effSelector, 'op', f, t)
2223
2224
              ] // END * v_sos2
          ) // END sum(effSelector)
2225
2226
2227
2228
2229

    =E=

    // Energy output into v_gen
    + sum(gnu_output(grid, node, unit),
2230
        + v_gen(grid, node, unit, s, f, t)
2231
        ) // END sum(gnu_output)
Juha Kiviluoma's avatar
Juha Kiviluoma committed
2232
;
2233

2234
2235
* --- Fuel Use Limitation -----------------------------------------------------

2236
2237
2238
2239
2240
q_fuelUseLimit(s_active(s), fuel, uft(unit_fuel(unit), f, t))
    ${  sft(s, f, t)
        and uFuel(unit, 'main', fuel)
        and p_uFuel(unit, 'main', fuel, 'maxFuelFraction')
        } ..
2241
2242

    // Use of the limited fuel
2243
    + v_fuelUse(fuel, unit, s, f, t)
2244
2245
2246
2247
2248
2249

    =L=

    // Sum over fuel energy inputs multiplied by the maximum fraction
    + p_uFuel(unit, 'main', fuel, 'maxFuelFraction')
        * sum(uFuel(unit, 'main', fuel_),
2250
            + v_fuelUse(fuel_, unit, s, f, t)
2251
2252
2253
            ) // END sum(uFuel)
;

2254
2255
* --- Total Transfer Limits ---------------------------------------------------

2256
q_transfer(gn2n_directional(grid, node, node_), sft(s, f, t)) ..
2257
2258

    // Rightward + Leftward
2259
2260
    + v_transferRightward(grid, node, node_, s, f, t)
    - v_transferLeftward(grid, node, node_, s, f, t)
2261
2262
2263
2264

    =E=

    // = Total Transfer
2265
    + v_transfer(grid, node, node_, s, f, t)
2266
2267
2268
2269
;

* --- Rightward Transfer Limits -----------------------------------------------

2270
2271
2272
2273
q_transferRightwardLimit(gn2n_directional(grid, node, node_), sft(s, f, t))
    ${  p_gnn(grid, node, node_, 'transferCapInvLimit')
        } ..

2274
    // Rightward transfer
2275
    + v_transferRightward(grid, node, node_, s, f, t)
2276
2277
2278
2279
2280
2281
2282
2283

    =L=

    // Existing transfer capacity
    + p_gnn(grid, node, node_, 'transferCap')

    // Investments into additional transfer capacity
    + sum(t_invest(t_)$(ord(t_)<=ord(t)),
Niina Helistö's avatar
Niina Helistö committed
2284
2285
2286
        + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
        + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
            * p_gnn(grid, node, node_, 'unitSize')
2287
2288
2289
2290
2291
        ) // END sum(t_invest)
;

* --- Leftward Transfer Limits ------------------------------------------------

2292
2293
2294
q_transferLeftwardLimit(gn2n_directional(grid, node, node_), sft(s, f, t))
    ${  p_gnn(grid, node, node_, 'transferCapInvLimit')
        } ..
2295
2296

    // Leftward transfer
2297
    + v_transferLeftward(grid, node, node_, s, f, t)
2298
2299
2300
2301
2302
2303
2304
2305

    =L=

    // Existing transfer capacity
    + p_gnn(grid, node_, node, 'transferCap')

    // Investments into additional transfer capacity
    + sum(t_invest(t_)${ord(t_)<=ord(t)},
Niina Helistö's avatar
Niina Helistö committed
2306
2307
2308
        + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
        + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
            * p_gnn(grid, node, node_, 'unitSize')
2309
2310
2311
2312
2313
        ) // END sum(t_invest)
;

* --- Rightward Reserve Transfer Limits ---------------------------------------

2314
q_resTransferLimitRightward(gn2n_directional(grid, node, node_), sft(s, f, t))
2315
2316
    ${  sum(restypeDirection(restype, 'up'), restypeDirectionGridNodeNode(restype, 'up', grid, node, node_))
        or sum(restypeDirection(restype, 'down'), restypeDirectionGridNodeNode(restype, 'down', grid, node_, node))
2317
2318
        or p_gnn(grid, node, node_, 'transferCapInvLimit')
        } ..
2319
2320

    // Transfer from node
2321
    + v_transfer(grid, node, node_, s, f, t)
2322
2323

    // Reserved transfer capacities from node
2324
2325
    + sum(restypeDirection(restype, 'up')${restypeDirectionGridNodeNode(restype, 'up', grid, node_, node)},
        + v_resTransferRightward(restype, 'up', grid, node, node_, s, f+df_reserves(grid, node_, restype, f, t), t)
2326
        ) // END sum(restypeDirection)
2327
2328
    + sum(restypeDirection(restype, 'down')${restypeDirectionGridNodeNode(restype, 'down', grid, node, node_)},
        + v_resTransferLeftward(restype, 'down', grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
2329
2330
2331
2332
2333
2334
2335
2336
2337
        ) // END sum(restypeDirection)

    =L=

    // Existing transfer capacity
    + p_gnn(grid, node, node_, 'transferCap')

    // Investments into additional transfer capacity
    + sum(t_invest(t_)${ord(t_)<=ord(t)},
Niina Helistö's avatar
Niina Helistö committed
2338
2339
2340
        + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
        + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
            * p_gnn(grid, node, node_, 'unitSize')
2341
2342
2343
2344
2345
        ) // END sum(t_invest)
;

* --- Leftward Reserve Transfer Limits ----------------------------------------

2346
q_resTransferLimitLeftward(gn2n_directional(grid, node, node_), sft(s, f, t))
2347
2348
    ${  sum(restypeDirection(restype, 'up'), restypeDirectionGridNodeNode(restype, 'up', grid, node_, node))
        or sum(restypeDirection(restype, 'down'), restypeDirectionGridNodeNode(restype, 'down', grid, node, node_))
2349
2350
        or p_gnn(grid, node, node_, 'transferCapInvLimit')
        } ..
2351
2352

    // Transfer from node
2353
    + v_transfer(grid, node, node_, s, f, t)
2354
2355

    // Reserved transfer capacities from node
2356
2357
    - sum(restypeDirection(restype, 'up')${restypeDirectionGridNodeNode(restype, 'up', grid, node, node_)},
        + v_resTransferLeftward(restype, 'up', grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t)
2358
        ) // END sum(restypeDirection)
2359
2360
    - sum(restypeDirection(restype, 'down')${restypeDirectionGridNodeNode(restype, 'down', grid, node_, node)},
        + v_resTransferRightward(restype, 'down', grid, node, node_, s, f+df_reserves(grid, node_, restype, f, t), t)
2361
        ) // END sum(restypeDirection)
2362

2363
  =G=
2364

2365
2366
2367
2368
2369
    // Existing transfer capacity
    - p_gnn(grid, node_, node, 'transferCap')

    // Investments into additional transfer capacity
    - sum(t_invest(t_)${ord(t_)<=ord(t)},
Niina Helistö's avatar
Niina Helistö committed
2370
2371
2372
        + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
        + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
            * p_gnn(grid, node, node_, 'unitSize')
2373
        ) // END sum(t_invest)
2374
;
2375

2376
2377
* --- Rightward Reserve Provision Limits ----------------------------------------

2378
2379
2380
q_reserveProvisionRightward(restypeDirectionGridNodeNode(restype, up_down, grid, node, node_), sft(s, f, t))
    ${  p_gnn(grid, node, node_, 'transferCapInvLimit')
        and gn2n_directional(grid, node, node_)
2381
2382
2383
2384
        and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
                        ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
                 or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
                        ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
2385
2386
                 ]
        } ..
2387

2388
    + v_resTransferRightward(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node_, restype, f, t), t) // df_reserves based on the receiving node
2389
2390
2391

    =L=

2392
    + p_gnnReserves(grid, node, node_, restype, up_down)
2393
        * [
2394
2395
2396
2397
2398
2399
2400
2401
2402
            // Existing transfer capacity
            + p_gnn(grid, node, node_, 'transferCap')

            // Investments into additional transfer capacity
            + sum(t_invest(t_)${ord(t_)<=ord(t)},
                + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
                + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
                    * p_gnn(grid, node, node_, 'unitSize')
                ) // END sum(t_invest)
2403
2404
2405
2406
2407
            ]
;

* --- Leftward Reserve Provision Limits ----------------------------------------

2408
2409
2410
q_reserveProvisionLeftward(restypeDirectionGridNodeNode(restype, up_down, grid, node_, node), sft(s, f, t))
    ${  p_gnn(grid, node, node_, 'transferCapInvLimit')
        and gn2n_directional(grid, node, node_)
2411
2412
2413
2414
2415
        and not [   sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
                        ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
                 or sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node_, group),
                        ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
                 ]
2416
        } ..
2417

2418
    + v_resTransferLeftward(restype, up_down, grid, node, node_, s, f+df_reserves(grid, node, restype, f, t), t) // df_reserves based on the receiving node
2419
2420
2421

    =L=

2422
    + p_gnnReserves(grid, node_, node, restype, up_down)
2423
        * [
2424
2425
2426
2427
2428
2429
2430
2431
2432
            // Existing transfer capacity
            + p_gnn(grid, node_, node, 'transferCap')

            // Investments into additional transfer capacity
            + sum(t_invest(t_)${ord(t_)<=ord(t)},
                + v_investTransfer_LP(grid, node, node_, t_)${gn2n_directional_investLP(grid, node, node_)}
                + v_investTransfer_MIP(grid, node, node_, t_)${gn2n_directional_investMIP(grid, node, node_)}
                    * p_gnn(grid, node, node_, 'unitSize')
                ) // END sum(t_invest)
2433
2434
2435
            ]
;

2436
2437
* --- State Variable Slack ----------------------------------------------------

2438
2439
2440
2441
q_stateSlack(gn_stateSlack(grid, node), slack, sft(s, f, t))
    ${  p_gnBoundaryPropertiesForStates(grid, node, slack, 'slackCost')
        and not df_central(f, t)
        } ..
2442
2443

    // Slack value
2444
    + v_stateSlack(grid, node, slack, s, f, t)
2445

2446
    =G=
2447
2448
2449
2450

    // Slack limits
    + p_slackDirection(slack)
        * [
2451
            + v_state(grid, node, s, f, t)
2452
            - p_gnBoundaryPropertiesForStates(grid, node, slack, 'constant')$p_gnBoundaryPropertiesForStates(grid, node, slack, 'useConstant')
2453
            - ts_node_(grid, node, slack, f, t, s)${ p_gnBoundaryPropertiesForStates(grid, node, slack, 'useTimeSeries') }
2454
            ] // END * p_slackDirection
2455
;
2456
2457
2458

* --- Upwards Limit for State Variables ---------------------------------------

2459
q_stateUpwardLimit(gn_state(grid, node), msft(m, s, f, t))
2460
2461
    ${  sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, gnuRescapable(restype, 'down', grid_, 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, gnuRescapable(restype, 'down', grid_, node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
2462
2463
        or sum(gnu(grid, node, unit), p_gnu(grid, node, unit, 'upperLimitCapacityRatio'))  // or nodes that have units whose invested capacity limits their state
        } ..
2464
2465
2466
2467
2468

    // Utilizable headroom in the state variable
    + [
        // Upper boundary of the variable
        + p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'constant')${p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useConstant')}
2469
        + ts_node_(grid, node, 'upwardLimit', f, t, s)${ p_gnBoundaryPropertiesForStates(grid, node, 'upwardLimit', 'useTimeseries') }
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481

        // Investments
        + sum(gnu(grid, node, unit),
            + p_gnu(grid, node, unit, 'upperLimitCapacityRatio')
                * p_gnu(grid, node, unit, 'unitSizeTot')
                * sum(t_invest(t_)${ord(t_)<=ord(t)},
                    + v_invest_LP(unit, t_)${unit_investLP(unit)}
                    + v_invest_MIP(unit, t_)${unit_investMIP(unit)}
                    ) // END sum(t_invest)
            ) // END sum(gnu)

        // Current state of the variable
2482
        - v_state(grid, node, s, f+df_central(f,t), t)
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
        ] // END Headroom
        * [
            // Conversion to energy
            + p_gn(grid, node, 'energyStoredPerUnitOfState')
            // Accounting for losses from the node
            + p_stepLength(m, f, t)
                * [
                    + p_gn(grid, node, 'selfDischargeLoss')
                    + sum(gnn_state(grid, node, to_node),
                        + p_gnn(grid, node, to_node, 'diffCoeff')
                        ) // END sum(to_node)
                    ]
            ] // END * Headroom

    =G=

    // Convert reserve power to energy
    + p_stepLength(m, f, t)
        * [
            // Reserve provision from units that output to this node
            + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f, t)},
                // Downward reserves from units that output energy to the node
2505
2506
                + sum(gnuRescapable(restype, 'down', grid_, node_input, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid_, node_input, restype, 'reserve_length') },
                    + v_reserve(restype, 'down', grid_, node_input, unit, s, f+df_reserves(grid_, node_input, restype, f, t), t)
2507
                        / sum(suft(effGroup, unit, f, t),
2508
2509
2510
2511
2512
2513
2514
2515
2516
                            + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                            + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                            ) // END sum(effGroup)
                    ) // END sum(restype)
                ) // END sum(gn2gnu)

            // Reserve provision from units that take input from this node
            + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f, t)},
                // Downward reserves from units that use the node as energy input
2517
2518
                + sum(gnuRescapable(restype, 'down', grid_, node_output, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid_, node_output, restype, 'reserve_length') },
                    + v_reserve(restype, 'down', grid_, node_output, unit, s, f+df_reserves(grid_, node_output, restype, f, t), t)
2519
                        * sum(suft(effGroup, unit, f, t),
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
                            + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                            + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                            ) // END sum(effGroup)
                    ) // END sum(restype)
                ) // END sum(gn2gnu)

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

            ] // END * p_stepLength
2530
;
2531
2532
2533

* --- Downwards Limit for State Variables -------------------------------------

2534
q_stateDownwardLimit(gn_state(grid, node), msft(m, s, f, t))
2535
2536
    ${  sum(gn2gnu(grid, node, grid_, node_output, unit)$(sum(restype, gnuRescapable(restype, 'up', grid_, 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, gnuRescapable(restype, 'up', grid_, node_input , unit))), 1)  // or nodes that have units with endogenous input with possible reserve provision
2537
        } ..
2538
2539
2540
2541

    // Utilizable headroom in the state variable
    + [
        // Current state of the variable
2542
        + v_state(grid, node, s, f+df_central(f,t), t)
2543
2544
2545

        // Lower boundary of the variable
        - p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'constant')${p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useConstant')}
2546
        - ts_node_(grid, node, 'downwardLimit', f, t, s)${ p_gnBoundaryPropertiesForStates(grid, node, 'downwardLimit', 'useTimeseries') }
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
        ] // END Headroom
        * [
            // Conversion to energy
            + p_gn(grid, node, 'energyStoredPerUnitOfState')
            // Accounting for losses from the node
            + p_stepLength(m, f, t)
                * [
                    + p_gn(grid, node, 'selfDischargeLoss')
                    + sum(gnn_state(grid, node, to_node),
                        + p_gnn(grid, node, to_node, 'diffCoeff')
                        ) // END sum(to_node)
                    ]
            ] // END * Headroom

2561
    =G=
2562
2563
2564
2565
2566
2567
2568

    // Convert reserve power to energy
    + p_stepLength(m, f, t)
        * [
            // Reserve provision from units that output to this node
            + sum(gn2gnu(grid_, node_input, grid, node, unit)${uft(unit, f, t)},
                // Upward reserves from units that output energy to the node
2569
2570
                + sum(gnuRescapable(restype, 'up', grid_, node_input, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid_, node_input, restype, 'reserve_length') },
                    + v_reserve(restype, 'up', grid_, node_input, unit, s, f+df_reserves(grid_, node_input, restype, f, t), t)
2571
                        / sum(suft(effGroup, unit, f, t),
2572
2573
2574
2575
2576
2577
2578
2579
2580
                            + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                            + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                            ) // END sum(effGroup)
                    ) // END sum(restype)
                ) // END sum(gn2gnu)

            // Reserve provision from units that take input from this node
            + sum(gn2gnu(grid, node, grid_, node_output, unit)${uft(unit, f, t)},
                // Upward reserves from units that use the node as energy input
2581
2582
                + sum(gnuRescapable(restype, 'up', grid_, node_output, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid_, node_output, restype, 'reserve_length') },
                    + v_reserve(restype, 'up', grid_, node_output, unit, s, f+df_reserves(grid_, node_output, restype, f, t), t)
2583
                        * sum(suft(effGroup, unit, f, t),
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
                            + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                            + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                            ) // END sum(effGroup)
                    ) // END sum(restype)
                ) // END sum(gn2gnu)

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

            ] // END * p_stepLength
2594
;
2595
2596
2597

* --- State Variable Difference -----------------------------------------------

2598
q_boundStateMaxDiff(gnn_boundState(grid, node, node_), msft(m, s, f, t)) ..
2599
2600

    // State of the bound node
2601
   + v_state(grid, node, s, f+df_central(f,t), t)
2602
2603

    // Reserve contributions affecting bound node, converted to energy
2604
    + p_stepLength(m, f, t)
2605
2606
        * [
            // Downwards reserve provided by input units
2607
2608
2609
2610
2611
2612
            + sum(gnuRescapable(restype, 'down', grid_, node_input, unit)${ p_gn(grid, node, 'energyStoredPerUnitOfState') // Reserve provisions not applicable if no state energy content
                                                                            and gn2gnu(grid_, node_input, grid, node, unit)
                                                                            and uft(unit, f, t)
                                                                            and ord(t) < tSolveFirst + p_gnReserves(grid_, node, restype, 'reserve_length')
                                                                            },
                + v_reserve(restype, 'down', grid_, node_input, unit, s, f+df_reserves(grid_, node_input, restype, f, t), t)
2613
                    / sum(suft(effGroup, unit, f, t),
2614
2615
2616
2617
2618
                        + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                        + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                        ) // END sum(effGroup)
                ) // END sum(nuRescapable)

2619
            // Downwards reserve provided by output units
2620
2621
2622
2623
2624
2625
            + sum(gnuRescapable(restype, 'down', grid_, node_output, unit)${ p_gn(grid, node, 'energyStoredPerUnitOfState') // Reserve provisions not applicable if no state energy content
                                                                             and gn2gnu(grid, node, grid_, node_output, unit)
                                                                             and uft(unit, f, t)
                                                                             and ord(t) < tSolveFirst + p_gnReserves(grid_, node, restype, 'reserve_length')
                                                                             },
                + v_reserve(restype, 'down', grid_, node_output, unit, s, f+df_reserves(grid_, node_output, restype, f, t), t)
2626
                    * sum(suft(effGroup, unit, f, t),
2627
2628
2629
2630
2631
2632
2633
2634
                        + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                        + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                        ) // END sum(effGroup)
                ) // END sum(nuRescapable)

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

2635
            ] // END * p_stepLength
2636

2637
2638
2639
            // Convert the reserve provisions into state variable values
            / ( p_gn(grid, node, 'energyStoredPerUnitOfState') + 1${not p_gn(grid, node, 'energyStoredPerUnitOfState')} )

2640
    =L=
2641

2642
    // State of the binding node
2643
    + v_state(grid, node_, s, f+df_central(f,t), t)
2644
2645
2646

   // Maximum state difference parameter
    + p_gnn(grid, node, node_, 'boundStateMaxDiff')
2647
2648

    // Reserve contributions affecting bounding node, converted to energy
2649
    - p_stepLength(m, f, t)
2650
2651
        * [
            // Upwards reserve by input node
2652
2653
2654
2655
2656
2657
            + sum(gnuRescapable(restype, 'up', grid_, node_input, unit)${ p_gn(grid, node_, 'energyStoredPerUnitOfState')
                                                                          and gn2gnu(grid_, node_input, grid, node_, unit)
                                                                          and uft(unit, f, t)
                                                                          and ord(t) < tSolveFirst + p_gnReserves(grid_, node, restype, 'reserve_length')
                                                                          },
                + v_reserve(restype, 'up', grid_, node_input, unit, s, f+df_reserves(grid_, node_input, restype, f, t), t)
2658
                    / sum(suft(effGroup, unit, f, t),
2659
2660
2661
2662
2663
2664
                        + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                        + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                        ) // END sum(effGroup)
                ) // END sum(nuRescapable)

            // Upwards reserve by output node
2665
2666
2667
2668
2669
2670
            + sum(gnuRescapable(restype, 'up', grid_, node_output, unit)${ p_gn(grid, node_, 'energyStoredPerUnitOfState')
                                                                           and gn2gnu(grid, node_, grid_, node_output, unit)
                                                                           and uft(unit, f, t)
                                                                           and ord(t) < tSolveFirst + p_gnReserves(grid_, node, restype, 'reserve_length')
                                                                           },
                + v_reserve(restype, 'up', grid_, node_output, unit, s, f+df_reserves(grid_, node_output, restype, f, t), t)
2671
                    * sum(suft(effGroup, unit, f, t),
2672
2673
2674
2675
2676
2677
2678
2679
                        + p_effGroupUnit(effGroup, unit, 'slope')${not ts_effGroupUnit(effGroup, unit, 'slope', f, t)}
                        + ts_effGroupUnit(effGroup, unit, 'slope', f, t) // Efficiency approximated using maximum slope of effGroup?
                        ) // END sum(effGroup)
                ) // END sum(nuRescapable)

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

2680
            ] // END * p_stepLength
2681
2682
2683

            // Convert the reserve provisions into state variable values
            / ( p_gn(grid, node_, 'energyStoredPerUnitOfState') + 1${not p_gn(grid, node_, 'energyStoredPerUnitOfState')} )
2684
;
2685
2686
2687

* --- Cyclic Boundary Conditions ----------------------------------------------

2688
2689
2690
2691
2692
q_boundCyclic(gnss_bound(gn_state(grid, node), s_, s), m)
    ${  ms(m, s_)
        and ms(m, s)
        and tSolveFirst = mSettings(m, 't_start')
        }..
2693

2694
    // Initial value of the state of the node at the start of the sample s
Niina Helistö's avatar
Niina Helistö committed
2695
    + sum(mst_start(m, s, t),
2696
2697
        + sum(sft(s, f, t),
            + v_state(grid, node, s, f+df(f,t+dt(t)), t+dt(t))
Niina Helistö's avatar
Niina Helistö committed
2698
2699
            ) // END sum(ft)
        ) // END sum(mst_start)
2700

2701
    =E=
2702

2703
2704
2705
2706
    // Initial value of the state of the node at the start of the sample s_
    + sum(mst_start(m, s_, t),
        + sum(sft(s_, f, t),
            + v_state(grid, node, s_, f+df(f,t+dt(t)), t+dt(t))
Niina Helistö's avatar
Niina Helistö committed
2707
            ) // END sum(ft)
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
        ) // END sum(mst_start)
    // Change in the state value over the sample s_, multiplied by sample s_ temporal weight
    + p_msWeight(m, s_)
        * [
            // State of the node at the end of the sample s_
            + sum(mst_end(m, s_, t),
                + sum(sft(s_, f, t),
                    + v_state(grid, node, s_, f, t)
                    ) // END sum(ft)
                ) // END sum(mst_end)
            // State of the node at the end of the sample s_
            - sum(mst_start(m, s_, t),
                + sum(sft(s_, f, t),
                    + v_state(grid, node, s_, f+df(f,t+dt(t)), t+dt(t))
                    ) // END sum(ft)
                ) // END sum(mst_start)
            ] // END * p_msWeight(m, s_)
2725
;
2726

Niina Helistö's avatar
Niina Helistö committed
2727
2728
*--- Minimum Inertia ----------------------------------------------------------

2729
2730
2731
q_inertiaMin(group, sft(s, f, t))
    ${  p_groupPolicy(group, 'kineticEnergyMin')
        } ..
Niina Helistö's avatar
Niina Helistö committed
2732
2733
2734
2735

    // Kinectic energy in the system
    + sum(gnu_output(grid, node, unit)${    p_gnu(grid, node, unit, 'unitSizeGen')
                                            and gnGroup(grid, node, group)
2736
                                            and gnuft(grid, node, unit, f, t)
Niina Helistö's avatar
Niina Helistö committed
2737
2738
2739
2740
                                            },
        + p_gnu(grid, node, unit, 'inertia')
            * p_gnu(grid ,node, unit, 'unitSizeMVA')
            * [
Topi Rasku's avatar
Topi Rasku committed
2741
                + v_online_LP(unit, s, f+df_central(f,t), t)
2742
                    ${uft_onlineLP(unit, f, t)}
Topi Rasku's avatar
Topi Rasku committed
2743
                + v_online_MIP(unit, s, f+df_central(f,t), t)
2744
                    ${uft_onlineMIP(unit, f, t)}
2745
                + v_gen(grid, node, unit, s, f, t)${not uft_online(unit, f, t)}
2746
                    / p_gnu(grid, node, unit, 'unitSizeGen')
Niina Helistö's avatar
Niina Helistö committed
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
                ] // * p_gnu
        ) // END sum(gnu_output)

    =G=

    + p_groupPolicy(group, 'kineticEnergyMin')
;

*--- Maximum Share of Instantaneous Generation --------------------------------

2757
2758
2759
q_instantaneousShareMax(group, sft(s, f, t))
    ${  p_groupPolicy(group, 'instantaneousShareMax')
        } ..
Niina Helistö's avatar
Niina Helistö committed
2760
2761
2762
2763
2764

    // Generation of units in the group
    + sum(gnu(grid, node, unit)${   gnuGroup(grid, node, unit, group)
                                    and p_gnu(grid, node, unit, 'unitSizeGen')
                                    and gnGroup(grid, node, group)
2765
                                    and gnuft(grid, node, unit, f, t)
Niina Helistö's avatar
Niina Helistö committed
2766
                                    },
2767
        + v_gen(grid, node, unit, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
2768
2769
2770
2771
2772
2773
2774
2775
        ) // END sum(gnu)

    // Controlled transfer to this node group
    // Set gn2nGroup controls whether transfer is included in the equation
    + sum(gn2n_directional(grid, node, node_)${ gn2nGroup(grid, node, node_, group)
                                                and gnGroup(grid, node, group)
                                                and not gnGroup(grid, node_, group)
                                                },
2776
        + v_transferLeftward(grid, node, node_, s, f, t) * (1-p_gnn(grid, node, node_, 'transferLoss'))
Niina Helistö's avatar
Niina Helistö committed
2777
2778
2779
2780
2781
2782
        ) // END sum(gn2n_directional)

    + sum(gn2n_directional(grid, node_, node)${ gn2nGroup(grid, node_, node, group)
                                                and gnGroup(grid, node, group)
                                                and not gnGroup(grid, node_, group)
                                                },
2783
        + v_transferRightward(grid, node_, node, s, f, t) * (1-p_gnn(grid, node_, node, 'transferLoss'))
Niina Helistö's avatar
Niina Helistö committed
2784
2785
2786
2787
2788
2789
2790
2791
        ) // END sum(gn2n_directional)

    =L=

    + p_groupPolicy(group, 'instantaneousShareMax')
        * [
            // External power inflow/outflow
            - sum(gnGroup(grid, node, group),
2792
                + ts_influx_(grid, node, f, t, s)
Niina Helistö's avatar
Niina Helistö committed
2793
2794
2795
2796
2797
                ) // END sum(gnGroup)

            // Consumption of units
            - sum(gnu_input(grid, node, unit)${ p_gnu(grid, node, unit, 'unitSizeCons')
                                                and gnGroup(grid, node, group)
2798
                                                and gnuft(grid, node, unit, f, t)
Niina Helistö's avatar
Niina Helistö committed
2799
                                                },
2800
                + v_gen(grid, node, unit, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
2801
2802
2803
                ) // END sum(gnu)

            // Controlled transfer from this node group
2804
            + sum(gn2n_directional(grid, node, node_)${ gnGroup(grid, node, group)
Niina Helistö's avatar
Niina Helistö committed
2805
2806
                                                        and not gnGroup(grid, node_, group)
                                                        },
2807
                + v_transferRightward(grid, node, node_, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
2808
2809
                ) // END sum(gn2n_directional)

2810
            + sum(gn2n_directional(grid, node_, node)${ gnGroup(grid, node, group)
Niina Helistö's avatar
Niina Helistö committed
2811
2812
                                                        and not gnGroup(grid, node_, group)
                                                        },
2813
                + v_transferLeftward(grid, node_, node, s, f, t)
Niina Helistö's avatar
Niina Helistö committed
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
                ) // END sum(gn2n_directional)
$ontext
        // No uncontrolled (AC) transfer because this equation is typically used
        // for one synchronous area which does not have any external AC links

        // Energy diffusion from this node to neighbouring nodes
      + sum(gnn_state(grid, node, node_)${  gnGroup(grid, node, group)
                                            and not gnGroup(grid, node_, group)
            }, p_gnn(grid, node, node_, 'diffCoeff') * v_state(grid, node, f+df_central(f,t), t)
        )
        // Energy diffusion from neighbouring nodes to this node
      - sum(gnn_state(grid, node_, node)${  gnGroup(grid, node, group)
                                            and not gnGroup(grid, node_, group)
            }, p_gnn(grid, node_, node, 'diffCoeff') * v_state(grid, node_, f+df_central(f,t), t)
        )
$offtext
            ] // END * p_groupPolicy

;

2834
2835
*--- Constrained Number of Online Units ---------------------------------------

2836
2837
2838
2839
q_constrainedOnlineMultiUnit(group, sft(s, f, t))
    ${  p_groupPolicy(group, 'constrainedOnlineTotalMax')
        or sum(unit$uGroup(unit, group), abs(p_groupPolicy3D(group, 'constrainedOnlineMultiplier', unit)))
        } ..
2840
2841
2842
2843
2844

    // Sum of multiplied online units
    + sum(unit$uGroup(unit, group),
        + p_groupPolicy3D(group, 'constrainedOnlineMultiplier', unit)
            * [
Topi Rasku's avatar
Topi Rasku committed
2845
2846
2847
2848
                + v_online_LP(unit, s, f+df_central(f,t), t)
                    ${uft_onlineLP(unit, f, t)}
                + v_online_MIP(unit, s, f+df_central(f,t), t)
                    ${uft_onlineMIP(unit, f, t)}
2849
2850
2851
2852
2853
2854
2855
2856
2857
                ] // END * p_groupPolicy3D(group, 'constrainedOnlineMultiplier', unit)
        ) // END sum(unit)

    =L=

    // Total maximum of multiplied online units
    + p_groupPolicy(group, 'constrainedOnlineTotalMax')
;

2858
2859
*--- Required Capacity Margin -------------------------------------------------

2860
2861
2862
q_capacityMargin(gn(grid, node), sft(s, f, t))
    ${  p_gn(grid, node, 'capacityMargin')
        } ..
2863

2864
    // Availability of units, based on 'availabilityCapacityMargin'
2865
2866
2867
    + sum(gnu_output(grid, node, unit)${ gnuft(grid, node, unit, f, t)
                                         and p_gnu(grid, node, unit, 'availabilityCapacityMargin')
                                         },
2868
        + p_unit(unit, 'availability')
2869
            * p_gnu(grid, node, unit, 'availabilityCapacityMargin')
2870
            * [
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
                // Output capacity before investments
                + p_gnu(grid, node, unit, 'maxGen')

                // Output capacity investments
                + p_gnu(grid, node, unit, 'unitSizeGen')
                    * sum(t_invest(t_)${ord(t_)<=ord(t)},
                        + v_invest_LP(unit, t_)${unit_investLP(unit)}
                        + v_invest_MIP(unit, t_)${unit_investMIP(unit)}
                        ) // END sum(t_invest)
                ] // END * p_unit(availability)
        ) // END sum(gnu_output)

    // Availability of units, including capacity factors for flow units and v_gen for other units
2884
2885
2886
    + sum(gnu_output(grid, node, unit)${ gnuft(grid, node, unit, f, t)
                                         and not p_gnu(grid, node, unit, 'availabilityCapacityMargin')
                                         },
2887
2888
2889
2890
2891
        // Capacity factors for flow units
        + sum(flowUnit(flow, unit)${ unit_flow(unit) },
            + ts_cf_(flow, node, f, t, s)
            ) // END sum(flow)
            * p_unit(unit, 'availability')
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
            * [
                // Output capacity before investments
                + p_gnu(grid, node, unit, 'maxGen')

                // Output capacity investments
                + p_gnu(grid, node, unit, 'unitSizeGen')
                    * sum(t_invest(t_)${ord(t_)<=ord(t)},
                        + v_invest_LP(unit, t_)${unit_investLP(unit)}
                        + v_invest_MIP(unit, t_)${unit_investMIP(unit)}
                        ) // END sum(t_invest)
                ] // END * p_unit(availability)
2903
        + v_gen(grid, node, unit, s, f, t)${not unit_flow(unit)}
2904
2905
2906
2907
        ) // END sum(gnu_output)

    // Transfer to node
    + sum(gn2n_directional(grid, node_, node),
2908
2909
        + v_transfer(grid, node_, node, s, f, t)
        - v_transferRightward(grid, node_, node, s, f, t)
2910
2911
2912
2913
2914
            * p_gnn(grid, node_, node, 'transferLoss')
        ) // END sum(gn2n_directional)

    // Transfer from node
    - sum(gn2n_directional(grid, node, node_),
2915
2916
        + v_transfer(grid, node, node_, s, f, t)
        + v_transferLeftward(grid, node, node_, s, f, t)
2917
2918
2919
2920
2921
2922
            * p_gnn(grid, node, node_, 'transferLoss')
        ) // END sum(gn2n_directional)

    // Diffusion to node
    + sum(gnn_state(grid, from_node, node),
        + p_gnn(grid, from_node, node, 'diffCoeff')
2923
            * v_state(grid, from_node, s, f+df_central(f,t), t)
2924
2925
2926
2927
2928
        ) // END sum(gnn_state)

    // Diffusion from node
    - sum(gnn_state(grid, node, to_node),
        + p_gnn(grid, node, to_node, 'diffCoeff')
2929
            * v_state(grid, node, s, f+df_central(f,t), t)
2930
2931
2932
2933
        ) // END sum(gnn_state)

    // Conversion unit inputs might require additional capacity
    + sum(gnu_input(grid, node, unit),
2934
        + v_gen(grid, node, unit, s, f, t)
2935
2936
2937
        ) // END sum(gnu_input)

    // Energy influx
2938
    + ts_influx_(grid, node, f, t, s)
2939

2940
    // Capacity margin feasibility dummy variables
2941
    + vq_capacity(grid, node, s, f, t)
2942

2943
2944
2945
2946
    =G=

    // Capacity minus influx must be greated than the desired margin
    + p_gn(grid, node, 'capacityMargin')
2947
;
2948

Niina Helistö's avatar
Niina Helistö committed
2949
2950
*--- Constrained Investment Ratios and Sums For Groups of Units -----------

2951
2952
2953
2954
q_constrainedCapMultiUnit(group, t_invest(t))
    ${  p_groupPolicy(group, 'constrainedCapTotalMax')
        or sum(uGroup(unit, group), abs(p_groupPolicy3D(group, 'constrainedCapMultiplier', unit)))
        } ..
Niina Helistö's avatar
Niina Helistö committed
2955
2956

    // Sum of multiplied investments
2957
    + sum(uGroup(unit, group),
Niina Helistö's avatar
Niina Helistö committed
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
        + p_groupPolicy3D(group, 'constrainedCapMultiplier', unit)
            * [
                + v_invest_LP(unit, t)${unit_investLP(unit)}
                + v_invest_MIP(unit, t)${unit_investMIP(unit)}
                ] // END * p_groupPolicy3D(group, 'constrainedCapMultiplier', unit)
        ) // END sum(unit)

    =L=

    // Total maximum of multiplied investments
    + p_groupPolicy(group, 'constrainedCapTotalMax')
;

2971
2972
2973
2974
2975
*--- Required Emission Cap ----------------------------------------------------
// !!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// This equation doesn't really make sense for rolling planning simulations.
// Is there any way to make it work?

2976
2977
2978
q_emissioncap(group, emission)
    ${  p_groupPolicy3D(group, 'emissionCap', emission)
        } ..
2979
2980

    + sum(msft(m, s, f, t),
2981
        + p_msft_Probability(m,s,f,t)
2982
2983
2984
2985
        * [
            // Time step length dependent emissions
            + p_stepLength(m, f, t)
                * sum((uft(unit_fuel, f, t), fuel)${uFuel(unit_fuel, 'main', fuel)},
2986
                    + v_fuelUse(fuel, unit_fuel, s, f, t)
2987
                        * p_fuelEmission(fuel, emission) / 1e3
2988
                        * sum(gnu_output(grid, node, unit_fuel)${gnGroup(grid, node, group)},
2989
2990
2991
2992
2993
2994
2995
                            + p_gnu(grid, node, unit_fuel, 'unitSizeGen')
                            ) // END sum(gnu_output)
                        / sum(gnu_output(grid_, node_, unit_fuel),
                            + p_gnu(grid_, node_, unit_fuel, 'unitSizeGen')
                            ) // END sum(gnu_output)
                    ) // END sum(uft)

2996
            // Start-up emissions
2997
            + sum(uft_online(unit_fuel, f, t),
2998
                + sum(unitStarttype(unit_fuel, starttype),
2999
3000
                    + [
                        + v_startup_LP(unit_fuel, starttype, s, f, t)