openTEPES 4.18.4__py3-none-any.whl → 4.18.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openTEPES/9n_PTDF/oT_Data_Demand_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_Duration_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_Emission_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_EnergyInflows_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_EnergyOutflows_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_Generation_9n_PTDF.csv +17 -0
- openTEPES/9n_PTDF/oT_Data_Inertia_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_Network_9n_PTDF.csv +14 -0
- openTEPES/9n_PTDF/oT_Data_NodeLocation_9n_PTDF.csv +10 -0
- openTEPES/9n_PTDF/oT_Data_OperatingReserveDown_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_OperatingReserveUp_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_Option_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_Parameter_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_Period_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_RESEnergy_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_ReserveMargin_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_Scenario_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_Stage_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Data_VariableEmissionCost_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableFuelCost_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMaxConsumption_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMaxEnergy_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMaxGeneration_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMaxStorage_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMinConsumption_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMinEnergy_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMinGeneration_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariableMinStorage_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Data_VariablePTDF_9n_PTDF.csv +8740 -0
- openTEPES/9n_PTDF/oT_Data_VariableTTCBck_9n_PTDF.csv +8739 -0
- openTEPES/9n_PTDF/oT_Data_VariableTTCFrw_9n_PTDF.csv +8739 -0
- openTEPES/9n_PTDF/oT_Dict_AreaToRegion_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Dict_Area_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Dict_Circuit_9n_PTDF.csv +3 -0
- openTEPES/9n_PTDF/oT_Dict_Generation_9n_PTDF.csv +17 -0
- openTEPES/9n_PTDF/oT_Dict_Line_9n_PTDF.csv +3 -0
- openTEPES/9n_PTDF/oT_Dict_LoadLevel_9n_PTDF.csv +8737 -0
- openTEPES/9n_PTDF/oT_Dict_NodeToZone_9n_PTDF.csv +10 -0
- openTEPES/9n_PTDF/oT_Dict_Node_9n_PTDF.csv +10 -0
- openTEPES/9n_PTDF/oT_Dict_Period_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Dict_Region_9n_PTDF.csv +31 -0
- openTEPES/9n_PTDF/oT_Dict_Scenario_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Dict_Stage_9n_PTDF.csv +2 -0
- openTEPES/9n_PTDF/oT_Dict_Storage_9n_PTDF.csv +3 -0
- openTEPES/9n_PTDF/oT_Dict_Technology_9n_PTDF.csv +7 -0
- openTEPES/9n_PTDF/oT_Dict_ZoneToArea_9n_PTDF.csv +10 -0
- openTEPES/9n_PTDF/oT_Dict_Zone_9n_PTDF.csv +10 -0
- openTEPES/RTS-GMLC/oT_Data_VariableMinGeneration_RTS-GMLC.csv +1 -1
- openTEPES/RTS-GMLC_6y/oT_Data_VariableFuelCost_RTS-GMLC_6y.csv +1 -1
- openTEPES/RTS-GMLC_6y/oT_Dict_AreaToRegion_RTS-GMLC_6y.csv +4 -4
- openTEPES/RTS-GMLC_6y/oT_Dict_Area_RTS-GMLC_6y.csv +4 -4
- openTEPES/RTS-GMLC_6y/oT_Dict_Circuit_RTS-GMLC_6y.csv +5 -5
- openTEPES/RTS-GMLC_6y/oT_Dict_Line_RTS-GMLC_6y.csv +3 -3
- openTEPES/RTS-GMLC_6y/oT_Dict_NodeToZone_RTS-GMLC_6y.csv +74 -74
- openTEPES/RTS-GMLC_6y/oT_Dict_Region_RTS-GMLC_6y.csv +2 -2
- openTEPES/RTS-GMLC_6y/oT_Dict_Scenario_RTS-GMLC_6y.csv +2 -2
- openTEPES/RTS-GMLC_6y/oT_Dict_Storage_RTS-GMLC_6y.csv +3 -3
- openTEPES/RTS-GMLC_6y/oT_Dict_Technology_RTS-GMLC_6y.csv +10 -10
- openTEPES/RTS-GMLC_6y/oT_Dict_ZoneToArea_RTS-GMLC_6y.csv +22 -22
- openTEPES/RTS-GMLC_6y/oT_Dict_Zone_RTS-GMLC_6y.csv +22 -22
- openTEPES/__init__.py +1 -1
- openTEPES/openTEPES.py +26 -17
- openTEPES/openTEPES_InputData.py +1335 -1188
- openTEPES/openTEPES_Main.py +3 -3
- openTEPES/openTEPES_ModelFormulation.py +413 -156
- openTEPES/openTEPES_OutputResults.py +263 -206
- openTEPES/openTEPES_ProblemSolving.py +35 -28
- openTEPES/sSEP/oT_Data_DemandHydrogen_sSEP.csv +8736 -8736
- openTEPES/sSEP/oT_Data_Generation_sSEP.csv +1 -1
- openTEPES/sSEP/oT_Data_HydroOutflows_sSEP.csv +1 -1
- {opentepes-4.18.4.dist-info → opentepes-4.18.6.dist-info}/METADATA +65 -46
- {opentepes-4.18.4.dist-info → opentepes-4.18.6.dist-info}/RECORD +75 -28
- {opentepes-4.18.4.dist-info → opentepes-4.18.6.dist-info}/WHEEL +1 -1
- {opentepes-4.18.4.dist-info → opentepes-4.18.6.dist-info}/entry_points.txt +0 -0
- {opentepes-4.18.4.dist-info → opentepes-4.18.6.dist-info/licenses}/LICENSE +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) -
|
|
2
|
+
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - September 19, 2025
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import time
|
|
@@ -150,9 +150,9 @@ def GenerationOperationModelFormulationObjFunct(OptModel, mTEPES, pIndLogConsole
|
|
|
150
150
|
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pStartUpCost [ nr] * OptModel.vStartUp [p,sc,n,nr] +
|
|
151
151
|
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pShutDownCost [ nr] * OptModel.vShutDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr) +
|
|
152
152
|
sum(mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ nr] * OptModel.vReserveUp [p,sc,n,nr] +
|
|
153
|
-
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ nr] * OptModel.vReserveDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and mTEPES.
|
|
153
|
+
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ nr] * OptModel.vReserveDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and mTEPES.pIndOperReserveGen[nr] == 0) +
|
|
154
154
|
sum(mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveUp [p,sc,n,eh] +
|
|
155
|
-
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveDown [p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh and mTEPES.
|
|
155
|
+
mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveDown [p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh and mTEPES.pIndOperReserveCon[eh] == 0) +
|
|
156
156
|
sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pLinearVarCost [p,sc,n,bo] * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (p,bo) in mTEPES.pbo) +
|
|
157
157
|
sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pLinearOMCost [ re] * OptModel.vTotalOutput [p,sc,n,re] for re in mTEPES.re if (p,re) in mTEPES.pre) )
|
|
158
158
|
setattr(OptModel, f'eTotalGCost_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eTotalGCost, doc='system variable generation operation cost [MEUR]'))
|
|
@@ -403,118 +403,163 @@ def GenerationOperationModelFormulationDemand(OptModel, mTEPES, pIndLogConsole,
|
|
|
403
403
|
print('eSystemInertia ... ', len(getattr(OptModel, f'eSystemInertia_{p}_{sc}_{st}')), ' rows')
|
|
404
404
|
|
|
405
405
|
def eOperReserveUp(OptModel,n,ar):
|
|
406
|
-
if
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
406
|
+
# Skip if there are no upward operating reserves
|
|
407
|
+
if not mTEPES.pOperReserveUp[p,sc,n,ar]:
|
|
408
|
+
return Constraint.Skip
|
|
409
|
+
# Skip if there are no generators in this area which can provide reserves
|
|
410
|
+
if (sum(1 for nr in n2a[ar] if (mTEPES.pIndOperReserveGen[nr] == 0 or mTEPES.pIndOperReserveCon[nr] == 0 ))
|
|
411
|
+
+ sum(1 for eh in e2a[ar] if (mTEPES.pIndOperReserveGen[eh] == 0 or mTEPES.pIndOperReserveCon[eh] == 0 )) == 0):
|
|
412
412
|
return Constraint.Skip
|
|
413
|
+
|
|
414
|
+
return sum(OptModel.vReserveUp [p,sc,n,nr] for nr in n2a[ar] if mTEPES.pIndOperReserveGen[nr] == 0 and (p,nr) in mTEPES.pnr) + sum(OptModel.vESSReserveUp [p,sc,n,eh] for eh in e2a[ar] if mTEPES.pIndOperReserveCon[eh] == 0 and (p,eh) in mTEPES.peh) == mTEPES.pOperReserveUp[p,sc,n,ar]
|
|
415
|
+
|
|
413
416
|
setattr(OptModel, f'eOperReserveUp_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eOperReserveUp, doc='up operating reserve [GW]'))
|
|
414
417
|
|
|
415
418
|
if pIndLogConsole == 1:
|
|
416
419
|
print('eOperReserveUp ... ', len(getattr(OptModel, f'eOperReserveUp_{p}_{sc}_{st}')), ' rows')
|
|
417
420
|
|
|
418
421
|
def eOperReserveDw(OptModel,n,ar):
|
|
419
|
-
if
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
422
|
+
# Skip if there are no downward operating reserves
|
|
423
|
+
if not mTEPES.pOperReserveDw[p,sc,n,ar]:
|
|
424
|
+
return Constraint.Skip
|
|
425
|
+
# Skip if there are no generators in this area which can provide reserves
|
|
426
|
+
if (sum(1 for nr in n2a[ar] if (mTEPES.pIndOperReserveGen[nr] == 0 or mTEPES.pIndOperReserveCon[nr] == 0 ))
|
|
427
|
+
+ sum(1 for eh in e2a[ar] if (mTEPES.pIndOperReserveGen[eh] == 0 or mTEPES.pIndOperReserveCon[eh] == 0 )) == 0):
|
|
425
428
|
return Constraint.Skip
|
|
429
|
+
|
|
430
|
+
return sum(OptModel.vReserveDown[p,sc,n,nr] for nr in n2a[ar] if mTEPES.pIndOperReserveGen[nr] == 0 and (p,nr) in mTEPES.pnr) + sum(OptModel.vESSReserveDown[p,sc,n,eh] for eh in e2a[ar] if mTEPES.pIndOperReserveCon[eh] == 0 and (p,eh) in mTEPES.peh) == mTEPES.pOperReserveDw[p,sc,n,ar]
|
|
431
|
+
|
|
426
432
|
setattr(OptModel, f'eOperReserveDw_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eOperReserveDw, doc='down operating reserve [GW]'))
|
|
427
433
|
|
|
428
434
|
if pIndLogConsole == 1:
|
|
429
435
|
print('eOperReserveDw ... ', len(getattr(OptModel, f'eOperReserveDw_{p}_{sc}_{st}')), ' rows')
|
|
430
436
|
|
|
431
437
|
def eReserveMinRatioDwUp(OptModel,n,nr):
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
else:
|
|
438
|
+
# Skip if there is no minimum up/down reserve ratio
|
|
439
|
+
if not mTEPES.pMinRatioDwUp:
|
|
435
440
|
return Constraint.Skip
|
|
441
|
+
# Skip if no reserves are needed in the Area where the generator is located
|
|
442
|
+
if sum(mTEPES.pOperReserveUp[p,sc,n,ar] + mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2n[nr]) == 0:
|
|
443
|
+
return Constraint.Skip
|
|
444
|
+
# Skip if generator cannot provide reserves while generating power
|
|
445
|
+
if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0 or mTEPES.pIndOperReserveGen[nr]:
|
|
446
|
+
return Constraint.Skip
|
|
447
|
+
|
|
448
|
+
return OptModel.vReserveDown[p,sc,n,nr] >= OptModel.vReserveUp[p,sc,n,nr] * mTEPES.pMinRatioDwUp
|
|
449
|
+
|
|
436
450
|
setattr(OptModel, f'eReserveMinRatioDwUp_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.nr, rule=eReserveMinRatioDwUp, doc='minimum ratio down to up operating reserve [GW]'))
|
|
437
451
|
|
|
438
452
|
if pIndLogConsole == 1:
|
|
439
453
|
print('eReserveMinRatioDwUp ... ', len(getattr(OptModel, f'eReserveMinRatioDwUp_{p}_{sc}_{st}')), ' rows')
|
|
440
454
|
|
|
441
455
|
def eReserveMaxRatioDwUp(OptModel,n,nr):
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
456
|
+
# Skip if there is no maximum up/down reserve ratio
|
|
457
|
+
if mTEPES.pMaxRatioDwUp >= 1.0:
|
|
458
|
+
return Constraint.Skip
|
|
459
|
+
# Skip if no reserves are needed in the Area where the generator is located
|
|
460
|
+
if sum(mTEPES.pOperReserveUp[p,sc,n,ar] + mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2n[nr]) == 0:
|
|
445
461
|
return Constraint.Skip
|
|
462
|
+
# Skip if generator cannot provide reserves while generating power
|
|
463
|
+
if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0 or mTEPES.pIndOperReserveGen[nr]:
|
|
464
|
+
return Constraint.Skip
|
|
465
|
+
|
|
466
|
+
return OptModel.vReserveDown[p,sc,n,nr] <= OptModel.vReserveUp[p,sc,n,nr] * mTEPES.pMaxRatioDwUp
|
|
467
|
+
|
|
446
468
|
setattr(OptModel, f'eReserveMaxRatioDwUp_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.nr, rule=eReserveMaxRatioDwUp, doc='maximum ratio down to up operating reserve [GW]'))
|
|
447
469
|
|
|
448
470
|
if pIndLogConsole == 1:
|
|
449
471
|
print('eReserveMaxRatioDwUp ... ', len(getattr(OptModel, f'eReserveMaxRatioDwUp_{p}_{sc}_{st}')), ' rows')
|
|
450
472
|
|
|
451
473
|
def eRsrvMinRatioDwUpESS(OptModel,n,eh):
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
474
|
+
# Skip if there is no minimum up/down reserve ratio
|
|
475
|
+
if not mTEPES.pMinRatioDwUp:
|
|
476
|
+
return Constraint.Skip
|
|
477
|
+
# Skip if no reserves are needed in the Area where the generator is located
|
|
478
|
+
if sum(mTEPES.pOperReserveUp[p,sc,n,ar] + mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2n[nr]) == 0:
|
|
479
|
+
return Constraint.Skip
|
|
480
|
+
# Skip if generator cannot provide reserves while generating power
|
|
481
|
+
if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0 or mTEPES.pIndOperReserveCon[nr]:
|
|
455
482
|
return Constraint.Skip
|
|
483
|
+
|
|
484
|
+
return OptModel.vESSReserveDown[p,sc,n,eh] >= OptModel.vESSReserveUp[p,sc,n,eh] * mTEPES.pMinRatioDwUp
|
|
456
485
|
setattr(OptModel, f'eRsrvMinRatioDwUpESS_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eRsrvMinRatioDwUpESS, doc='minimum ratio down to up operating reserve [GW]'))
|
|
457
486
|
|
|
458
487
|
if pIndLogConsole == 1:
|
|
459
488
|
print('eRsrvMinRatioDwUpESS ... ', len(getattr(OptModel, f'eRsrvMinRatioDwUpESS_{p}_{sc}_{st}')), ' rows')
|
|
460
489
|
|
|
461
490
|
def eRsrvMaxRatioDwUpESS(OptModel,n,eh):
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
491
|
+
# Skip if there is no maximum up/down reserve ratio
|
|
492
|
+
if mTEPES.pMaxRatioDwUp >= 1.0:
|
|
493
|
+
return Constraint.Skip
|
|
494
|
+
# Skip if no reserves are needed in the Area where the generator is located
|
|
495
|
+
if sum(mTEPES.pOperReserveUp[p,sc,n,ar] + mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2n[nr]) == 0:
|
|
465
496
|
return Constraint.Skip
|
|
497
|
+
# Skip if generator cannot provide reserves while generating power
|
|
498
|
+
if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0 or mTEPES.pIndOperReserveCon[nr]:
|
|
499
|
+
return Constraint.Skip
|
|
500
|
+
|
|
501
|
+
return OptModel.vESSReserveDown[p,sc,n,eh] <= OptModel.vESSReserveUp[p,sc,n,eh] * mTEPES.pMaxRatioDwUp
|
|
502
|
+
|
|
466
503
|
setattr(OptModel, f'eRsrvMaxRatioDwUpESS_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eRsrvMaxRatioDwUpESS, doc='maximum ratio down to up operating reserve [GW]'))
|
|
467
504
|
|
|
468
505
|
if pIndLogConsole == 1:
|
|
469
506
|
print('eRsrvMaxRatioDwUpESS ... ', len(getattr(OptModel, f'eRsrvMaxRatioDwUpESS_{p}_{sc}_{st}')), ' rows')
|
|
470
507
|
|
|
508
|
+
|
|
471
509
|
def eReserveUpIfEnergy(OptModel,n,es):
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
510
|
+
# When ESS units offer operating reserves, they must be able to provide the corresponding energy
|
|
511
|
+
# This means they must have enough stored energy to provide all reserves if they were to have 100% activation
|
|
512
|
+
|
|
513
|
+
# Skip if generator is not available in the period
|
|
514
|
+
if (p,es) not in mTEPES.pes:
|
|
515
|
+
return Constraint.Skip
|
|
516
|
+
# Skip if generator cannot provide operating reserves while generating power
|
|
517
|
+
if mTEPES.pIndOperReserveGen[es] or mTEPES.pMaxPower2ndBlock [p,sc,n,es] == 0:
|
|
478
518
|
return Constraint.Skip
|
|
519
|
+
# Skip if no upward reserves are needed in the area where the generator is located
|
|
520
|
+
if sum(mTEPES.pOperReserveUp[p,sc,n,ar] for ar in a2e[es]) == 0:
|
|
521
|
+
return Constraint.Skip
|
|
522
|
+
# Skip if the ESS has no charging capabilities and receives no Inflows
|
|
523
|
+
if (mTEPES.pTotalMaxCharge[es] and mTEPES.pTotalEnergyInflows[es]) == 0:
|
|
524
|
+
return Constraint.Skip
|
|
525
|
+
# Skip if the duration of the LoadLevel is 0
|
|
526
|
+
if not mTEPES.pDuration[p,sc,n]():
|
|
527
|
+
return Constraint.Skip
|
|
528
|
+
|
|
529
|
+
# Avoid division by 0 if unit has no minimum power
|
|
530
|
+
if mTEPES.pMinPowerElec[p,sc,n,es] == 0:
|
|
531
|
+
return ((OptModel.vOutput2ndBlock[p,sc,n,es] + OptModel.vReserveUp[p,sc,n,es]) ) / math.sqrt(mTEPES.pEfficiency[es])<= (OptModel.vESSInventory[p,sc,n,es] - mTEPES.pMinStorage[p,sc,n,es]) / mTEPES.pDuration[p,sc,n]()
|
|
532
|
+
else:
|
|
533
|
+
return ((OptModel.vOutput2ndBlock[p,sc,n,es] + OptModel.vReserveUp[p,sc,n,es]) / mTEPES.pMinPowerElec[p,sc,n,es] + 1) / math.sqrt(mTEPES.pEfficiency[es]) <= (OptModel.vESSInventory[p,sc,n,es] - mTEPES.pMinStorage[p,sc,n,es]) / mTEPES.pDuration[p,sc,n]() / mTEPES.pMinPowerElec[p,sc,n,es]
|
|
534
|
+
|
|
479
535
|
setattr(OptModel, f'eReserveUpIfEnergy_{p}_{sc}_{st}', Constraint(mTEPES.nesc, rule=eReserveUpIfEnergy, doc='up operating reserve if energy available [GW]'))
|
|
480
536
|
|
|
481
537
|
if pIndLogConsole == 1:
|
|
482
538
|
print('eReserveUpIfEnergy ... ', len(getattr(OptModel, f'eReserveUpIfEnergy_{p}_{sc}_{st}')), ' rows')
|
|
483
539
|
|
|
484
|
-
def
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
return OptModel.vReserveDown[p,sc,n,es] <= (mTEPES.pMaxStorage[p,sc,n,es] - OptModel.vESSInventory[p,sc,n,es]) / mTEPES.pDuration[p,sc,n]()
|
|
488
|
-
else:
|
|
489
|
-
return Constraint.Skip
|
|
490
|
-
else:
|
|
491
|
-
return Constraint.Skip
|
|
492
|
-
setattr(OptModel, f'eReserveDwIfEnergy_{p}_{sc}_{st}', Constraint(mTEPES.nesc, rule=eReserveDwIfEnergy, doc='down operating reserve if energy available [GW]'))
|
|
493
|
-
|
|
494
|
-
if pIndLogConsole == 1:
|
|
495
|
-
print('eReserveDwIfEnergy ... ', len(getattr(OptModel, f'eReserveDwIfEnergy_{p}_{sc}_{st}')), ' rows')
|
|
540
|
+
def eESSReserveDwIfEnergy(OptModel,n,es):
|
|
541
|
+
# When ESS units offer operating reserves, they must be able to provide the corresponding energy
|
|
542
|
+
# This means they must have enough stored energy to provide all reserves if they were to have 100% activation
|
|
496
543
|
|
|
497
|
-
|
|
498
|
-
if
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
544
|
+
# Skip if generator is not available in the period
|
|
545
|
+
if (p,es) not in mTEPES.pes:
|
|
546
|
+
return Constraint.Skip
|
|
547
|
+
# Skip if generator cannot provide operating reserves while generating power
|
|
548
|
+
if mTEPES.pIndOperReserveGen[es] or mTEPES.pMaxPower2ndBlock [p,sc,n,es] == 0:
|
|
549
|
+
return Constraint.Skip
|
|
550
|
+
# Skip if no downward reserves are needed in the area where the generator is located
|
|
551
|
+
if sum(mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2e[es]) == 0:
|
|
552
|
+
return Constraint.Skip
|
|
553
|
+
# Skip if the duration of the LoadLevel is 0
|
|
554
|
+
if not mTEPES.pDuration[p,sc,n]():
|
|
504
555
|
return Constraint.Skip
|
|
505
|
-
setattr(OptModel, f'eESSReserveUpIfEnergy_{p}_{sc}_{st}', Constraint(mTEPES.nesc, rule=eESSReserveUpIfEnergy, doc='up operating reserve if energy available [GW]'))
|
|
506
|
-
|
|
507
|
-
if pIndLogConsole == 1:
|
|
508
|
-
print('eESSReserveUpIfEnergy ... ', len(getattr(OptModel, f'eESSReserveUpIfEnergy_{p}_{sc}_{st}')), ' rows')
|
|
509
556
|
|
|
510
|
-
|
|
511
|
-
if mTEPES.
|
|
512
|
-
|
|
513
|
-
return OptModel.vESSReserveDown[p,sc,n,es] <= OptModel.vESSInventory[p,sc,n,es] / mTEPES.pDuration[p,sc,n]()
|
|
514
|
-
else:
|
|
515
|
-
return Constraint.Skip
|
|
557
|
+
# Avoid division by 0 if unit has no minimum power
|
|
558
|
+
if mTEPES.pMinCharge[p,sc,n,es] == 0:
|
|
559
|
+
return OptModel.vCharge2ndBlock[p,sc,n,es] + OptModel.vESSReserveDown[p,sc,n,es] * math.sqrt(mTEPES.pEfficiency[es]) <= (mTEPES.pMaxStorage[p,sc,n,es] - OptModel.vESSInventory[p,sc,n,es]) / mTEPES.pDuration[p,sc,n]()
|
|
516
560
|
else:
|
|
517
|
-
return
|
|
561
|
+
return ((OptModel.vCharge2ndBlock[p,sc,n,es] + OptModel.vESSReserveDown[p,sc,n,es]) / mTEPES.pMinCharge[p,sc,n,es] + 1) * math.sqrt(mTEPES.pEfficiency[es]) <= (mTEPES.pMaxStorage[p,sc,n,es] - OptModel.vESSInventory[p,sc,n,es]) / mTEPES.pDuration[p,sc,n]() / mTEPES.pMinCharge[p,sc,n,es]
|
|
562
|
+
|
|
518
563
|
setattr(OptModel, f'eESSReserveDwIfEnergy_{p}_{sc}_{st}', Constraint(mTEPES.nesc, rule=eESSReserveDwIfEnergy, doc='down operating reserve if energy available [GW]'))
|
|
519
564
|
|
|
520
565
|
if pIndLogConsole == 1:
|
|
@@ -591,14 +636,14 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
|
|
|
591
636
|
if (p,es) in mTEPES.pes and (mTEPES.pTotalMaxCharge[es] or mTEPES.pTotalEnergyInflows[es]):
|
|
592
637
|
if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == mTEPES.pStorageTimeStep[es]:
|
|
593
638
|
if es not in mTEPES.ec:
|
|
594
|
-
return mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] + mTEPES.pEfficiency[es]*OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
639
|
+
return mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
595
640
|
else:
|
|
596
|
-
return OptModel.vIniInventory[p,sc,n,es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] + mTEPES.pEfficiency[es]*OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
641
|
+
return OptModel.vIniInventory[p,sc,n,es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
597
642
|
elif (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) > mTEPES.pStorageTimeStep[es]:
|
|
598
643
|
if es not in mTEPES.ec:
|
|
599
|
-
return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] + mTEPES.pEfficiency[es]*OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
644
|
+
return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
600
645
|
else:
|
|
601
|
-
return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] + mTEPES.pEfficiency[es]*OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
646
|
+
return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
|
|
602
647
|
else:
|
|
603
648
|
return Constraint.Skip
|
|
604
649
|
else:
|
|
@@ -646,7 +691,7 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
|
|
|
646
691
|
# Hydro units have commitment while ESS units are implicitly always committed
|
|
647
692
|
if eh not in mTEPES.h:
|
|
648
693
|
# ESS units only need this constraint when they can offer operating reserves and the systems demands reserves
|
|
649
|
-
if mTEPES.
|
|
694
|
+
if mTEPES.pIndOperReserveCon[eh] != 0 or not sum(mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2e[eh]):
|
|
650
695
|
return Constraint.Skip
|
|
651
696
|
# ESS case equation
|
|
652
697
|
return (OptModel.vCharge2ndBlock[p, sc, n, eh] + OptModel.vESSReserveDown[p, sc, n, eh]) / mTEPES.pMaxCharge2ndBlock[p, sc, n, eh] <= 1.0
|
|
@@ -660,13 +705,21 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
|
|
|
660
705
|
print('eMaxCharge ... ', len(getattr(OptModel, f'eMaxCharge_{p}_{sc}_{st}')), ' rows')
|
|
661
706
|
|
|
662
707
|
def eMinCharge(OptModel, n, eh):
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
else:
|
|
708
|
+
# Skip if ESS is not available in the period
|
|
709
|
+
if (p,eh) not in mTEPES.peh:
|
|
710
|
+
return Constraint.Skip
|
|
711
|
+
# Skip if ESS cannot provide reserves while consuming power
|
|
712
|
+
if mTEPES.pIndOperReserveCon[eh]:
|
|
669
713
|
return Constraint.Skip
|
|
714
|
+
# Skip if no reserves are demanded in the area where the ESS is located
|
|
715
|
+
if sum(mTEPES.pOperReserveUp[p, sc, n, ar] for ar in a2e[eh]) == 0:
|
|
716
|
+
return Constraint.Skip
|
|
717
|
+
# Skip if the ESS cannot consume at variable power
|
|
718
|
+
if not mTEPES.pMaxCharge2ndBlock[p, sc, n, eh]:
|
|
719
|
+
return Constraint.Skip
|
|
720
|
+
|
|
721
|
+
return OptModel.vCharge2ndBlock[p, sc, n, eh] - OptModel.vESSReserveUp[p, sc, n, eh] >= 0.0
|
|
722
|
+
|
|
670
723
|
setattr(OptModel, f'eMinCharge_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eMinCharge, doc='min charge of an ESS [p.u.]'))
|
|
671
724
|
|
|
672
725
|
if pIndLogConsole == 1:
|
|
@@ -817,57 +870,132 @@ def GenerationOperationModelFormulationReservoir(OptModel, mTEPES, pIndLogConsol
|
|
|
817
870
|
if pIndLogConsole == 1:
|
|
818
871
|
print('eMinVolume2Comm ... ', len(getattr(OptModel, f'eMinVolume2Comm_{p}_{sc}_{st}')), ' rows')
|
|
819
872
|
|
|
820
|
-
def
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
873
|
+
def eTrbReserveUpIfUpstream(OptModel,n,h):
|
|
874
|
+
# There must be enough water upstream of the turbine to produce all the possible offered power (scheduled + reserves)
|
|
875
|
+
|
|
876
|
+
# Skip if generator is not available in the period
|
|
877
|
+
if (p,h) not in mTEPES.ph:
|
|
878
|
+
return Constraint.Skip
|
|
879
|
+
# Skip if turbine cannot provide reserves
|
|
880
|
+
if mTEPES.pIndOperReserveGen[h]:
|
|
881
|
+
return Constraint.Skip
|
|
882
|
+
# Skip if no reserves are demanded in the area where the turbine is located
|
|
883
|
+
if sum(mTEPES.pOperReserveUp[p, sc, n, ar] for ar in a2h[h]) == 0:
|
|
884
|
+
return Constraint.Skip
|
|
885
|
+
# Skip if turbine cannot generate at variable power
|
|
886
|
+
if mTEPES.pMaxPower2ndBlock [p,sc,n,h] == 0:
|
|
887
|
+
return Constraint.Skip
|
|
888
|
+
# Skip if LoalLevel has duration 0
|
|
889
|
+
if mTEPES.pDuration[p, sc, n]() == 0:
|
|
827
890
|
return Constraint.Skip
|
|
828
|
-
|
|
891
|
+
|
|
892
|
+
# Avoid division by 0 if turbine has no minimum power
|
|
893
|
+
if mTEPES.pMinPowerElec[p, sc, n, h] == 0:
|
|
894
|
+
return OptModel.vOutput2ndBlock[p, sc, n, h] + OptModel.vReserveUp[p, sc, n, h] <= sum(OptModel.vReservoirVolume[p, sc, n, rs] - mTEPES.pMinVolume[p, sc, n, rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]()
|
|
895
|
+
else:
|
|
896
|
+
return (OptModel.vOutput2ndBlock[p, sc, n, h] + OptModel.vReserveUp[p, sc, n, h]) / mTEPES.pMinPowerElec[p, sc, n, h] + OptModel.vCommitment[p, sc, n, h] <= sum(OptModel.vReservoirVolume[p, sc, n, rs] - mTEPES.pMinVolume[p, sc, n, rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]() / mTEPES.pMinPowerElec[p, sc, n, h]
|
|
897
|
+
|
|
898
|
+
setattr(OptModel, f'eTrbReserveUpIfUpstream_{p}_{sc}_{st}', Constraint(mTEPES.nhc, rule=eTrbReserveUpIfUpstream, doc='up operating reserve if energy available [GW]'))
|
|
829
899
|
|
|
830
900
|
if pIndLogConsole == 1:
|
|
831
|
-
print('
|
|
901
|
+
print('eTrbReserveUpIfUpstream ... ', len(getattr(OptModel, f'eTrbReserveUpIfUpstream_{p}_{sc}_{st}')), ' rows')
|
|
832
902
|
|
|
833
|
-
def
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
903
|
+
def eTrbReserveUpIfDownstream(OptModel,n,h):
|
|
904
|
+
#There must be enough spare reservoir capacity downstream of a turbine to fit all the possible water (scheduled + reserves)
|
|
905
|
+
|
|
906
|
+
# Skip if generator is not available in the period
|
|
907
|
+
if (p,h) not in mTEPES.ph:
|
|
908
|
+
return Constraint.Skip
|
|
909
|
+
# Skip if turbine cannot provide reserves
|
|
910
|
+
if mTEPES.pIndOperReserveGen[h]:
|
|
911
|
+
return Constraint.Skip
|
|
912
|
+
# Skip if no reserves are demanded in the area where the turbine is located
|
|
913
|
+
if sum(mTEPES.pOperReserveUp[p, sc, n, ar] for ar in a2h[h]) == 0:
|
|
840
914
|
return Constraint.Skip
|
|
841
|
-
|
|
915
|
+
# Skip if turbine cannot generate at variable power
|
|
916
|
+
if mTEPES.pMaxPower2ndBlock [p,sc,n,h] == 0:
|
|
917
|
+
return Constraint.Skip
|
|
918
|
+
# Skip if LoalLevel has duration 0
|
|
919
|
+
if mTEPES.pDuration[p, sc, n]() == 0:
|
|
920
|
+
return Constraint.Skip
|
|
921
|
+
|
|
922
|
+
# Avoid division by 0 if turbine has no minimum power
|
|
923
|
+
if mTEPES.pMinPowerElec[p, sc, n, h] == 0:
|
|
924
|
+
return OptModel.vOutput2ndBlock[p, sc, n, h] + OptModel.vReserveUp[p, sc, n, h] <= sum(mTEPES.pMaxVolume[p, sc, n, rs] - OptModel.vReservoirVolume[p, sc, n, rs] for rs in mTEPES.rs if (h,rs) in mTEPES.h2r) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]()
|
|
925
|
+
else:
|
|
926
|
+
return (OptModel.vOutput2ndBlock[p, sc, n, h] + OptModel.vReserveUp[p, sc, n, h]) / mTEPES.pMinPowerElec[p, sc, n, h] + OptModel.vCommitment[p, sc, n, h] <= sum(mTEPES.pMaxVolume[p, sc, n, rs] - OptModel.vReservoirVolume[p, sc, n, rs] for rs in mTEPES.rs if (h,rs) in mTEPES.h2r) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]() / mTEPES.pMinPowerElec[p, sc, n, h]
|
|
927
|
+
|
|
928
|
+
setattr(OptModel, f'eTrbReserveUpIfDownstream_{p}_{sc}_{st}', Constraint(mTEPES.nhc, rule=eTrbReserveUpIfDownstream, doc='up operating reserve if energy available [GW]'))
|
|
842
929
|
|
|
843
930
|
if pIndLogConsole == 1:
|
|
844
|
-
print('
|
|
931
|
+
print('eTrbReserveUpIfDownstream ... ', len(getattr(OptModel, f'eTrbReserveUpIfDownstream_{p}_{sc}_{st}')), ' rows')
|
|
845
932
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
933
|
+
|
|
934
|
+
def ePmpReserveDwIfUpstream(OptModel,n,h):
|
|
935
|
+
# There must be enough reservoir capacity upstream to store all the possible water (scheduled + reserves)
|
|
936
|
+
|
|
937
|
+
# Skip if pump is not available in the period
|
|
938
|
+
if (p, h) not in mTEPES.ph:
|
|
939
|
+
return Constraint.Skip
|
|
940
|
+
# Skip if pump cannot provide reserves
|
|
941
|
+
if mTEPES.pIndOperReserveCon[h]:
|
|
942
|
+
return Constraint.Skip
|
|
943
|
+
# Skip if pump is not connected to any reservoir
|
|
944
|
+
if sum(1 for rs in mTEPES.rs if (h,rs) in mTEPES.p2r) == 0:
|
|
945
|
+
return Constraint.Skip
|
|
946
|
+
# Skip if no reserves are demanded in the area where the turbine is located
|
|
947
|
+
if sum(mTEPES.pOperReserveDw[p, sc, n, ar] for ar in a2h[h]) == 0:
|
|
948
|
+
return Constraint.Skip
|
|
949
|
+
# Skip if pump cannot consume at variable power
|
|
950
|
+
if mTEPES.pMaxCharge2ndBlock[p, sc, n, h] == 0:
|
|
951
|
+
return Constraint.Skip
|
|
952
|
+
# Skip if LoalLevel has duration 0
|
|
953
|
+
if mTEPES.pDuration[p, sc, n]() == 0:
|
|
853
954
|
return Constraint.Skip
|
|
854
|
-
|
|
955
|
+
|
|
956
|
+
# Avoid dividing by 0 if pump has no minimum charge
|
|
957
|
+
if mTEPES.pMinCharge[p,sc,n,h] == 0:
|
|
958
|
+
return (OptModel.vCharge2ndBlock[p,sc,n,h] + OptModel.vESSReserveDown[p,sc,n,h] ) * mTEPES.pEfficiency[h] <= sum(mTEPES.pMaxVolume[p,sc,n,rs] - OptModel.vReservoirVolume[p,sc,n,rs] for rs in mTEPES.rs if (h,rs) in mTEPES.p2r) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]()
|
|
959
|
+
else:
|
|
960
|
+
return ((OptModel.vCharge2ndBlock[p,sc,n,h] + OptModel.vESSReserveDown[p,sc,n,h]) / mTEPES.pMinCharge[p,sc,n,h] + OptModel.vCommitmentCons[p,sc,n,h]) * mTEPES.pEfficiency[h] <= sum(mTEPES.pMaxVolume[p,sc,n,rs] - OptModel.vReservoirVolume[p,sc,n,rs] for rs in mTEPES.rs if (h,rs) in mTEPES.p2r) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]() / mTEPES.pMinCharge[p,sc,n,h]
|
|
961
|
+
|
|
962
|
+
setattr(OptModel, f'ePmpReserveDwIfUpstream_{p}_{sc}_{st}', Constraint(mTEPES.npc, rule=ePmpReserveDwIfUpstream, doc='down operating reserve if energy available [GW]'))
|
|
855
963
|
|
|
856
964
|
if pIndLogConsole == 1:
|
|
857
|
-
print('
|
|
965
|
+
print('ePmpReserveDwIfUpstream ... ', len(getattr(OptModel, f'ePmpReserveDwIfUpstream_{p}_{sc}_{st}')), ' rows')
|
|
858
966
|
|
|
859
|
-
def
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
return Constraint.Skip
|
|
865
|
-
else:
|
|
967
|
+
def ePmpReserveDwIfDownstream(OptModel, n, h):
|
|
968
|
+
# There must be enough water downstream for the pump to draw from in case it needs to operate at the maximum capacity offered (scheduled + reserves)
|
|
969
|
+
|
|
970
|
+
# Skip if pump is not available in the period
|
|
971
|
+
if (p, h) not in mTEPES.ph:
|
|
866
972
|
return Constraint.Skip
|
|
867
|
-
|
|
973
|
+
# Skip if pump cannot provide reserves
|
|
974
|
+
if mTEPES.pIndOperReserveCon[h]:
|
|
975
|
+
return Constraint.Skip
|
|
976
|
+
# Skip if pump is not connected to any reservoir
|
|
977
|
+
if sum(1 for rs in mTEPES.rs if (h, rs) in mTEPES.p2r) == 0:
|
|
978
|
+
return Constraint.Skip
|
|
979
|
+
# Skip if no reserves are demanded in the area where the turbine is located
|
|
980
|
+
if sum(mTEPES.pOperReserveDw[p, sc, n, ar] for ar in a2h[h]) == 0:
|
|
981
|
+
return Constraint.Skip
|
|
982
|
+
# Skip if pump cannot consume at variable power
|
|
983
|
+
if mTEPES.pMaxCharge2ndBlock[p, sc, n, h] == 0:
|
|
984
|
+
return Constraint.Skip
|
|
985
|
+
# Skip if LoalLevel has duration 0
|
|
986
|
+
if mTEPES.pDuration[p, sc, n]() == 0:
|
|
987
|
+
return Constraint.Skip
|
|
988
|
+
|
|
989
|
+
# Avoid dividing by 0 if pump has no minimum charge
|
|
990
|
+
if mTEPES.pMinCharge[p, sc, n, h] == 0:
|
|
991
|
+
return (OptModel.vCharge2ndBlock[p, sc, n, h] + OptModel.vESSReserveDown[p, sc, n, h] ) * mTEPES.pEfficiency[h] <= sum(OptModel.vReservoirVolume[p, sc, n, rs] - mTEPES.pMinVolume[p, sc, n, rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2p) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]()
|
|
992
|
+
else:
|
|
993
|
+
return ((OptModel.vCharge2ndBlock[p, sc, n, h] + OptModel.vESSReserveDown[p, sc, n, h]) / mTEPES.pMinCharge[p, sc, n, h] + OptModel.vCommitmentCons[p, sc, n, h]) * mTEPES.pEfficiency[h] <= sum(OptModel.vReservoirVolume[p, sc, n, rs] - mTEPES.pMinVolume[p, sc, n, rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2p) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p, sc, n]() / mTEPES.pMinCharge[p, sc, n, h]
|
|
994
|
+
|
|
995
|
+
setattr(OptModel, f'ePmpReserveDwIfDownstream_{p}_{sc}_{st}', Constraint(mTEPES.npc, rule=ePmpReserveDwIfDownstream, doc='down operating reserve if energy available [GW]'))
|
|
868
996
|
|
|
869
997
|
if pIndLogConsole == 1:
|
|
870
|
-
print('
|
|
998
|
+
print('ePmpReserveDwIfDownstream ... ', len(getattr(OptModel, f'ePmpReserveDwIfDownstream_{p}_{sc}_{st}')), ' rows')
|
|
871
999
|
|
|
872
1000
|
def eHydroInventory(OptModel,n,rs):
|
|
873
1001
|
if (p,rs) in mTEPES.prs and sum(1 for h in mTEPES.h if (rs,h) in mTEPES.r2h or (h,rs) in mTEPES.h2r or (rs,h) in mTEPES.r2p or (h,rs) in mTEPES.p2r):
|
|
@@ -999,50 +1127,114 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
|
|
|
999
1127
|
if pIndLogConsole == 1:
|
|
1000
1128
|
print('eStableStates ... ', len(getattr(OptModel, f'eStableStates_{p}_{sc}_{st}')), ' rows')
|
|
1001
1129
|
|
|
1002
|
-
def
|
|
1003
|
-
if
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
else:
|
|
1130
|
+
def eMaxCommitmentYearly(OptModel,n,group,nr):
|
|
1131
|
+
# Skip if generator not available on period
|
|
1132
|
+
if (p,nr) not in mTEPES.pnr:
|
|
1133
|
+
return Constraint.Skip
|
|
1134
|
+
# Skip if the generator is not part of the exclusive group
|
|
1135
|
+
if nr not in mTEPES.GeneratorsInYearlyGroup[group]:
|
|
1009
1136
|
return Constraint.Skip
|
|
1010
|
-
|
|
1137
|
+
# Skip if there are one or less generators in the group
|
|
1138
|
+
if len(mTEPES.GeneratorsInYearlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
|
|
1139
|
+
return Constraint.Skip
|
|
1140
|
+
|
|
1141
|
+
return OptModel.vCommitment[p,sc,n,nr] <= OptModel.vMaxCommitmentYearly[p,sc,nr,group]
|
|
1142
|
+
|
|
1143
|
+
setattr(OptModel, f'eMaxCommitmentYearly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsYearly, mTEPES.nr, rule=eMaxCommitmentYearly, doc='maximum of all the commitments [p.u.]'))
|
|
1011
1144
|
|
|
1012
1145
|
if pIndLogConsole == 1:
|
|
1013
|
-
print('
|
|
1146
|
+
print('eMaxCommitmentYearly ... ', len(getattr(OptModel, f'eMaxCommitmentYearly_{p}_{sc}_{st}')), ' rows')
|
|
1014
1147
|
|
|
1015
|
-
def
|
|
1016
|
-
if
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1148
|
+
def eMaxCommitGenYearly(OptModel,n,group,nr):
|
|
1149
|
+
# Skip if generator not available on period
|
|
1150
|
+
if (p,nr) not in mTEPES.pnr:
|
|
1151
|
+
return Constraint.Skip
|
|
1152
|
+
# Skip if the generator is not part of the exclusive group
|
|
1153
|
+
if nr not in mTEPES.GeneratorsInYearlyGroup[group]:
|
|
1154
|
+
return Constraint.Skip
|
|
1155
|
+
# Avoid division by 0. If Maximum power is 0 this equation is not needed anyways
|
|
1156
|
+
if mTEPES.pMaxPowerElec[p, sc, n, nr] == 0:
|
|
1157
|
+
return Constraint.Skip
|
|
1158
|
+
# Skip if there are one or less generators in the group
|
|
1159
|
+
if len(mTEPES.GeneratorsInYearlyGroup[group] & {nr for (p, nr) in mTEPES.pnr}) <= 1:
|
|
1022
1160
|
return Constraint.Skip
|
|
1023
|
-
|
|
1161
|
+
|
|
1162
|
+
return OptModel.vTotalOutput[p,sc,n,nr]/mTEPES.pMaxPowerElec[p,sc,n,nr] <= OptModel.vMaxCommitmentYearly[p,sc,nr,group]
|
|
1163
|
+
|
|
1164
|
+
setattr(OptModel, f'eMaxCommitGenYearly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsYearly,mTEPES.nr, rule=eMaxCommitGenYearly, doc='maximum of all the capacity factors'))
|
|
1024
1165
|
|
|
1025
1166
|
if pIndLogConsole == 1:
|
|
1026
|
-
print('
|
|
1167
|
+
print('eMaxCommitGenYearly ... ', len(getattr(OptModel, f'eMaxCommitGenYearly_{p}_{sc}_{st}')), ' rows')
|
|
1027
1168
|
|
|
1028
|
-
def
|
|
1029
|
-
if
|
|
1030
|
-
|
|
1031
|
-
return OptModel.vMaxCommitment[p,sc,g] + sum(OptModel.vMaxCommitment[p,sc,gg] for gg in mTEPES.g if (gg,g) in mTEPES.g2g) <= 1
|
|
1032
|
-
else:
|
|
1033
|
-
return Constraint.Skip
|
|
1034
|
-
else:
|
|
1169
|
+
def eExclusiveGensYearly(OptModel,group):
|
|
1170
|
+
# Skip if there are one or less generators in the group
|
|
1171
|
+
if len(mTEPES.GeneratorsInYearlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
|
|
1035
1172
|
return Constraint.Skip
|
|
1036
|
-
|
|
1173
|
+
return sum(OptModel.vMaxCommitmentYearly[p, sc, nr, group] + (OptModel.vCommitmentCons[p, sc, nr] if nr in mTEPES.h else 0) for nr in mTEPES.GeneratorsInYearlyGroup[group] if (p, nr) in mTEPES.pnr ) <= 1
|
|
1174
|
+
setattr(OptModel, f'eExclusiveGensYearly_{p}_{sc}_{st}', Constraint(mTEPES.ExclusiveGroupsYearly, rule=eExclusiveGensYearly, doc='mutually exclusive generators'))
|
|
1037
1175
|
|
|
1038
1176
|
if pIndLogConsole == 1:
|
|
1039
|
-
print('
|
|
1177
|
+
print('eExclusiveGensYearly ... ', len(getattr(OptModel, f'eExclusiveGensYearly_{p}_{sc}_{st}')), ' rows')
|
|
1040
1178
|
|
|
1041
1179
|
GeneratingTime = time.time() - StartTime
|
|
1042
1180
|
if pIndLogConsole == 1:
|
|
1043
1181
|
print('Generating generation commitment ... ', round(GeneratingTime), 's')
|
|
1044
1182
|
|
|
1183
|
+
def eMaxCommitmentHourly(OptModel,n,group,nr):
|
|
1184
|
+
# Skip if generator not available on period
|
|
1185
|
+
if (p,nr) not in mTEPES.pnr:
|
|
1186
|
+
return Constraint.Skip
|
|
1187
|
+
# Skip if the generator is not part of the exclusive group
|
|
1188
|
+
if nr not in mTEPES.GeneratorsInHourlyGroup[group]:
|
|
1189
|
+
return Constraint.Skip
|
|
1190
|
+
# Skip if there are one or less generators in the group
|
|
1191
|
+
if len(mTEPES.GeneratorsInHourlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
|
|
1192
|
+
return Constraint.Skip
|
|
1193
|
+
|
|
1194
|
+
return OptModel.vCommitment[p,sc,n,nr] <= OptModel.vMaxCommitmentHourly[p,sc,n,nr,group]
|
|
1195
|
+
|
|
1196
|
+
setattr(OptModel, f'eMaxCommitmentHourly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsHourly, mTEPES.nr, rule=eMaxCommitmentHourly, doc='maximum of all the commitments [p.u.]'))
|
|
1197
|
+
|
|
1198
|
+
if pIndLogConsole == 1:
|
|
1199
|
+
print('eMaxCommitmentHourly ... ', len(getattr(OptModel, f'eMaxCommitmentHourly_{p}_{sc}_{st}')), ' rows')
|
|
1200
|
+
|
|
1201
|
+
def eMaxCommitGenHourly(OptModel,n,group,nr):
|
|
1202
|
+
# Skip if generator not available on period
|
|
1203
|
+
if (p,nr) not in mTEPES.pnr:
|
|
1204
|
+
return Constraint.Skip
|
|
1205
|
+
# Skip if the generator is not part of the exclusive group
|
|
1206
|
+
if nr not in mTEPES.GeneratorsInHourlyGroup[group]:
|
|
1207
|
+
return Constraint.Skip
|
|
1208
|
+
# Avoid division by 0. If Maximum power is 0 this equation is not needed anyways
|
|
1209
|
+
if mTEPES.pMaxPowerElec[p,sc,n,nr] == 0:
|
|
1210
|
+
return Constraint.Skip
|
|
1211
|
+
# Skip if there are one or less generators in the group
|
|
1212
|
+
if len(mTEPES.GeneratorsInHourlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
|
|
1213
|
+
return Constraint.Skip
|
|
1214
|
+
|
|
1215
|
+
return OptModel.vTotalOutput[p,sc,n,nr]/mTEPES.pMaxPowerElec[p,sc,n,nr] <= OptModel.vMaxCommitmentHourly[p,sc,n,nr,group]
|
|
1216
|
+
|
|
1217
|
+
setattr(OptModel, f'eMaxCommitGenHourly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsHourly,mTEPES.nr, rule=eMaxCommitGenHourly, doc='maximum of all the capacity factors'))
|
|
1045
1218
|
|
|
1219
|
+
if pIndLogConsole == 1:
|
|
1220
|
+
print('eMaxCommitGenHourly ... ', len(getattr(OptModel, f'eMaxCommitGenHourly_{p}_{sc}_{st}')), ' rows')
|
|
1221
|
+
|
|
1222
|
+
def eExclusiveGensHourly(OptModel,n,group):
|
|
1223
|
+
# Skip if there are one or less generators in the group
|
|
1224
|
+
# This is written in a different way to the rest of the code to avoid variable shadowing due to comprehension
|
|
1225
|
+
if len(mTEPES.GeneratorsInHourlyGroup[group] & {gen for (period, gen) in mTEPES.pnr if period == p}) <= 1:
|
|
1226
|
+
return Constraint.Skip
|
|
1227
|
+
|
|
1228
|
+
return sum(OptModel.vMaxCommitmentHourly[p,sc,n,nr,group] + (OptModel.vCommitmentCons[p,sc,n,nr] if nr in mTEPES.h else 0) for nr in mTEPES.GeneratorsInHourlyGroup[group] if (p, nr) in mTEPES.pnr ) <= 1
|
|
1229
|
+
|
|
1230
|
+
setattr(OptModel, f'eExclusiveGensHourly_{p}_{sc}_{st}', Constraint(mTEPES.n,mTEPES.ExclusiveGroupsHourly, rule=eExclusiveGensHourly, doc='mutually exclusive generators'))
|
|
1231
|
+
|
|
1232
|
+
if pIndLogConsole == 1:
|
|
1233
|
+
print('eExclusiveGensHourly ... ', len(getattr(OptModel, f'eExclusiveGensHourly_{p}_{sc}_{st}')), ' rows')
|
|
1234
|
+
|
|
1235
|
+
GeneratingTime = time.time() - StartTime
|
|
1236
|
+
if pIndLogConsole == 1:
|
|
1237
|
+
print('Generating generation commitment ... ', round(GeneratingTime), 's')
|
|
1046
1238
|
def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogConsole, p, sc, st):
|
|
1047
1239
|
print('Ramp and min up/down time constraints ****')
|
|
1048
1240
|
|
|
@@ -1308,7 +1500,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1308
1500
|
|
|
1309
1501
|
def eNetCapacity1(OptModel,n,ni,nf,cc):
|
|
1310
1502
|
if mTEPES.pIndBinSingleNode() == 0 and ((ni,nf,cc) in mTEPES.lc or mTEPES.pIndBinLineSwitch[ni,nf,cc] == 1):
|
|
1311
|
-
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.
|
|
1503
|
+
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.pMaxNTCMax[p,sc,n,ni,nf,cc] >= - OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1312
1504
|
else:
|
|
1313
1505
|
return Constraint.Skip
|
|
1314
1506
|
setattr(OptModel, f'eNetCapacity1_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.la, rule=eNetCapacity1, doc='maximum flow by existing network capacity [p.u.]'))
|
|
@@ -1318,7 +1510,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1318
1510
|
|
|
1319
1511
|
def eNetCapacity2(OptModel,n,ni,nf,cc):
|
|
1320
1512
|
if mTEPES.pIndBinSingleNode() == 0 and ((ni,nf,cc) in mTEPES.lc or mTEPES.pIndBinLineSwitch[ni,nf,cc] == 1):
|
|
1321
|
-
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.
|
|
1513
|
+
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.pMaxNTCMax[p,sc,n,ni,nf,cc] <= OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1322
1514
|
else:
|
|
1323
1515
|
return Constraint.Skip
|
|
1324
1516
|
setattr(OptModel, f'eNetCapacity2_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.la, rule=eNetCapacity2, doc='maximum flow by existing network capacity [p.u.]'))
|
|
@@ -1327,7 +1519,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1327
1519
|
print('eNetCapacity2 ... ', len(getattr(OptModel, f'eNetCapacity2_{p}_{sc}_{st}')), ' rows')
|
|
1328
1520
|
|
|
1329
1521
|
def eKirchhoff2ndLaw1(OptModel,n,ni,nf,cc):
|
|
1330
|
-
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pLineX[ni,nf,cc]:
|
|
1522
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pLineX[ni,nf,cc] and mTEPES.pIndPTDF == 0 and mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] + mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc]:
|
|
1331
1523
|
if (ni,nf,cc) in mTEPES.lca:
|
|
1332
1524
|
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.pBigMFlowBck[ni,nf,cc]() - (OptModel.vTheta[p,sc,n,ni] - OptModel.vTheta[p,sc,n,nf]) / mTEPES.pLineX[ni,nf,cc] / mTEPES.pBigMFlowBck[ni,nf,cc]() * mTEPES.pSBase >= - 1 + OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1333
1525
|
else:
|
|
@@ -1340,7 +1532,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1340
1532
|
print('eKirchhoff2ndLaw1 ... ', len(getattr(OptModel, f'eKirchhoff2ndLaw1_{p}_{sc}_{st}')), ' rows')
|
|
1341
1533
|
|
|
1342
1534
|
def eKirchhoff2ndLaw2(OptModel,n,ni,nf,cc):
|
|
1343
|
-
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pLineX[ni,nf,cc]:
|
|
1535
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pLineX[ni,nf,cc] and mTEPES.pIndPTDF == 0 and mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] + mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc]:
|
|
1344
1536
|
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] / mTEPES.pBigMFlowFrw[ni,nf,cc]() - (OptModel.vTheta[p,sc,n,ni] - OptModel.vTheta[p,sc,n,nf]) / mTEPES.pLineX[ni,nf,cc] / mTEPES.pBigMFlowFrw[ni,nf,cc]() * mTEPES.pSBase <= 1 - OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1345
1537
|
else:
|
|
1346
1538
|
return Constraint.Skip
|
|
@@ -1350,7 +1542,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1350
1542
|
print('eKirchhoff2ndLaw2 ... ', len(getattr(OptModel, f'eKirchhoff2ndLaw2_{p}_{sc}_{st}')), ' rows')
|
|
1351
1543
|
|
|
1352
1544
|
def eLineLosses1(OptModel,n,ni,nf,cc):
|
|
1353
|
-
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndBinNetLosses() and
|
|
1545
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndBinNetLosses() and mTEPES.ll and mTEPES.pIndPTDF == 0:
|
|
1354
1546
|
return OptModel.vLineLosses[p,sc,n,ni,nf,cc] >= - 0.5 * mTEPES.pLineLossFactor[ni,nf,cc] * OptModel.vFlowElec[p,sc,n,ni,nf,cc]
|
|
1355
1547
|
else:
|
|
1356
1548
|
return Constraint.Skip
|
|
@@ -1360,7 +1552,7 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1360
1552
|
print('eLineLosses1 ... ', len(getattr(OptModel, f'eLineLosses1_{p}_{sc}_{st}')), ' rows')
|
|
1361
1553
|
|
|
1362
1554
|
def eLineLosses2(OptModel,n,ni,nf,cc):
|
|
1363
|
-
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndBinNetLosses() and
|
|
1555
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndBinNetLosses() and mTEPES.ll and mTEPES.pIndPTDF == 0:
|
|
1364
1556
|
return OptModel.vLineLosses[p,sc,n,ni,nf,cc] >= 0.5 * mTEPES.pLineLossFactor[ni,nf,cc] * OptModel.vFlowElec[p,sc,n,ni,nf,cc]
|
|
1365
1557
|
else:
|
|
1366
1558
|
return Constraint.Skip
|
|
@@ -1369,11 +1561,70 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
|
|
|
1369
1561
|
if pIndLogConsole == 1:
|
|
1370
1562
|
print('eLineLosses2 ... ', len(getattr(OptModel, f'eLineLosses2_{p}_{sc}_{st}')), ' rows')
|
|
1371
1563
|
|
|
1564
|
+
# nodes to generators (g2n)
|
|
1565
|
+
g2n = defaultdict(list)
|
|
1566
|
+
for nd,g in mTEPES.n2g:
|
|
1567
|
+
g2n[nd].append(g)
|
|
1568
|
+
e2n = defaultdict(list)
|
|
1569
|
+
for nd,eh in mTEPES.nd*mTEPES.eh:
|
|
1570
|
+
if (nd,eh) in mTEPES.n2g:
|
|
1571
|
+
e2n[nd].append(eh)
|
|
1572
|
+
|
|
1573
|
+
def eNetPosition(OptModel,n,nd):
|
|
1574
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndPTDF == 1:
|
|
1575
|
+
"""Net position NP_n = Σ P_g in node n − demand"""
|
|
1576
|
+
return (OptModel.vNetPosition[p,sc,n,nd] == sum(OptModel.vTotalOutput[p,sc,n,g] for g in g2n[nd] if (p,g) in mTEPES.pg) - sum(OptModel.vESSTotalCharge[p,sc,n,eh] for eh in e2n[nd] if (p,eh) in mTEPES.peh) + OptModel.vENS[p,sc,n,nd] - mTEPES.pDemandElec[p,sc,n,nd])
|
|
1577
|
+
else:
|
|
1578
|
+
return Constraint.Skip
|
|
1579
|
+
setattr(OptModel, f'eNetPosition_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.nd, rule=eNetPosition, doc='net position [GW]'))
|
|
1580
|
+
|
|
1581
|
+
def eFlowBasedCalcu1(OptModel,n,ni,nf,cc):
|
|
1582
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pIndPTDF == 1 and mTEPES.pIndBinLinePTDF[ni,nf,cc] == 1:
|
|
1583
|
+
if (ni,nf,cc) in mTEPES.lca:
|
|
1584
|
+
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] - sum(mTEPES.pPTDF[p,sc,n,ni,nf,cc,nd] * OptModel.vNetPosition[p,sc,n,nd] for nd in mTEPES.nd if (p,sc,n,ni,nf,cc,nd) in mTEPES.psnland) >= - 1 + OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1585
|
+
else:
|
|
1586
|
+
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] - sum(mTEPES.pPTDF[p,sc,n,ni,nf,cc,nd] * OptModel.vNetPosition[p,sc,n,nd] for nd in mTEPES.nd if (p,sc,n,ni,nf,cc,nd) in mTEPES.psnland) == 0
|
|
1587
|
+
else:
|
|
1588
|
+
return Constraint.Skip
|
|
1589
|
+
setattr(OptModel, f'eFlowBasedCalcu1_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.la, rule=eFlowBasedCalcu1, doc='flow based calculation [p.u.]'))
|
|
1590
|
+
|
|
1591
|
+
if pIndLogConsole == 1:
|
|
1592
|
+
print('eFlowBasedCalcu1 ... ', len(getattr(OptModel, f'eFlowBasedCalcu1_{p}_{sc}_{st}')), ' rows')
|
|
1593
|
+
|
|
1594
|
+
def eFlowBasedCalcu2(OptModel,n,ni,nf,cc):
|
|
1595
|
+
if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pIndPTDF == 1 and mTEPES.pIndBinLinePTDF[ni,nf,cc] == 1:
|
|
1596
|
+
return OptModel.vFlowElec[p,sc,n,ni,nf,cc] - sum(mTEPES.pPTDF[p,sc,n,ni,nf,cc,nd] * OptModel.vNetPosition[p,sc,n,nd] for nd in mTEPES.nd if (p,sc,n,ni,nf,cc,nd) in mTEPES.psnland) <= 1 - OptModel.vLineCommit[p,sc,n,ni,nf,cc]
|
|
1597
|
+
else:
|
|
1598
|
+
return Constraint.Skip
|
|
1599
|
+
setattr(OptModel, f'eFlowBasedCalcu2_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.lca, rule=eFlowBasedCalcu2, doc='flow based calculation [p.u.]'))
|
|
1600
|
+
|
|
1601
|
+
if pIndLogConsole == 1:
|
|
1602
|
+
print('eFlowBasedCalcu2 ... ', len(getattr(OptModel, f'eFlowBasedCalcu2_{p}_{sc}_{st}')), ' rows')
|
|
1603
|
+
|
|
1604
|
+
# def eSecurityMargingTTCFrw(OptModel,n,ni,nf,cc):
|
|
1605
|
+
# if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pIndBinLinePTDF[ni,nf,cc] == 1 and mTEPES.pIndPTDF == 1:
|
|
1606
|
+
# return OptModel.vFlowElec[p,sc,n,ni,nf,cc] <= mTEPES.pVariableTTCFrw[p,sc,n,ni,nf,cc]
|
|
1607
|
+
# else:
|
|
1608
|
+
# return Constraint.Skip
|
|
1609
|
+
# setattr(OptModel, f'eSecurityMargingTTCFrw_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.lca, rule=eSecurityMargingTTCFrw, doc='security margin TTC for flow based calculation [p.u.]'))
|
|
1610
|
+
#
|
|
1611
|
+
# if pIndLogConsole == 1:
|
|
1612
|
+
# print('eSecurityMargingTTCFrw... ', len(getattr(OptModel, f'eSecurityMargingTTCFrw_{p}_{sc}_{st}')), ' rows')
|
|
1613
|
+
#
|
|
1614
|
+
# def eSecurityMargingTTCBck(OptModel,n,ni,nf,cc):
|
|
1615
|
+
# if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pElecNetPeriodIni[ni,nf,cc] <= p and mTEPES.pElecNetPeriodFin[ni,nf,cc] >= p and mTEPES.pIndBinLinePTDF[ni,nf,cc] == 1 and mTEPES.pIndPTDF == 1:
|
|
1616
|
+
# return OptModel.vFlowElec[p,sc,n,ni,nf,cc] >= - mTEPES.pVariableTTCBck[p,sc,n,ni,nf,cc]
|
|
1617
|
+
# else:
|
|
1618
|
+
# return Constraint.Skip
|
|
1619
|
+
# setattr(OptModel, f'eSecurityMargingTTCBck_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.lca, rule=eSecurityMargingTTCBck, doc='security margin TTC for flow based calculation [p.u.]'))
|
|
1620
|
+
#
|
|
1621
|
+
# if pIndLogConsole == 1:
|
|
1622
|
+
# print('eSecurityMargingTTCBck... ', len(getattr(OptModel, f'eSecurityMargingTTCBck_{p}_{sc}_{st}')), ' rows')
|
|
1623
|
+
|
|
1372
1624
|
GeneratingTime = time.time() - StartTime
|
|
1373
1625
|
if pIndLogConsole == 1:
|
|
1374
1626
|
print('Generating network constraints ... ', round(GeneratingTime), 's')
|
|
1375
1627
|
|
|
1376
|
-
|
|
1377
1628
|
def NetworkCycles(mTEPES, pIndLogConsole):
|
|
1378
1629
|
print('Network Cycles Detection ****')
|
|
1379
1630
|
|
|
@@ -1456,25 +1707,31 @@ def CycleConstraints(OptModel, mTEPES, pIndLogConsole, p, sc, st):
|
|
|
1456
1707
|
|
|
1457
1708
|
#%% cycle Kirchhoff's second law with some candidate lines
|
|
1458
1709
|
# this equation is formulated for every AC candidate line included in the cycle
|
|
1459
|
-
def eCycleKirchhoff2ndLawCnd1(OptModel,sc,
|
|
1460
|
-
|
|
1461
|
-
|
|
1710
|
+
def eCycleKirchhoff2ndLawCnd1(OptModel,p,sc,n,cyc,nii,nff,cc):
|
|
1711
|
+
if mTEPES.pIndPTDF == 0:
|
|
1712
|
+
return (sum(OptModel.vFlowElec[p,sc,n,ni,nf,cc] * mTEPES.pLineX[ni,nf,cc] / mTEPES.pSBase for ni,nf in list(zip(mTEPES.ncd[cyc], mTEPES.ncd[cyc][1:] + mTEPES.ncd[cyc][:1])) for cc in mTEPES.cc if (ni,nf,cc) in mTEPES.uctc) -
|
|
1713
|
+
sum(OptModel.vFlowElec[p,sc,n,ni,nf,cc] * mTEPES.pLineX[ni,nf,cc] / mTEPES.pSBase for nf,ni in list(zip(mTEPES.ncd[cyc], mTEPES.ncd[cyc][1:] + mTEPES.ncd[cyc][:1])) for cc in mTEPES.cc if (ni,nf,cc) in mTEPES.uctc) ) / mTEPES.pBigMTheta[cyc,nii,nff,cc] <= 1 - OptModel.vLineCommit[p,sc,n,nii,nff,cc]
|
|
1714
|
+
else:
|
|
1715
|
+
return Constraint.Skip
|
|
1462
1716
|
setattr(OptModel, f'eCycleKirchhoff2ndLawCnd1_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.lcac, rule=eCycleKirchhoff2ndLawCnd1, doc='cycle flow for with some AC candidate lines [rad]'))
|
|
1463
1717
|
|
|
1464
1718
|
if pIndLogConsole == 1:
|
|
1465
1719
|
print('eCycleKirchhoff2ndLC1 ... ', len(getattr(OptModel, f'eCycleKirchhoff2ndLawCnd1_{p}_{sc}_{st}')), ' rows')
|
|
1466
1720
|
|
|
1467
|
-
def eCycleKirchhoff2ndLawCnd2(OptModel,sc,
|
|
1468
|
-
|
|
1469
|
-
|
|
1721
|
+
def eCycleKirchhoff2ndLawCnd2(OptModel,p,sc,n,cyc,nii,nff,cc):
|
|
1722
|
+
if mTEPES.pIndPTDF == 0:
|
|
1723
|
+
return (sum(OptModel.vFlowElec[p,sc,n,ni,nf,cc] * mTEPES.pLineX[ni,nf,cc] / mTEPES.pSBase for ni,nf in list(zip(mTEPES.ncd[cyc], mTEPES.ncd[cyc][1:] + mTEPES.ncd[cyc][:1])) for cc in mTEPES.cc if (ni,nf,cc) in mTEPES.uctc) -
|
|
1724
|
+
sum(OptModel.vFlowElec[p,sc,n,ni,nf,cc] * mTEPES.pLineX[ni,nf,cc] / mTEPES.pSBase for nf,ni in list(zip(mTEPES.ncd[cyc], mTEPES.ncd[cyc][1:] + mTEPES.ncd[cyc][:1])) for cc in mTEPES.cc if (ni,nf,cc) in mTEPES.uctc) ) / mTEPES.pBigMTheta[cyc,nii,nff,cc] >= - 1 + OptModel.vLineCommit[p,sc,n,nii,nff,cc]
|
|
1725
|
+
else:
|
|
1726
|
+
return Constraint.Skip
|
|
1470
1727
|
setattr(OptModel, f'eCycleKirchhoff2ndLawCnd2_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.lcac, rule=eCycleKirchhoff2ndLawCnd2, doc='cycle flow for with some AC candidate lines [rad]'))
|
|
1471
1728
|
|
|
1472
1729
|
if pIndLogConsole == 1:
|
|
1473
1730
|
print('eCycleKirchhoff2ndLC2 ... ', len(getattr(OptModel, f'eCycleKirchhoff2ndLawCnd2_{p}_{sc}_{st}')), ' rows')
|
|
1474
1731
|
|
|
1475
|
-
def eFlowParallelCandidate1(OptModel,sc,
|
|
1476
|
-
if cc < c2 and (ni,nf,cc) in mTEPES.lea and (ni,nf,c2) in mTEPES.lca:
|
|
1477
|
-
return (OptModel.vFlowElec[sc,
|
|
1732
|
+
def eFlowParallelCandidate1(OptModel,p,sc,n,ni,nf,cc,c2):
|
|
1733
|
+
if (cc < c2 and (ni,nf,cc) in mTEPES.lea and (ni,nf,c2) in mTEPES.lca) and mTEPES.pIndPTDF == 0:
|
|
1734
|
+
return (OptModel.vFlowElec[p,sc,n,ni,nf,cc] - OptModel.vFlowElec[p,sc,n,ni,nf,c2] * mTEPES.pLineX[ni,nf,c2] / mTEPES.pLineX[ni,nf,cc]) / max(mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc],mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc]) <= 1 - OptModel.vLineCommit[p,sc,n,ni,nf,c2]
|
|
1478
1735
|
else:
|
|
1479
1736
|
return Constraint.Skip
|
|
1480
1737
|
setattr(OptModel, f'eFlowParallelCandidate1_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.pct, mTEPES.cc, mTEPES.c2, rule=eFlowParallelCandidate1, doc='unitary flow for each AC candidate parallel circuit [p.u.]'))
|
|
@@ -1482,9 +1739,9 @@ def CycleConstraints(OptModel, mTEPES, pIndLogConsole, p, sc, st):
|
|
|
1482
1739
|
if pIndLogConsole == 1:
|
|
1483
1740
|
print('eFlowParallelCnddate1 ... ', len(getattr(OptModel, f'eFlowParallelCandidate1_{p}_{sc}_{st}')), ' rows')
|
|
1484
1741
|
|
|
1485
|
-
def eFlowParallelCandidate2(OptModel,sc,
|
|
1486
|
-
if cc < c2 and (ni,nf,cc) in mTEPES.lea and (ni,nf,c2) in mTEPES.lca:
|
|
1487
|
-
return (OptModel.vFlowElec[sc,
|
|
1742
|
+
def eFlowParallelCandidate2(OptModel,p,sc,n,ni,nf,cc,c2):
|
|
1743
|
+
if (cc < c2 and (ni,nf,cc) in mTEPES.lea and (ni,nf,c2) in mTEPES.lca) and mTEPES.pIndPTDF == 0:
|
|
1744
|
+
return (OptModel.vFlowElec[p,sc,n,ni,nf,cc] - OptModel.vFlowElec[p,sc,n,ni,nf,c2] * mTEPES.pLineX[ni,nf,c2] / mTEPES.pLineX[ni,nf,cc]) / max(mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc],mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc]) >= - 1 + OptModel.vLineCommit[p,sc,n,ni,nf,c2]
|
|
1488
1745
|
else:
|
|
1489
1746
|
return Constraint.Skip
|
|
1490
1747
|
setattr(OptModel, f'eFlowParallelCandidate2_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.pct, mTEPES.cc, mTEPES.c2, rule=eFlowParallelCandidate2, doc='unitary flow for each AC candidate parallel circuit [p.u.]'))
|