2d_constraints.gms 175 KB
Newer Older
1001
1002
1003
        and gnuft(grid, node, unit, f, t)
        and not sum(restypeDirectionGridNodeGroup(restype, up_down, grid, node, group),
                    ft_reservesFixed(group, restype, f+df_reservesGroup(group, restype, f, t), t))
1004
        and uft_online(unit, f ,t)
1005
        and not gnuOfflineRescapable(restype, grid, node, unit)
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
1006
1007
        }..

1008
    + v_reserve(restype, up_down, grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t)
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
1009
1010
1011

    =L=

1012
1013
    + p_gnuReserves(grid, node, unit, restype, up_down)
        * p_gnu(grid, node, unit, 'unitSizeTot')
1014
1015
1016
        * [
            + 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)}
Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
1017
1018
1019
1020
1021
1022
1023
1024
1025
            ]
        * p_unit(unit, 'availability') // Taking into account availability...
        * [
            // ... and capacity factor for flow units
            + sum(flowUnit(flow, unit),
                + ts_cf_(flow, node, f, t, s)
                ) // END sum(flow)
            + 1${not unit_flow(unit)}
            ] // How to consider reserveReliability in the case of investments when we typically only have "realized" time steps?
1026

Ciara O'Dwyer's avatar
Ciara O'Dwyer committed
1027
1028
1029
;


1030
1031
* --- Unit Startup and Shutdown -----------------------------------------------

1032
1033
1034
1035
q_startshut(ms(m, s), uft_online(unit, f, t))
    ${  msft(m, s, f, t)
        }..

1036
    // Units currently online
1037
1038
    + 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)}
1039
1040

    // Units previously online
1041
    // The same units
1042
    - v_online_LP (unit, s+ds(s,t), f+df(f,t+dt(t)), t+dt(t))${ uft_onlineLP_withPrevious(unit, f+df(f,t+dt(t)), t+dt(t))
1043
                                                             and not uft_aggregator_first(unit, f, t) } // This reaches to tFirstSolve when dt = -1
1044
    - v_online_MIP(unit, s+ds(s,t), f+df(f,t+dt(t)), t+dt(t))${ uft_onlineMIP_withPrevious(unit, f+df(f,t+dt(t)), t+dt(t))
1045
1046
1047
1048
                                                             and not uft_aggregator_first(unit, f, t) }

    // Aggregated units just before they are turned into aggregator units
    - sum(unit_${unitAggregator_unit(unit, unit_)},
1049
1050
        + v_online_LP (unit_, s, f+df(f,t+dt(t)), t+dt(t))${uft_onlineLP_withPrevious(unit_, f+df(f,t+dt(t)), t+dt(t))}
        + v_online_MIP(unit_, s, f+df(f,t+dt(t)), t+dt(t))${uft_onlineMIP_withPrevious(unit_, f+df(f,t+dt(t)), t+dt(t))}
1051
        )${uft_aggregator_first(unit, f, t)} // END sum(unit_)
1052

1053
1054
    =E=

1055
    // Unit startup and shutdown
1056

1057
    // Add startup of units dt_toStartup before the current t (no start-ups for aggregator units before they become active)
1058
    + sum(unitStarttype(unit, starttype),
1059
        + v_startup_LP(unit, starttype, s, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t))
1060
            ${ uft_onlineLP_withPrevious(unit, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) }
1061
        + v_startup_MIP(unit, starttype, s, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t))
1062
            ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) }
1063
        )${not [unit_aggregator(unit) and ord(t) + dt_toStartup(unit, t) <= tSolveFirst + p_unit(unit, 'lastStepNotAggregated')]} // END sum(starttype)
1064

1065
1066
1067
1068
    // NOTE! According to 3d_setVariableLimits,
    // cannot start a unit if the time when the unit would become online is outside
    // the horizon when the unit has an online variable
    // --> no need to add start-ups of aggregated units to aggregator units
1069

1070
    // Shutdown of units at time t
1071
1072
1073
1074
    - v_shutdown_LP(unit, s, f, t)
        ${ uft_onlineLP(unit, f, t) }
    - v_shutdown_MIP(unit, s, f, t)
        ${ uft_onlineMIP(unit, f, t) }
1075
;
1076

1077
*--- Startup Type -------------------------------------------------------------
1078
// !!! NOTE !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1079
1080
1081
// This formulation doesn't work as intended when unitCount > 1, as one recent
// shutdown allows for multiple hot/warm startups on subsequent time steps.
// Pending changes.
1082

1083
1084
1085
1086
q_startuptype(ms(m, s), starttypeConstrained(starttype), uft_online(unit, f, t))
    ${  msft(m, s, f, t)
        and unitStarttype(unit, starttype)
        } ..
1087
1088

    // Startup type
1089
1090
    + v_startup_LP(unit, starttype, s, f, t)${ uft_onlineLP(unit, f, t) }
    + v_startup_MIP(unit, starttype, s, f, t)${ uft_onlineMIP(unit, f, t) }
1091
1092
1093
1094

    =L=

    // Subunit shutdowns within special startup timeframe
1095
1096
1097
1098
1099
1100
1101
    + sum(unitCounter(unit, counter)${  dt_starttypeUnitCounter(starttype, unit, counter)
                                        and t_active(t+(dt_starttypeUnitCounter(starttype, unit, counter)+1))
                                        },
        + v_shutdown_LP(unit, s, f+df(f,t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)), t+(dt_starttypeUnitCounter(starttype, unit, counter)+1))
            ${ uft_onlineLP_withPrevious(unit, f+df(f,t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)), t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)) }
        + v_shutdown_MIP(unit, s, f+df(f,t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)), t+(dt_starttypeUnitCounter(starttype, unit, counter)+1))
            ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)), t+(dt_starttypeUnitCounter(starttype, unit, counter)+1)) }
1102
1103
1104
        ) // END sum(counter)

    // NOTE: for aggregator units, shutdowns for aggregated units are not considered
1105
;
1106

1107

1108
1109
*--- Online Limits with Startup Type Constraints and Investments --------------

1110
1111
1112
1113
1114
1115
1116
1117
1118
q_onlineLimit(ms(m, s), uft_online(unit, f, t))
    ${  msft(m, s, f, t)
        and {
            p_unit(unit, 'minShutdownHours')
            or p_u_runUpTimeIntervals(unit)
            or unit_investLP(unit)
            or unit_investMIP(unit)
        }} ..

1119
    // Online variables
1120
1121
    + 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)}
1122
1123
1124
1125
1126
1127

    =L=

    // Number of existing units
    + p_unit(unit, 'unitCount')

1128
    // Number of units unable to become online due to restrictions
1129
1130
1131
1132
1133
1134
1135
    - sum(unitCounter(unit, counter)${  dt_downtimeUnitCounter(unit, counter)
                                        and t_active(t+(dt_downtimeUnitCounter(unit, counter) + 1))
                                        },
        + v_shutdown_LP(unit, s, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1))
            ${ uft_onlineLP_withPrevious(unit, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1)) }
        + v_shutdown_MIP(unit, s, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1))
            ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1)) }
1136
1137
1138
        ) // END sum(counter)

    // Number of units unable to become online due to restrictions (aggregated units in the past horizon or if they have an online variable)
1139
    - sum(unitAggregator_unit(unit, unit_),
1140
1141
1142
1143
1144
1145
1146
        + sum(unitCounter(unit, counter)${  dt_downtimeUnitCounter(unit, counter)
                                            and t_active(t+(dt_downtimeUnitCounter(unit, counter) + 1))
                                            },
            + v_shutdown_LP(unit_, s, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1))
                ${ uft_onlineLP_withPrevious(unit_, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1)) }
            + v_shutdown_MIP(unit_, s, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1))
                ${ uft_onlineMIP_withPrevious(unit_, f+df(f,t+(dt_downtimeUnitCounter(unit, counter) + 1)), t+(dt_downtimeUnitCounter(unit, counter) + 1)) }
1147
1148
            ) // END sum(counter)
        )${unit_aggregator(unit)} // END sum(unit_)
1149
1150
1151

    // Investments into units
    + sum(t_invest(t_)${ord(t_)<=ord(t)},
1152
1153
        + v_invest_LP(unit, t_)${unit_investLP(unit)}
        + v_invest_MIP(unit, t_)${unit_investMIP(unit)}
1154
1155
1156
        ) // END sum(t_invest)
;

1157
1158
1159
1160
*--- Both q_offlineAfterShutdown and q_onlineOnStartup work when there is only one unit.
*    These equations prohibit single units turning on and off at the same time step.
*    Unfortunately there seems to be no way to prohibit this when unit count is > 1.
*    (it shouldn't be worthwhile anyway if there is a startup cost, but it can fall within the solution gap).
1161
1162
1163
1164
q_onlineOnStartUp(s_active(s), uft_online(unit, f, t))
    ${  sft(s, f, t)
        and sum(starttype, unitStarttype(unit, starttype))
        }..
1165
1166

    // Units currently online
1167
1168
    + 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)}
1169
1170
1171
1172

    =G=

    + sum(unitStarttype(unit, starttype),
1173
        + v_startup_LP(unit, starttype, s, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) //dt_toStartup displaces the time step to the one where the unit would be started up in order to reach online at t
1174
            ${ uft_onlineLP_withPrevious(unit, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) }
1175
        + v_startup_MIP(unit, starttype, s, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) //dt_toStartup displaces the time step to the one where the unit would be started up in order to reach online at t
1176
            ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+dt_toStartup(unit, t)), t+dt_toStartup(unit, t)) }
1177
1178
1179
      ) // END sum(starttype)
;

1180
1181
1182
1183
q_offlineAfterShutdown(s_active(s), uft_online(unit, f, t))
    ${  sft(s, f, t)
        and sum(starttype, unitStarttype(unit, starttype))
        }..
1184

1185
1186
1187
1188
1189
    // Number of existing units
    + p_unit(unit, 'unitCount')

    // Investments into units
    + sum(t_invest(t_)${ord(t_)<=ord(t)},
1190
1191
        + v_invest_LP(unit, t_)${unit_investLP(unit)}
        + v_invest_MIP(unit, t_)${unit_investMIP(unit)}
1192
1193
        ) // END sum(t_invest)

1194
    // Units currently online
1195
1196
    - 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)}
1197
1198
1199

    =G=

1200
1201
1202
1203
    + v_shutdown_LP(unit, s, f, t)
        ${ uft_onlineLP(unit, f, t) }
    + v_shutdown_MIP(unit, s, f, t)
        ${ uft_onlineMIP(unit, f, t) }
1204
1205
;

1206
1207
*--- Minimum Unit Uptime ------------------------------------------------------

1208
1209
1210
1211
q_onlineMinUptime(ms(m, s), uft_online(unit, f, t))
    ${  msft(m, s, f, t)
        and  p_unit(unit, 'minOperationHours')
        } ..
1212
1213

    // Units currently online
1214
1215
    + 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)}
1216
1217
1218
1219

    =G=

    // Units that have minimum operation time requirements active
1220
1221
1222
    + sum(unitCounter(unit, counter)${  dt_uptimeUnitCounter(unit, counter)
                                        and t_active(t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) // Don't sum over counters that don't point to an active time step
                                        },
1223
        + sum(unitStarttype(unit, starttype),
1224
            + v_startup_LP(unit, starttype, s, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1))
1225
                ${ uft_onlineLP_withPrevious(unit, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) }
1226
            + v_startup_MIP(unit, starttype, s, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1))
1227
                ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) }
1228
            ) // END sum(starttype)
1229
1230
1231
        ) // END sum(counter)

    // Units that have minimum operation time requirements active (aggregated units in the past horizon or if they have an online variable)
Topi Rasku's avatar
Topi Rasku committed
1232
    + sum(unitAggregator_unit(unit, unit_),
1233
1234
1235
        + sum(unitCounter(unit, counter)${  dt_uptimeUnitCounter(unit, counter)
                                            and t_active(t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) // Don't sum over counters that don't point to an active time step
                                            },
1236
            + sum(unitStarttype(unit, starttype),
1237
                + v_startup_LP(unit, starttype, s, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1))
1238
                    ${ uft_onlineLP_withPrevious(unit, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) }
1239
                + v_startup_MIP(unit, starttype, s, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1))
1240
                    ${ uft_onlineMIP_withPrevious(unit, f+df(f,t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)), t+(dt_uptimeUnitCounter(unit, counter)+dt_toStartup(unit, t) + 1)) }
1241
1242
1243
                ) // END sum(starttype)
            ) // END sum(counter)
        )${unit_aggregator(unit)} // END sum(unit_)
1244
1245
;

1246
1247
* --- Cyclic Boundary Conditions for Online State -----------------------------

1248
1249
1250
1251
1252
q_onlineCyclic(uss_bound(unit, s_, s), m)
    ${  ms(m, s_)
        and ms(m, s)
        and tSolveFirst = mSettings(m, 't_start')
        }..
1253
1254
1255
1256

    // Initial value of the state of the unit at the start of the sample
    + sum(mst_start(m, s, t),
        + sum(sft(s, f, t),
Topi Rasku's avatar
Topi Rasku committed
1257
1258
1259
1260
            + v_online_LP(unit, s, f+df(f,t+dt(t)), t+dt(t))
                ${uft_onlineLP_withPrevious(unit, f+df(f,t+dt(t)), t+dt(t))}
            + v_online_MIP(unit, s, f+df(f,t+dt(t)), t+dt(t))
                ${uft_onlineMIP_withPrevious(unit, f+df(f,t+dt(t)), t+dt(t))}
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
            ) // END sum(ft)
        ) // END sum(mst_start)

    =E=

    // State of the unit at the end of the sample
    + sum(mst_end(m, s_, t_),
        + sum(sft(s_, f_, t_),
            + v_online_LP(unit, s_, f_, t_)${uft_onlineLP(unit, f_, t_)}
            + v_online_MIP(unit, s_, f_, t_)${uft_onlineMIP(unit, f_, t_)}
            ) // END sum(ft)
        ) // END sum(mst_end)
;

1275
* --- Ramp Constraints --------------------------------------------------------
1276

1277
1278
1279
1280
q_genRamp(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
    ${  ord(t) > msStart(m, s) + 1
        and msft(m, s, f, t)
        } ..
1281

1282
1283
    + v_genRamp(grid, node, unit, s, f, t)
        * p_stepLength(m, f, t)
1284

1285
    =E=
1286

1287
    // Change in generation over the interval: v_gen(t) - v_gen(t-1)
1288
    + v_gen(grid, node, unit, s, f, t)
1289

1290
    // Unit generation at t-1 (except aggregator units right before the aggregation threshold, see next term)
1291
    - v_gen(grid, node, unit, s+ds(s,t), f+df(f,t+dt(t)), t+dt(t))${not uft_aggregator_first(unit, f, t)}
1292
1293
    // Unit generation at t-1, aggregator units right before the aggregation threshold
    + sum(unit_${unitAggregator_unit(unit, unit_)},
1294
        - v_gen(grid, node, unit_, s+ds(s,t), f+df(f,t+dt(t)), t+dt(t))
1295
      )${uft_aggregator_first(unit, f, t)}
1296
;
1297

1298
* --- Ramp Up Limits ----------------------------------------------------------
1299

1300
1301
1302
1303
q_rampUpLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
    ${  ord(t) > msStart(m, s) + 1
        and msft(m, s, f, t)
        and p_gnu(grid, node, unit, 'maxRampUp')
1304
        and [ sum(restype, gnuRescapable(restype, 'up', grid, node, unit))
1305
1306
1307
1308
1309
1310
1311
              or uft_online(unit, f, t)
              or unit_investLP(unit)
              or unit_investMIP(unit)
              ]
        } ..

    // Ramp speed of the unit?
1312
    + v_genRamp(grid, node, unit, s, f, t)
1313
1314
1315
    + sum(gnuRescapable(restype, 'up', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
                                                           and not gnuOfflineRescapable(restype, grid, node, unit)
                                                           },
1316
        + v_reserve(restype, 'up', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
1317
1318
1319
1320
1321
        ) // END sum(nuRescapable)
        / p_stepLength(m, f, t)

    =L=

1322
    // Ramping capability of units without an online variable
1323
1324
1325
    + (
        + ( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') )${not uft_online(unit, f, t)}
        + sum(t_invest(t_)${ ord(t_)<=ord(t) },
1326
            + v_invest_LP(unit, t_)${not uft_online(unit, f, t) and unit_investLP(unit)}
1327
                * p_gnu(grid, node, unit, 'unitSizeTot')
1328
            + v_invest_MIP(unit, t_)${not uft_online(unit, f, t) and unit_investMIP(unit)}
1329
1330
1331
1332
1333
1334
                * p_gnu(grid, node, unit, 'unitSizeTot')
          )
      )
        * p_gnu(grid, node, unit, 'maxRampUp')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]

1335
    // Ramping capability of units with an online variable
1336
    + (
Topi Rasku's avatar
Topi Rasku committed
1337
1338
1339
1340
        + 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)}
1341
1342
1343
1344
1345
      )
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * p_gnu(grid, node, unit, 'maxRampUp')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]

1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
    // Generation units not be able to ramp from zero to min. load within one time interval according to their maxRampUp
    + sum(unitStarttype(unit, starttype)${   uft_online(unit, f, t)
                                             and gnu_output(grid, node, unit)
                                             and not uft_startupTrajectory(unit, f, t)
                                             and ( + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                                                       + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                                                       + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                                                     ) // END sum(effGroup)
                                                       / p_stepLength(m, f, t)
                                                   - p_gnu(grid, node, unit, 'maxRampUp')
                                                       * 60 > 0
                                                   )
                                             },
1359
1360
1361
1362
        + v_startup_LP(unit, starttype, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_startup_MIP(unit, starttype, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
      ) // END sum(starttype)
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
              ) // END sum(effGroup)
                / p_stepLength(m, f, t)
            - p_gnu(grid, node, unit, 'maxRampUp')
                * 60   // Unit conversion from [p.u./min] to [p.u./h]
          ) // END * v_startup

1375
1376
1377
1378
    // Units in the run-up phase need to keep up with the run-up rate
    + p_gnu(grid, node, unit, 'unitSizeTot')
        * 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
1379
1380
                + [
                    + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1381
                        ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1382
                    + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1383
                        ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1384
                    ]
1385
1386
1387
1388
1389
1390
1391
1392
                    * [
                        + p_unit(unit, 'rampSpeedToMinLoad')
                        + ( p_gnu(grid, node, unit, 'maxRampUp') - p_unit(unit, 'rampSpeedToMinLoad') )${ not runUpCounter(unit, counter+1) } // Ramp speed adjusted for the last run-up interval
                            * ( p_u_runUpTimeIntervalsCeil(unit) - p_u_runUpTimeIntervals(unit) )
                        ]
                    * 60 // Unit conversion from [p.u./min] into [p.u./h]
                ) // END sum(runUpCounter)
            ) // END sum(unitStarttype)
1393

1394
    // Shutdown of consumption units according to maxRampUp
1395
1396
1397
1398
1399
1400
    + [
        + v_shutdown_LP(unit, s, f, t)
            ${uft_onlineLP(unit, f, t) and gnu_input(grid, node, unit)}
        + v_shutdown_MIP(unit, s, f, t)
            ${uft_onlineMIP(unit, f, t) and gnu_input(grid, node, unit)}
        ]
1401
        * p_gnu(grid, node, unit, 'unitSizeTot')
1402
1403
1404
        * p_gnu(grid, node, unit, 'maxRampUp')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]
    // Consumption units not be able to ramp from min. load to zero within one time interval according to their maxRampUp
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
    + [
        + v_shutdown_LP(unit, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_shutdown_MIP(unit, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
        ]
        ${  gnu_input(grid, node, unit)
            and ( + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                      + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                      + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                      ) // END sum(effGroup)
                      / p_stepLength(m, f, t)
                  - p_gnu(grid, node, unit, 'maxRampUp')
                      * 60 > 0
                  )
            }
1421
1422
1423
1424
1425
1426
1427
1428
1429
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                ) // END sum(effGroup)
                / p_stepLength(m, f, t)
            - p_gnu(grid, node, unit, 'maxRampUp')
                * 60   // Unit conversion from [p.u./min] to [p.u./h]
1430
          ) // END * v_shutdown
1431
;
1432
1433


1434
* --- Ramp Down Limits --------------------------------------------------------
1435

1436
1437
1438
1439
q_rampDownLimit(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
    ${  ord(t) > msStart(m, s) + 1
        and msft(m, s, f, t)
        and p_gnu(grid, node, unit, 'maxRampDown')
1440
        and [ sum(restype, gnuRescapable(restype, 'down', grid, node, unit))
1441
1442
1443
1444
1445
1446
1447
              or uft_online(unit, f, t)
              or unit_investLP(unit)
              or unit_investMIP(unit)
              ]
        } ..

    // Ramp speed of the unit?
1448
    + v_genRamp(grid, node, unit, s, f, t)
1449
1450
1451
    - sum(gnuRescapable(restype, 'down', grid, node, unit)${ ord(t) < tSolveFirst + p_gnReserves(grid, node, restype, 'reserve_length')
                                                             and not gnuOfflineRescapable(restype, grid, node, unit)
                                                             },
1452
        + v_reserve(restype, 'down', grid, node, unit, s, f+df_reserves(grid, node, restype, f, t), t) // (v_reserve can be used only if the unit is capable of providing a particular reserve)
1453
1454
1455
1456
1457
        ) // END sum(nuRescapable)
        / p_stepLength(m, f, t)

    =G=

1458
    // Ramping capability of units without online variable
1459
    - (
Topi Rasku's avatar
Topi Rasku committed
1460
1461
        + ( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') )
            ${not uft_online(unit, f, t)}
1462
        + sum(t_invest(t_)${ ord(t_)<=ord(t) },
Topi Rasku's avatar
Topi Rasku committed
1463
            + v_invest_LP(unit, t_)
1464
                ${not uft_online(unit, f, t) and unit_investLP(unit)}
1465
                * p_gnu(grid, node, unit, 'unitSizeTot')
Topi Rasku's avatar
Topi Rasku committed
1466
            + v_invest_MIP(unit, t_)
1467
                ${not uft_online(unit, f, t) and unit_investMIP(unit)}
1468
1469
1470
1471
1472
1473
                * p_gnu(grid, node, unit, 'unitSizeTot')
          )
      )
        * p_gnu(grid, node, unit, 'maxRampDown')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]

1474
    // Ramping capability of units that are online
1475
    - (
Topi Rasku's avatar
Topi Rasku committed
1476
1477
1478
1479
        + 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)}
1480
1481
1482
1483
1484
      )
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * p_gnu(grid, node, unit, 'maxRampDown')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]

1485
    // Shutdown of generation units according to maxRampDown
1486
1487
1488
1489
1490
1491
1492
1493
1494
    - [
        + v_shutdown_LP(unit, s, f, t)
            ${  uft_onlineLP(unit, f, t) }
        + v_shutdown_MIP(unit, s, f, t)
            ${  uft_onlineMIP(unit, f, t) }
        ]
        ${  gnu_output(grid, node, unit)
            and not uft_shutdownTrajectory(unit, f, t)
            }
1495
        * p_gnu(grid, node, unit, 'unitSizeTot')
1496
1497
1498
        * p_gnu(grid, node, unit, 'maxRampDown')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]
    // Generation units not be able to ramp from min. load to zero within one time interval according to their maxRampDown
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
    - [
        + v_shutdown_LP(unit, s, f, t)
            ${  uft_onlineLP(unit, f, t) }
        + v_shutdown_MIP(unit, s, f, t)
            ${  uft_onlineMIP(unit, f, t) }
        ]
        ${  gnu_output(grid, node, unit)
            and not uft_shutdownTrajectory(unit, f, t)
            and ( + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                      + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                      + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                    ) // END sum(effGroup)
                    / p_stepLength(m, f, t)
                  - p_gnu(grid, node, unit, 'maxRampDown')
                      * 60 > 0
                )
        }
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                ) // END sum(effGroup)
                / p_stepLength(m, f, t)
            - p_gnu(grid, node, unit, 'maxRampDown')
                * 60   // Unit conversion from [p.u./min] to [p.u./h]
          ) // END * v_shutdown
1526

1527
1528
    // Units in shutdown phase need to keep up with the shutdown ramp rate
    - p_gnu(grid, node, unit, 'unitSizeGen')
1529
1530
        * [
            + sum(shutdownCounter(unit, counter)${t_active(t+dt_trajectory(counter)) and uft_shutdownTrajectory(unit, f, t)}, // Sum over the shutdown intervals
1531
1532
1533
1534
1535
1536
                + [
                    + 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)) }
                    ]
1537
                    * [
1538
1539
1540
                        + p_gnu(grid, node, unit, 'maxRampDown')${ not shutdownCounter(unit, counter-1) } // Normal maxRampDown limit applies to the time interval when v_shutdown happens, i.e. over the change from online to offline (symmetrical to v_startup)
                        + p_unit(unit, 'rampSpeedFromMinLoad')${ shutdownCounter(unit, counter-1) } // Normal trajectory ramping
                        + ( p_gnu(grid, node, unit, 'maxRampDown') - p_unit(unit, 'rampSpeedFromMinLoad') )${ shutdownCounter(unit, counter-1) and not shutdownCounter(unit, counter-2) } // Ramp speed adjusted for the first shutdown interval
1541
1542
1543
                            * ( p_u_shutdownTimeIntervalsCeil(unit) - p_u_shutdownTimeIntervals(unit) )
                        ]
                ) // END sum(shutdownCounter)
1544
            // Units need to be able to shut down after shut down trajectory
1545
1546
1547
1548
1549
1550
1551
            + [
                + v_shutdown_LP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
                    ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t)) }
                + v_shutdown_MIP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
                    ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t)) }
                ]
                ${uft_shutdownTrajectory(unit, f, t)}
1552
1553
1554
1555
1556
                * [
                    + p_unit(unit, 'rampSpeedFromMinload')
                    + ( p_gnu(grid, node, unit, 'maxRampDown') - p_unit(unit, 'rampSpeedFromMinLoad') )${ sum(shutdownCounter(unit, counter), 1) = 1 } // Ramp speed adjusted if the unit has only one shutdown interval
                        * ( p_u_shutdownTimeIntervalsCeil(unit) - p_u_shutdownTimeIntervals(unit) )
                    ]
1557
1558
            ]
        * 60 // Unit conversion from [p.u./min] to [p.u./h]
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571

    // Consumption units not be able to ramp from zero to min. load within one time interval according to their maxRampDown
    - sum(unitStarttype(unit, starttype)${   uft_online(unit, f, t)
                                             and gnu_input(grid, node, unit)
                                             and ( + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                                                       + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                                                       + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                                                     ) // END sum(effGroup)
                                                       / p_stepLength(m, f, t)
                                                   - p_gnu(grid, node, unit, 'maxRampDown')
                                                       * 60 > 0
                                                   )
                                             },
1572
1573
1574
1575
        + v_startup_LP(unit, starttype, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_startup_MIP(unit, starttype, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
      ) // END sum(starttype)
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
              ) // END sum(effGroup)
                / p_stepLength(m, f, t)
            - p_gnu(grid, node, unit, 'maxRampDown')
                * 60   // Unit conversion from [p.u./min] to [p.u./h]
          ) // END * v_startup
1587
1588
;

1589
1590
* --- Ramps separated into upward and downward ramps --------------------------

1591
1592
1593
1594
1595
q_rampUpDown(ms(m, s), gnuft_ramp(grid, node, unit, f, t))
    ${  ord(t) > msStart(m, s) + 1
        and msft(m, s, f, t)
        and sum(slack, gnuft_rampCost(grid, node, unit, slack, f, t))
        } ..
1596

1597
    // Ramp speed of the unit?
1598
    + v_genRamp(grid, node, unit, s, f, t)
1599

1600
    =E=
1601

1602
1603
    // Upward and downward ramp categories
    + sum(slack${ gnuft_rampCost(grid, node, unit, slack, f, t) },
1604
1605
        + v_genRampUpDown(grid, node, unit, slack, s, f, t)$upwardSlack(slack)
        - v_genRampUpDown(grid, node, unit, slack, s, f, t)$downwardSlack(slack)
1606
      ) // END sum(slack)
1607
1608
1609
1610
1611
1612

    // Start-up of generation units to min. load (not counted in the ramping costs)
    + sum(unitStarttype(unit, starttype)${   uft_online(unit, f, t)
                                             and gnu_output(grid, node, unit)
                                             and not uft_startupTrajectory(unit, f, t)
                                             },
1613
1614
1615
1616
        + v_startup_LP(unit, starttype, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_startup_MIP(unit, starttype, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
      ) // END sum(starttype)
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
              ) // END sum(effGroup)
                / p_stepLength(m, f, t)
          ) // END * v_startup

    // Generation units in the run-up phase need to keep up with the run-up rate (not counted in the ramping costs)
    + p_gnu(grid, node, unit, 'unitSizeGen')
        * 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
1631
1632
1633
1634
1635
1636
                + [
                    + 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))}
                    ]
1637
                    * [
1638
1639
1640
1641
1642
1643
                        + p_uCounter_runUpMin(unit, counter)${ not runUpCounter(unit, counter-1) } // Ramp speed adjusted for the first run-up interval
                            / p_stepLength(m, f, t) // Ramp is the change of v_gen divided by interval length
                        + p_unit(unit, 'rampSpeedToMinLoad')${ runUpCounter(unit, counter-1) and runUpCounter(unit, counter+1) } // Normal trajectory ramping in the middle of the trajectory
                            * 60 // Unit conversion from [p.u./min] into [p.u./h]
                        + p_u_minRampSpeedInLastRunUpInterval(unit)${ runUpCounter(unit, counter-1) and not runUpCounter(unit, counter+1) } // Ramp speed adjusted for the last run-up interval
                            * 60 // Unit conversion from [p.u./min] into [p.u./h]
1644
1645
1646
1647
1648
                        ]
                ) // END sum(runUpCounter)
            ) // END sum(unitStarttype)

    // Shutdown of consumption units from min. load (not counted in the ramping costs)
1649
1650
1651
1652
1653
1654
    + [
        + v_shutdown_LP(unit, s, f, t)
            ${ uft_onlineLP(unit, f, t) and gnu_input(grid, node, unit)}
        + v_shutdown_MIP(unit, s, f, t)
            ${ uft_onlineMIP(unit, f, t) and gnu_input(grid, node, unit)}
        ]
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                ) // END sum(effGroup)
                / p_stepLength(m, f, t)
          ) // END * v_shutdown

    // Shutdown of generation units from min. load (not counted in the ramping costs)
1665
1666
1667
1668
1669
1670
    - [
        + v_shutdown_LP(unit, s, f, t)
            ${ uft_onlineLP(unit, f, t) and gnu_output(grid, node, unit) and not uft_shutdownTrajectory(unit, f, t)}
        + v_shutdown_MIP(unit, s, f, t)
            ${ uft_onlineMIP(unit, f, t) and gnu_output(grid, node, unit) and not uft_shutdownTrajectory(unit, f, t)}
        ]
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
                ) // END sum(effGroup)
                / p_stepLength(m, f, t)
          ) // END * v_shutdown

    // Generation units in shutdown phase need to keep up with the shutdown ramp rate (not counted in the ramping costs)
    - p_gnu(grid, node, unit, 'unitSizeGen')
        * [
            + sum(shutdownCounter(unit, counter)${t_active(t+dt_trajectory(counter)) and uft_shutdownTrajectory(unit, f, t)}, // Sum over the shutdown intervals
1684
1685
                + [
                    + v_shutdown_LP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1686
                        ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))}
1687
                    + v_shutdown_MIP(unit, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1688
                        ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))}
1689
                    ]
1690
                    * [
1691
1692
1693
1694
1695
                        // Note that ramping happening during shutdown trajectory when ord(counter) = 1 is considered 'normal ramping' and causes ramping costs
                        + p_u_minRampSpeedInFirstShutdownInterval(unit)${ not shutdownCounter(unit, counter-2) and shutdownCounter(unit, counter-1) } // Ramp speed adjusted for the first shutdown interval
                            * 60 // Unit conversion from [p.u./min] into [p.u./h]
                        + p_unit(unit, 'rampSpeedFromMinLoad')${ shutdownCounter(unit, counter-2) } // Normal trajectory ramping in the middle of the trajectory
                            * 60 // Unit conversion from [p.u./min] into [p.u./h]
1696
1697
1698
                        ]
                ) // END sum(shutdownCounter)
            // Units need to be able to shut down after shut down trajectory
1699
1700
            + [
                + v_shutdown_LP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
1701
                    ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))}
1702
                + v_shutdown_MIP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
1703
                    ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))}
1704
                ]
1705
1706
                * sum(shutdownCounter(unit, counter)${not shutdownCounter(unit, counter+1)}, p_uCounter_shutdownMin(unit, counter)) // Minimum generation level at the last shutdown interval
                / p_stepLength(m, f, t) // Ramp is the change of v_gen divided by interval length
1707
1708
1709
1710
1711
1712
            ]

    // Start-up of consumption units to min. load (not counted in the ramping costs)
    - sum(unitStarttype(unit, starttype)${   uft_online(unit, f, t)
                                             and gnu_input(grid, node, unit)
                                             },
1713
1714
1715
1716
        + v_startup_LP(unit, starttype, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_startup_MIP(unit, starttype, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
1717
1718
1719
1720
1721
1722
1723
1724
1725
      ) // END sum(starttype)
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * (
            + sum(suft(effGroup, unit, f, t), // Uses the minimum 'lb' for the current efficiency approximation
                + p_effGroupUnit(effGroup, unit, 'lb')${not ts_effGroupUnit(effGroup, unit, 'lb', f, t)}
                + ts_effGroupUnit(effGroup, unit, 'lb', f, t)
              ) // END sum(effGroup)
                / p_stepLength(m, f, t)
          ) // END * v_startup
1726
1727
;

Niina Helistö's avatar
Niina Helistö committed
1728
* --- Upward and downward ramps constrained by slack boundaries ---------------
1729

1730
1731
1732
1733
q_rampSlack(ms(m, s), gnuft_rampCost(grid, node, unit, slack, f, t))
    ${  ord(t) > msStart(m, s) + 1
        and msft(m, s, f, t)
        } ..
1734

1735
    // Directional ramp speed of the unit?
1736
    + v_genRampUpDown(grid, node, unit, slack, s, f, t)
1737

1738
    =L=
1739
1740

    // Ramping capability of units without an online variable
1741
1742
1743
    + (
        + ( p_gnu(grid, node, unit, 'maxGen') + p_gnu(grid, node, unit, 'maxCons') )${not uft_online(unit, f, t)}
        + sum(t_invest(t_)${ ord(t_)<=ord(t) },
1744
            + v_invest_LP(unit, t_)${not uft_online(unit, f, t) and unit_investLP(unit)}
1745
                * p_gnu(grid, node, unit, 'unitSizeTot')
1746
            + v_invest_MIP(unit, t_)${not uft_online(unit, f, t) and unit_investMIP(unit)}
1747
1748
1749
1750
1751
                * p_gnu(grid, node, unit, 'unitSizeTot')
          )
      )
        * p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]
1752
1753

    // Ramping capability of units with an online variable
1754
    + (
Topi Rasku's avatar
Topi Rasku committed
1755
1756
1757
1758
        + 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)}
1759
1760
1761
1762
      )
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]
1763

1764
    // Shutdown of units from above min. load and ramping happening during the first interval of the shutdown trajectory (commented out in the other v_shutdown term below)
1765
1766
1767
1768
1769
1770
    + [
        + v_shutdown_LP(unit, s, f, t)
            ${ uft_onlineLP(unit, f, t) }
        + v_shutdown_MIP(unit, s, f, t)
            ${ uft_onlineMIP(unit, f, t) }
        ]
1771
1772
1773
        * p_gnu(grid, node, unit, 'unitSizeTot')
        * p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')
        * 60   // Unit conversion from [p.u./min] to [p.u./h]
1774
1775
1776
1777
1778

    // Generation units in the last step of their run-up phase
    + p_gnu(grid, node, unit, 'unitSizeGen')
        * 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
1779
1780
                + [
                    + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1781
                        ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1782
                    + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1783
                        ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1784
                    ]
1785
1786
1787
1788
1789
1790
1791
1792
                    * [
                        + p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')${ not runUpCounter(unit, counter+1) } // Ramp speed adjusted for the last run-up interval
                            * ( p_u_runUpTimeIntervalsCeil(unit) - p_u_runUpTimeIntervals(unit) )
                        ]
                    * 60 // Unit conversion from [p.u./min] into [p.u./h]
                ) // END sum(runUpCounter)
            ) // END sum(unitStarttype)

1793
    // Generation units in the first step of their shutdown phase and ramping from online to offline state
1794
1795
1796
    + p_gnu(grid, node, unit, 'unitSizeGen')
        * [
            + sum(shutdownCounter(unit, counter)${t_active(t+dt_trajectory(counter)) and uft_shutdownTrajectory(unit, f, t)}, // Sum over the shutdown intervals
1797
1798
1799
1800
1801
1802
                + [
                    + 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)) }
                    ]
1803
                    * [
1804
                        //+ p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')${ not shutdownCounter(unit, counter-1) } // Note that ramping happening during shutdown trajectory when ord(counter) = 1 is considered 'normal ramping' and causes ramping costs (calculated above in the other v_shutdown term)
1805
                        + p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')${ shutdownCounter(unit, counter-1) and not shutdownCounter(unit, counter-2) } // Ramp speed adjusted for the first shutdown interval
1806
1807
1808
                            * ( p_u_shutdownTimeIntervalsCeil(unit) - p_u_shutdownTimeIntervals(unit) )
                        ]
                ) // END sum(shutdownCounter)
1809
            // First step can also be the last step
1810
1811
1812
1813
1814
1815
            + [
                + v_shutdown_LP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
                    ${uft_onlineLP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))}
                + v_shutdown_MIP(unit, s, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))
                    ${uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_toShutdown(unit, t)), t+dt_toShutdown(unit, t))}
                ]
1816
1817
                + p_gnuBoundaryProperties(grid, node, unit, slack, 'rampLimit')${ sum(shutdownCounter(unit, counter), 1) = 1 } // Ramp speed adjusted if the unit has only one shutdown interval
                    * ( p_u_shutdownTimeIntervalsCeil(unit) - p_u_shutdownTimeIntervals(unit) )
1818
1819
            ]
        * 60 // Unit conversion from [p.u./min] to [p.u./h]
1820
;
1821

1822
1823
* --- Fixed Output Ratio ------------------------------------------------------

1824
1825
1826
q_outputRatioFixed(gngnu_fixedOutputRatio(grid, node, grid_, node_, unit), sft(s, f, t))
    ${  uft(unit, f, t)
        } ..
1827
1828

    // Generation in grid
1829
    + v_gen(grid, node, unit, s, f, t)
1830
        / p_gnu(grid, node, unit, 'conversionFactor')
1831
1832
1833
1834

    =E=

    // Generation in grid_
1835
    + v_gen(grid_, node_, unit, s, f, t)
1836
        / p_gnu(grid_, node_, unit, 'conversionFactor')
1837
;
1838
1839
1840

* --- Constrained Output Ratio ------------------------------------------------

1841
1842
1843
q_outputRatioConstrained(gngnu_constrainedOutputRatio(grid, node, grid_, node_, unit), sft(s, f, t))
    ${  uft(unit, f, t)
        } ..
1844
1845

    // Generation in grid
1846
    + v_gen(grid, node, unit, s, f, t)
1847
        / p_gnu(grid, node, unit, 'conversionFactor')
1848
1849
1850
1851

    =G=

    // Generation in grid_
1852
    + v_gen(grid_, node_, unit, s, f, t)
1853
        / p_gnu(grid_, node_, unit, 'conversionFactor')
Juha Kiviluoma's avatar
Juha Kiviluoma committed
1854
;
1855
1856
1857

* --- Direct Input-Output Conversion ------------------------------------------

1858
1859
1860
q_conversionDirectInputOutput(s_active(s), suft(effDirect(effGroup), unit, f, t))
    ${  sft(s, f, t)
        }..
1861
1862

    // Sum over endogenous energy inputs
1863
    - sum(gnu_input(grid, node, unit)${not p_gnu(grid, node, unit, 'doNotOutput')},
1864
        + v_gen(grid, node, unit, s, f, t)
1865
1866
1867
1868
        ) // END sum(gnu_input)

    // Sum over fuel energy inputs
    + sum(uFuel(unit, 'main', fuel),
1869
        + v_fuelUse(fuel, unit, s, f, t)
1870
1871
1872
1873
1874
1875
        ) // END sum(uFuel)

    =E=

    // Sum over energy outputs
    + sum(gnu_output(grid, node, unit),
1876
        + v_gen(grid, node, unit, s, f, t)
1877
            * [ // efficiency rate
1878
                + p_effUnit(effGroup, unit, effGroup, 'slope')${ not ts_effUnit(effGroup, unit, effGroup, 'slope', f, t) }
1879
                + ts_effUnit(effGroup, unit, effGroup, 'slope', f, t)
1880
1881
1882
                ] // END * v_gen
        ) // END sum(gnu_output)

1883
    // Consumption of keeping units online (no-load fuel use)
1884
1885
1886
    + sum(gnu_output(grid, node, unit),
        + p_gnu(grid, node, unit, 'unitSizeGen')
        ) // END sum(gnu_output)
1887
        * [ // Unit online state
Topi Rasku's avatar
Topi Rasku committed
1888
1889
1890
1891
            + 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)}
1892
1893
1894
1895
1896

            // 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
1897
1898
                    + [
                        + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1899
                            ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1900
                        + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1901
                            ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1902
                        ]
1903
                        * p_uCounter_runUpMin(unit, counter)
1904
1905
1906
1907
1908
                        / 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
1909
1910
1911
1912
1913
1914
                + [
                    + 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)) }
                    ]
1915
                    * p_uCounter_shutdownMin(unit, counter)
1916
1917
                        / p_unit(unit, 'op00') // Scaling the p_uCounter_shutdown using minload
                ) // END sum(shutdownCounter)
1918
1919
            ] // END * sum(gnu_output)
        * [
1920
1921
            + p_effGroupUnit(effGroup, unit, 'section')${not ts_effUnit(effGroup, unit, effDirect, 'section', f, t)}
            + ts_effUnit(effGroup, unit, effGroup, 'section', f, t)
1922
            ] // END * sum(gnu_output)
1923
;
1924
* --- Incremental Heat Rate Conversion ------------------------------------------
1925

1926
1927
1928
q_conversionIncHR(s_active(s), suft(effIncHR(effGroup), unit, f, t))
    ${  sft(s, f, t)
        }..
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943

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

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

    =E=

    // Sum over energy outputs
    + sum(gnu_output(grid, node, unit),
1944
1945
1946
1947
1948
        + sum(hr,
            + v_gen_inc(grid, node, unit, hr, s, f, t) // output of each heat rate segment
            * [
                + p_unit(unit, hr) // heat rate
                / 3.6 // unit conversion from [GJ/MWh] into [MWh/MWh]
1949
                ] // END * v_gen_inc
1950
1951
            ) // END sum(hr)
        ) // END sum(gnu_output)
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963

    // Consumption of keeping units online (no-load fuel use)
    + sum(gnu_output(grid, node, unit),
        + p_gnu(grid, node, unit, 'unitSizeGen')
        ) // END sum(gnu_output)
        * [ // 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
1964
1965
                    + [
                        + v_startup_LP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1966
                            ${ uft_onlineLP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1967
                        + v_startup_MIP(unit, starttype, s, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter))
1968
                            ${ uft_onlineMIP_withPrevious(unit, f+df(f, t+dt_trajectory(counter)), t+dt_trajectory(counter)) }
1969
                        ]
1970
                        * p_uCounter_runUpMin(unit, counter)
1971
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_runUp using minload
1972
1973
1974
                    ) // END sum(runUpCounter)
                ) // END sum(unitStarttype)
            // Shutdown 'online state'
1975
1976
1977
1978
1979
1980
1981
1982
1983
            + 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)) }
                    ]
1984
                    * p_uCounter_shutdownMin(unit, counter)
1985
                        / p_unit(unit, 'hrop00') // Scaling the p_uCounter_shutdown using minload
1986
1987
1988
                ) // END sum(shutdownCounter)
            ] // END * sum(gnu_output)
        * [
1989
            + p_effUnit(effGroup, unit, effGroup, 'section')${not ts_effUnit(effGroup, unit, effIncHR, 'section', f, t)}
1990
1991
1992
1993
1994
            + ts_effUnit(effGroup, unit, effGroup, 'section', f, t)
            ] // END * sum(gnu_output)
;

* --- Incremental Heat Rate Conversion ------------------------------------------
1995

1996
1997
1998
1999
q_conversionIncHRMaxGen(gn(grid, node), s_active(s), suft(effIncHR(effGroup), unit, f, t))
    ${  sft(s, f, t)
        and gnu_output(grid, node, unit)
        } ..
2000