openTEPES 4.18.3__py3-none-any.whl → 4.18.5__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.
Files changed (70) hide show
  1. openTEPES/9n_PTDF/oT_Data_Demand_9n_PTDF.csv +8737 -0
  2. openTEPES/9n_PTDF/oT_Data_Duration_9n_PTDF.csv +8737 -0
  3. openTEPES/9n_PTDF/oT_Data_Emission_9n_PTDF.csv +2 -0
  4. openTEPES/9n_PTDF/oT_Data_EnergyInflows_9n_PTDF.csv +8737 -0
  5. openTEPES/9n_PTDF/oT_Data_EnergyOutflows_9n_PTDF.csv +8737 -0
  6. openTEPES/9n_PTDF/oT_Data_Generation_9n_PTDF.csv +17 -0
  7. openTEPES/9n_PTDF/oT_Data_Inertia_9n_PTDF.csv +8737 -0
  8. openTEPES/9n_PTDF/oT_Data_Network_9n_PTDF.csv +14 -0
  9. openTEPES/9n_PTDF/oT_Data_NodeLocation_9n_PTDF.csv +10 -0
  10. openTEPES/9n_PTDF/oT_Data_OperatingReserveDown_9n_PTDF.csv +8737 -0
  11. openTEPES/9n_PTDF/oT_Data_OperatingReserveUp_9n_PTDF.csv +8737 -0
  12. openTEPES/9n_PTDF/oT_Data_Option_9n_PTDF.csv +2 -0
  13. openTEPES/9n_PTDF/oT_Data_Parameter_9n_PTDF.csv +2 -0
  14. openTEPES/9n_PTDF/oT_Data_Period_9n_PTDF.csv +2 -0
  15. openTEPES/9n_PTDF/oT_Data_RESEnergy_9n_PTDF.csv +2 -0
  16. openTEPES/9n_PTDF/oT_Data_ReserveMargin_9n_PTDF.csv +2 -0
  17. openTEPES/9n_PTDF/oT_Data_Scenario_9n_PTDF.csv +2 -0
  18. openTEPES/9n_PTDF/oT_Data_Stage_9n_PTDF.csv +2 -0
  19. openTEPES/9n_PTDF/oT_Data_VariableEmissionCost_9n_PTDF.csv +8737 -0
  20. openTEPES/9n_PTDF/oT_Data_VariableFuelCost_9n_PTDF.csv +8737 -0
  21. openTEPES/9n_PTDF/oT_Data_VariableMaxConsumption_9n_PTDF.csv +8737 -0
  22. openTEPES/9n_PTDF/oT_Data_VariableMaxEnergy_9n_PTDF.csv +8737 -0
  23. openTEPES/9n_PTDF/oT_Data_VariableMaxGeneration_9n_PTDF.csv +8737 -0
  24. openTEPES/9n_PTDF/oT_Data_VariableMaxStorage_9n_PTDF.csv +8737 -0
  25. openTEPES/9n_PTDF/oT_Data_VariableMinConsumption_9n_PTDF.csv +8737 -0
  26. openTEPES/9n_PTDF/oT_Data_VariableMinEnergy_9n_PTDF.csv +8737 -0
  27. openTEPES/9n_PTDF/oT_Data_VariableMinGeneration_9n_PTDF.csv +8737 -0
  28. openTEPES/9n_PTDF/oT_Data_VariableMinStorage_9n_PTDF.csv +8737 -0
  29. openTEPES/9n_PTDF/oT_Data_VariablePTDF_9n_PTDF.csv +8740 -0
  30. openTEPES/9n_PTDF/oT_Data_VariableTTCBck_9n_PTDF.csv +8739 -0
  31. openTEPES/9n_PTDF/oT_Data_VariableTTCFrw_9n_PTDF.csv +8739 -0
  32. openTEPES/9n_PTDF/oT_Dict_AreaToRegion_9n_PTDF.csv +2 -0
  33. openTEPES/9n_PTDF/oT_Dict_Area_9n_PTDF.csv +2 -0
  34. openTEPES/9n_PTDF/oT_Dict_Circuit_9n_PTDF.csv +3 -0
  35. openTEPES/9n_PTDF/oT_Dict_Generation_9n_PTDF.csv +17 -0
  36. openTEPES/9n_PTDF/oT_Dict_Line_9n_PTDF.csv +3 -0
  37. openTEPES/9n_PTDF/oT_Dict_LoadLevel_9n_PTDF.csv +8737 -0
  38. openTEPES/9n_PTDF/oT_Dict_NodeToZone_9n_PTDF.csv +10 -0
  39. openTEPES/9n_PTDF/oT_Dict_Node_9n_PTDF.csv +10 -0
  40. openTEPES/9n_PTDF/oT_Dict_Period_9n_PTDF.csv +2 -0
  41. openTEPES/9n_PTDF/oT_Dict_Region_9n_PTDF.csv +31 -0
  42. openTEPES/9n_PTDF/oT_Dict_Scenario_9n_PTDF.csv +2 -0
  43. openTEPES/9n_PTDF/oT_Dict_Stage_9n_PTDF.csv +2 -0
  44. openTEPES/9n_PTDF/oT_Dict_Storage_9n_PTDF.csv +3 -0
  45. openTEPES/9n_PTDF/oT_Dict_Technology_9n_PTDF.csv +7 -0
  46. openTEPES/9n_PTDF/oT_Dict_ZoneToArea_9n_PTDF.csv +10 -0
  47. openTEPES/9n_PTDF/oT_Dict_Zone_9n_PTDF.csv +10 -0
  48. openTEPES/RTS-GMLC_6y/oT_Dict_AreaToRegion_RTS-GMLC_6y.csv +4 -4
  49. openTEPES/RTS-GMLC_6y/oT_Dict_Area_RTS-GMLC_6y.csv +4 -4
  50. openTEPES/RTS-GMLC_6y/oT_Dict_Circuit_RTS-GMLC_6y.csv +5 -5
  51. openTEPES/RTS-GMLC_6y/oT_Dict_Line_RTS-GMLC_6y.csv +3 -3
  52. openTEPES/RTS-GMLC_6y/oT_Dict_NodeToZone_RTS-GMLC_6y.csv +74 -74
  53. openTEPES/RTS-GMLC_6y/oT_Dict_Region_RTS-GMLC_6y.csv +2 -2
  54. openTEPES/RTS-GMLC_6y/oT_Dict_Scenario_RTS-GMLC_6y.csv +2 -2
  55. openTEPES/RTS-GMLC_6y/oT_Dict_Storage_RTS-GMLC_6y.csv +3 -3
  56. openTEPES/RTS-GMLC_6y/oT_Dict_Technology_RTS-GMLC_6y.csv +10 -10
  57. openTEPES/RTS-GMLC_6y/oT_Dict_ZoneToArea_RTS-GMLC_6y.csv +22 -22
  58. openTEPES/RTS-GMLC_6y/oT_Dict_Zone_RTS-GMLC_6y.csv +22 -22
  59. openTEPES/__init__.py +1 -1
  60. openTEPES/openTEPES.py +137 -65
  61. openTEPES/openTEPES_InputData.py +419 -234
  62. openTEPES/openTEPES_Main.py +2 -2
  63. openTEPES/openTEPES_ModelFormulation.py +469 -180
  64. openTEPES/openTEPES_OutputResults.py +305 -223
  65. openTEPES/openTEPES_ProblemSolving.py +68 -56
  66. {opentepes-4.18.3.dist-info → openTEPES-4.18.5.dist-info}/METADATA +17 -18
  67. {opentepes-4.18.3.dist-info → openTEPES-4.18.5.dist-info}/RECORD +70 -23
  68. {opentepes-4.18.3.dist-info → openTEPES-4.18.5.dist-info}/WHEEL +1 -1
  69. {opentepes-4.18.3.dist-info → openTEPES-4.18.5.dist-info}/LICENSE +0 -0
  70. {opentepes-4.18.3.dist-info → openTEPES-4.18.5.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 17, 2025
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - June 20, 2025
3
3
  """
4
4
 
5
5
  import datetime
@@ -10,7 +10,6 @@ import pandas as pd
10
10
  from collections import defaultdict
11
11
  from pyomo.environ import DataPortal, Set, Param, Var, Binary, NonNegativeReals, NonNegativeIntegers, PositiveReals, PositiveIntegers, Reals, UnitInterval, Any
12
12
 
13
-
14
13
  def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
15
14
  print('Input data ****')
16
15
 
@@ -47,6 +46,20 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
47
46
  dfNodeLocation = pd.read_csv(f'{_path}/oT_Data_NodeLocation_' f'{CaseName}.csv', header=0, index_col=[0 ])
48
47
  dfNetwork = pd.read_csv(f'{_path}/oT_Data_Network_' f'{CaseName}.csv', header=0, index_col=[0,1,2])
49
48
 
49
+ try:
50
+ dfVariableTTCFrw = pd.read_csv(f'{_path}/oT_Data_VariableTTCFrw_' f'{CaseName}.csv', header=[0,1,2 ], index_col=[0,1,2])
51
+ dfVariableTTCBck = pd.read_csv(f'{_path}/oT_Data_VariableTTCBck_' f'{CaseName}.csv', header=[0,1,2 ], index_col=[0,1,2])
52
+ pIndVarTTC = 1
53
+ except:
54
+ pIndVarTTC = 0
55
+ print('**** No variable transmission line TTCs')
56
+ try:
57
+ dfVariablePTDF = pd.read_csv(f'{_path}/oT_Data_VariablePTDF_' f'{CaseName}.csv', header=[0,1,2,3], index_col=[0,1,2])
58
+ pIndPTDF = 1
59
+ except:
60
+ pIndPTDF = 0
61
+ print('**** No flow-based market coupling method')
62
+
50
63
  try:
51
64
  dfReservoir = pd.read_csv(f'{_path}/oT_Data_Reservoir_' f'{CaseName}.csv', header=0, index_col=[0 ])
52
65
  dfVariableMinVolume = pd.read_csv(f'{_path}/oT_Data_VariableMinVolume_' f'{CaseName}.csv', header=0, index_col=[0,1,2])
@@ -76,34 +89,41 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
76
89
  print('**** No heat energy carrier')
77
90
 
78
91
  # substitute NaN by 0
79
- dfOption.fillna (0 , inplace=True)
80
- dfParameter.fillna (0.0, inplace=True)
81
- dfPeriod.fillna (0.0, inplace=True)
82
- dfScenario.fillna (0.0, inplace=True)
83
- dfStage.fillna (0.0, inplace=True)
84
- dfDuration.fillna (0 , inplace=True)
85
- dfReserveMargin.fillna (0.0, inplace=True)
86
- dfEmission.fillna (math.inf , inplace=True)
87
- dfRESEnergy.fillna (0.0, inplace=True)
88
- dfDemand.fillna (0.0, inplace=True)
89
- dfInertia.fillna (0.0, inplace=True)
90
- dfUpOperatingReserve.fillna (0.0, inplace=True)
91
- dfDwOperatingReserve.fillna (0.0, inplace=True)
92
- dfGeneration.fillna (0.0, inplace=True)
93
- dfVariableMinPower.fillna (0.0, inplace=True)
94
- dfVariableMaxPower.fillna (0.0, inplace=True)
95
- dfVariableMinCharge.fillna (0.0, inplace=True)
96
- dfVariableMaxCharge.fillna (0.0, inplace=True)
97
- dfVariableMinStorage.fillna (0.0, inplace=True)
98
- dfVariableMaxStorage.fillna (0.0, inplace=True)
99
- dfVariableMinEnergy.fillna (0.0, inplace=True)
100
- dfVariableMaxEnergy.fillna (0.0, inplace=True)
101
- dfVariableFuelCost.fillna (0.0, inplace=True)
102
- dfVariableEmissionCost.fillna (0.0, inplace=True)
103
- dfEnergyInflows.fillna (0.0, inplace=True)
104
- dfEnergyOutflows.fillna (0.0, inplace=True)
105
- dfNodeLocation.fillna (0.0, inplace=True)
106
- dfNetwork.fillna (0.0, inplace=True)
92
+ dfOption.fillna (0 , inplace=True)
93
+ dfParameter.fillna (0.0, inplace=True)
94
+ dfPeriod.fillna (0.0, inplace=True)
95
+ dfScenario.fillna (0.0, inplace=True)
96
+ dfStage.fillna (0.0, inplace=True)
97
+ dfDuration.fillna (0 , inplace=True)
98
+ dfReserveMargin.fillna (0.0, inplace=True)
99
+ dfEmission.fillna (math.inf , inplace=True)
100
+ dfRESEnergy.fillna (0.0, inplace=True)
101
+ dfDemand.fillna (0.0, inplace=True)
102
+ dfInertia.fillna (0.0, inplace=True)
103
+ dfUpOperatingReserve.fillna (0.0, inplace=True)
104
+ dfDwOperatingReserve.fillna (0.0, inplace=True)
105
+ dfGeneration.fillna ({"Efficiency":1.0}, inplace=True)
106
+ dfGeneration.fillna (0.0, inplace=True)
107
+ dfVariableMinPower.fillna (0.0, inplace=True)
108
+ dfVariableMaxPower.fillna (0.0, inplace=True)
109
+ dfVariableMinCharge.fillna (0.0, inplace=True)
110
+ dfVariableMaxCharge.fillna (0.0, inplace=True)
111
+ dfVariableMinStorage.fillna (0.0, inplace=True)
112
+ dfVariableMaxStorage.fillna (0.0, inplace=True)
113
+ dfVariableMinEnergy.fillna (0.0, inplace=True)
114
+ dfVariableMaxEnergy.fillna (0.0, inplace=True)
115
+ dfVariableFuelCost.fillna (0.0, inplace=True)
116
+ dfVariableEmissionCost.fillna (0.0, inplace=True)
117
+ dfEnergyInflows.fillna (0.0, inplace=True)
118
+ dfEnergyOutflows.fillna (0.0, inplace=True)
119
+ dfNodeLocation.fillna (0.0, inplace=True)
120
+ dfNetwork.fillna (0.0, inplace=True)
121
+
122
+ if pIndVarTTC == 1:
123
+ dfVariableTTCFrw.fillna (0.0, inplace=True)
124
+ dfVariableTTCBck.fillna (0.0, inplace=True)
125
+ if pIndPTDF == 1:
126
+ dfVariablePTDF.fillna (0.0, inplace=True)
107
127
 
108
128
  if pIndHydroTopology == 1:
109
129
  dfReservoir.fillna (0.0, inplace=True)
@@ -140,6 +160,11 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
140
160
  dfEnergyInflows = dfEnergyInflows.where (dfEnergyInflows > 0.0, 0.0)
141
161
  dfEnergyOutflows = dfEnergyOutflows.where (dfEnergyOutflows > 0.0, 0.0)
142
162
 
163
+ if (dfGeneration["Efficiency"] == 0).any():
164
+ print("WARNING: Efficiency values of 0.0 are not valid. They have been changed to 1.0.")
165
+ print("If you want to disable charging, set 'MaximumCharge' to 0.0 or leave it empty.")
166
+ dfGeneration["Efficiency"] = dfGeneration["Efficiency"].where(dfGeneration["Efficiency"] != 0.0, 1.0)
167
+
143
168
  if pIndHydroTopology == 1:
144
169
  dfVariableMinVolume = dfVariableMinVolume.where (dfVariableMinVolume > 0.0, 0.0)
145
170
  dfVariableMaxVolume = dfVariableMaxVolume.where (dfVariableMaxVolume > 0.0, 0.0)
@@ -170,6 +195,12 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
170
195
  print('Energy outflows \n', dfEnergyOutflows.describe (), '\n')
171
196
  print('Electric network \n', dfNetwork.describe (), '\n')
172
197
 
198
+ if pIndVarTTC == 1:
199
+ print('Variable TTC forward \n', dfVariableTTCFrw.describe (), '\n')
200
+ print('Variable TTC backward \n', dfVariableTTCBck.describe (), '\n')
201
+ if pIndPTDF == 1:
202
+ print('Variable PTDF \n', dfVariablePTDF.describe (), '\n')
203
+
173
204
  if pIndHydroTopology == 1:
174
205
  print('Reservoir \n', dfReservoir.describe (), '\n')
175
206
  print('Variable minimum reservoir volume \n', dfVariableMinVolume.describe (), '\n')
@@ -268,40 +299,40 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
268
299
  pass
269
300
 
270
301
  #%% parameters
271
- pIndBinGenInvest = dfOption ['IndBinGenInvest' ].iloc[0].astype('int') # Indicator of binary generation expansion decisions, 0 continuous - 1 binary - 2 no investment variables
272
- pIndBinGenRetire = dfOption ['IndBinGenRetirement'].iloc[0].astype('int') # Indicator of binary generation retirement decisions,0 continuous - 1 binary - 2 no retirement variables
273
- pIndBinRsrInvest = dfOption ['IndBinRsrInvest' ].iloc[0].astype('int') # Indicator of binary reservoir expansion decisions, 0 continuous - 1 binary - 2 no investment variables
274
- pIndBinNetElecInvest = dfOption ['IndBinNetInvest' ].iloc[0].astype('int') # Indicator of binary electric network expansion decisions, 0 continuous - 1 binary - 2 no investment variables
275
- pIndBinNetH2Invest = dfOption ['IndBinNetH2Invest' ].iloc[0].astype('int') # Indicator of binary hydrogen pipeline expansion decisions, 0 continuous - 1 binary - 2 no investment variables
276
- pIndBinNetHeatInvest = dfOption ['IndBinNetHeatInvest'].iloc[0].astype('int') # Indicator of binary heat pipe expansion decisions, 0 continuous - 1 binary - 2 no investment variables
277
- pIndBinGenOperat = dfOption ['IndBinGenOperat' ].iloc[0].astype('int') # Indicator of binary generation operation decisions, 0 continuous - 1 binary
278
- pIndBinSingleNode = dfOption ['IndBinSingleNode' ].iloc[0].astype('int') # Indicator of single node although with electric network, 0 electric network - 1 single node
279
- pIndBinGenRamps = dfOption ['IndBinGenRamps' ].iloc[0].astype('int') # Indicator of ramp constraints, 0 no ramps - 1 ramp constraints
280
- pIndBinGenMinTime = dfOption ['IndBinGenMinTime' ].iloc[0].astype('int') # Indicator of minimum up/downtime constraints, 0 no min time - 1 min time constraints
281
- pIndBinLineCommit = dfOption ['IndBinLineCommit' ].iloc[0].astype('int') # Indicator of binary electric network switching decisions, 0 continuous - 1 binary
282
- pIndBinNetLosses = dfOption ['IndBinNetLosses' ].iloc[0].astype('int') # Indicator of electric network losses, 0 lossless - 1 ohmic losses
283
- pENSCost = dfParameter['ENSCost' ].iloc[0] * 1e-3 # cost of energy not served [MEUR/GWh]
284
- pH2NSCost = dfParameter['HNSCost' ].iloc[0] * 1e-3 # cost of hydrogen not served [MEUR/tH2]
285
- pHeatNSCost = dfParameter['HTNSCost' ].iloc[0] * 1e-3 # cost of heat not served [MEUR/GWh]
286
- pCO2Cost = dfParameter['CO2Cost' ].iloc[0] # cost of CO2 emission [EUR/tCO2]
287
- pEconomicBaseYear = dfParameter['EconomicBaseYear' ].iloc[0] # economic base year [year]
288
- pAnnualDiscRate = dfParameter['AnnualDiscountRate' ].iloc[0] # annual discount rate [p.u.]
289
- pUpReserveActivation = dfParameter['UpReserveActivation'].iloc[0] # upward reserve activation [p.u.]
290
- pDwReserveActivation = dfParameter['DwReserveActivation'].iloc[0] # downward reserve activation [p.u.]
291
- pMinRatioDwUp = dfParameter['MinRatioDwUp' ].iloc[0] # minimum ratio down up operating reserves [p.u.]
292
- pMaxRatioDwUp = dfParameter['MaxRatioDwUp' ].iloc[0] # maximum ratio down up operating reserves [p.u.]
293
- pSBase = dfParameter['SBase' ].iloc[0] * 1e-3 # base power [GW]
294
- pReferenceNode = dfParameter['ReferenceNode' ].iloc[0] # reference node
295
- pTimeStep = dfParameter['TimeStep' ].iloc[0].astype('int') # duration of the unit time step [h]
296
-
297
- pPeriodWeight = dfPeriod ['Weight' ].astype('int') # weights of periods [p.u.]
298
- pScenProb = dfScenario ['Probability' ].astype('float64') # probabilities of scenarios [p.u.]
299
- pStageWeight = dfStage ['Weight' ].astype('float64') # weights of stages
300
- pDuration = dfDuration ['Duration' ] * pTimeStep # duration of load levels [h]
301
- pLevelToStage = dfDuration ['Stage' ] # load levels assignment to stages
302
- pReserveMargin = dfReserveMargin['ReserveMargin' ] # minimum adequacy reserve margin [p.u.]
303
- pEmission = dfEmission ['CO2Emission' ] # maximum CO2 emission [MtCO2]
304
- pRESEnergy = dfRESEnergy ['RESEnergy' ] # minimum RES energy [GWh]
302
+ pIndBinGenInvest = dfOption ['IndBinGenInvest' ].iloc[0].astype('int') # Indicator of binary generation expansion decisions, 0 continuous - 1 binary - 2 no investment variables
303
+ pIndBinGenRetire = dfOption ['IndBinGenRetirement'].iloc[0].astype('int') # Indicator of binary generation retirement decisions,0 continuous - 1 binary - 2 no retirement variables
304
+ pIndBinRsrInvest = dfOption ['IndBinRsrInvest' ].iloc[0].astype('int') # Indicator of binary reservoir expansion decisions, 0 continuous - 1 binary - 2 no investment variables
305
+ pIndBinNetElecInvest = dfOption ['IndBinNetInvest' ].iloc[0].astype('int') # Indicator of binary electric network expansion decisions, 0 continuous - 1 binary - 2 no investment variables
306
+ pIndBinNetH2Invest = dfOption ['IndBinNetH2Invest' ].iloc[0].astype('int') # Indicator of binary hydrogen pipeline expansion decisions, 0 continuous - 1 binary - 2 no investment variables
307
+ pIndBinNetHeatInvest = dfOption ['IndBinNetHeatInvest'].iloc[0].astype('int') # Indicator of binary heat pipe expansion decisions, 0 continuous - 1 binary - 2 no investment variables
308
+ pIndBinGenOperat = dfOption ['IndBinGenOperat' ].iloc[0].astype('int') # Indicator of binary generation operation decisions, 0 continuous - 1 binary
309
+ pIndBinSingleNode = dfOption ['IndBinSingleNode' ].iloc[0].astype('int') # Indicator of single node although with electric network, 0 electric network - 1 single node
310
+ pIndBinGenRamps = dfOption ['IndBinGenRamps' ].iloc[0].astype('int') # Indicator of ramp constraints, 0 no ramps - 1 ramp constraints
311
+ pIndBinGenMinTime = dfOption ['IndBinGenMinTime' ].iloc[0].astype('int') # Indicator of minimum up/downtime constraints, 0 no min time - 1 min time constraints
312
+ pIndBinLineCommit = dfOption ['IndBinLineCommit' ].iloc[0].astype('int') # Indicator of binary electric network switching decisions, 0 continuous - 1 binary
313
+ pIndBinNetLosses = dfOption ['IndBinNetLosses' ].iloc[0].astype('int') # Indicator of electric network losses, 0 lossless - 1 ohmic losses
314
+ pENSCost = dfParameter['ENSCost' ].iloc[0] * 1e-3 # cost of energy not served [MEUR/GWh]
315
+ pH2NSCost = dfParameter['HNSCost' ].iloc[0] * 1e-3 # cost of hydrogen not served [MEUR/tH2]
316
+ pHeatNSCost = dfParameter['HTNSCost' ].iloc[0] * 1e-3 # cost of heat not served [MEUR/GWh]
317
+ pCO2Cost = dfParameter['CO2Cost' ].iloc[0] # cost of CO2 emission [EUR/tCO2]
318
+ pEconomicBaseYear = dfParameter['EconomicBaseYear' ].iloc[0] # economic base year [year]
319
+ pAnnualDiscRate = dfParameter['AnnualDiscountRate' ].iloc[0] # annual discount rate [p.u.]
320
+ pUpReserveActivation = dfParameter['UpReserveActivation'].iloc[0] # upward reserve activation [p.u.]
321
+ pDwReserveActivation = dfParameter['DwReserveActivation'].iloc[0] # downward reserve activation [p.u.]
322
+ pMinRatioDwUp = dfParameter['MinRatioDwUp' ].iloc[0] # minimum ratio down up operating reserves [p.u.]
323
+ pMaxRatioDwUp = dfParameter['MaxRatioDwUp' ].iloc[0] # maximum ratio down up operating reserves [p.u.]
324
+ pSBase = dfParameter['SBase' ].iloc[0] * 1e-3 # base power [GW]
325
+ pReferenceNode = dfParameter['ReferenceNode' ].iloc[0] # reference node
326
+ pTimeStep = dfParameter['TimeStep' ].iloc[0].astype('int') # duration of the unit time step [h]
327
+
328
+ pPeriodWeight = dfPeriod ['Weight' ].astype('int') # weights of periods [p.u.]
329
+ pScenProb = dfScenario ['Probability' ].astype('float64') # probabilities of scenarios [p.u.]
330
+ pStageWeight = dfStage ['Weight' ].astype('float64') # weights of stages
331
+ pDuration = dfDuration ['Duration' ] * pTimeStep # duration of load levels [h]
332
+ pLevelToStage = dfDuration ['Stage' ] # load levels assignment to stages
333
+ pReserveMargin = dfReserveMargin['ReserveMargin' ] # minimum adequacy reserve margin [p.u.]
334
+ pEmission = dfEmission ['CO2Emission' ] # maximum CO2 emission [MtCO2]
335
+ pRESEnergy = dfRESEnergy ['RESEnergy' ] # minimum RES energy [GWh]
305
336
  pDemandElec = dfDemand.reindex (columns=mTEPES.nd, fill_value=0.0) * 1e-3 # electric demand [GW]
306
337
  pSystemInertia = dfInertia.reindex (columns=mTEPES.ar, fill_value=0.0) # inertia [s]
307
338
  pOperReserveUp = dfUpOperatingReserve.reindex (columns=mTEPES.ar, fill_value=0.0) * 1e-3 # upward operating reserve [GW]
@@ -320,20 +351,24 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
320
351
  pEnergyInflows = dfEnergyInflows.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic energy inflows [GW]
321
352
  pEnergyOutflows = dfEnergyOutflows.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic energy outflows [GW]
322
353
 
354
+ if pIndVarTTC == 1:
355
+ pVariableTTCFrw = dfVariableTTCFrw * 1e-3 # variable TTC forward [GW]
356
+ pVariableTTCBck = dfVariableTTCBck * 1e-3 # variable TTC backward [GW]
357
+ if pIndPTDF == 1:
358
+ pVariablePTDF = dfVariablePTDF # variable PTDF [p.u.]
359
+
323
360
  if pIndHydroTopology == 1:
324
361
  pVariableMinVolume = dfVariableMinVolume.reindex (columns=mTEPES.rs, fill_value=0.0) # dynamic variable minimum reservoir volume [hm3]
325
362
  pVariableMaxVolume = dfVariableMaxVolume.reindex (columns=mTEPES.rs, fill_value=0.0) # dynamic variable maximum reservoir volume [hm3]
326
363
  pHydroInflows = dfHydroInflows.reindex (columns=mTEPES.rs, fill_value=0.0) # dynamic hydro inflows [m3/s]
327
364
  pHydroOutflows = dfHydroOutflows.reindex (columns=mTEPES.rs, fill_value=0.0) # dynamic hydro outflows [m3/s]
328
365
 
329
-
330
-
331
366
  if pIndHydrogen == 1:
332
- pDemandH2 = dfDemandHydrogen [mTEPES.nd] # hydrogen demand [tH2/h]
367
+ pDemandH2 = dfDemandHydrogen [mTEPES.nd] # hydrogen demand [tH2/h]
333
368
 
334
369
  if pIndHeat == 1:
335
- pReserveMarginHeat = dfReserveMarginHeat ['ReserveMargin'] # minimum adequacy reserve margin [p.u.]
336
- pDemandHeat = dfDemandHeat [mTEPES.nd] * 1e-3 # heat demand [GW]
370
+ pReserveMarginHeat = dfReserveMarginHeat ['ReserveMargin'] # minimum adequacy reserve margin [p.u.]
371
+ pDemandHeat = dfDemandHeat [mTEPES.nd] * 1e-3 # heat demand [GW]
337
372
 
338
373
  if pTimeStep > 1:
339
374
 
@@ -344,6 +379,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
344
379
  pDataFrame = pDataFrame.rolling(pTimeStep).mean()
345
380
  pDataFrame.fillna(0.0, inplace=True)
346
381
  return pDataFrame
382
+
347
383
  # Apply the ProcessParameter function to each DataFrame
348
384
  pDemandElec = ProcessParameter(pDemandElec, pTimeStep)
349
385
  pSystemInertia = ProcessParameter(pSystemInertia, pTimeStep)
@@ -362,6 +398,12 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
362
398
  pEnergyInflows = ProcessParameter(pEnergyInflows, pTimeStep)
363
399
  pEnergyOutflows = ProcessParameter(pEnergyOutflows, pTimeStep)
364
400
 
401
+ if pIndVarTTC == 1:
402
+ pVariableTTCFrw = ProcessParameter(pVariableTTCFrw, pTimeStep)
403
+ pVariableTTCBck = ProcessParameter(pVariableTTCBck, pTimeStep)
404
+ if pIndPTDF == 1:
405
+ pVariablePTDF = ProcessParameter(pVariablePTDF, pTimeStep)
406
+
365
407
  if pIndHydroTopology == 1:
366
408
  pVariableMinVolume = ProcessParameter(pVariableMinVolume, pTimeStep)
367
409
  pVariableMaxVolume = ProcessParameter(pVariableMaxVolume, pTimeStep)
@@ -374,7 +416,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
374
416
  if pIndHeat == 1:
375
417
  pDemandHeat = ProcessParameter(pDemandHeat, pTimeStep)
376
418
 
377
- # assign duration 0 to load levels not being considered, active load levels are at the end of every pTimeStep
419
+ # assign duration 0 to load levels not being considered; active load levels are at the end of every pTimeStep
378
420
  for n in range(pTimeStep-2,-1,-1):
379
421
  pDuration.iloc[[range(n,len(mTEPES.pp)*len(mTEPES.scc)*len(mTEPES.nn),pTimeStep)]] = 0
380
422
 
@@ -548,7 +590,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
548
590
 
549
591
  #%% defining subsets: active load levels (n,n2), thermal units (t), RES units (r), ESS units (es), candidate gen units (gc), candidate ESS units (ec), all the electric lines (la), candidate electric lines (lc), candidate DC electric lines (cd), existing DC electric lines (cd), electric lines with losses (ll), reference node (rf), and reactive generating units (gq)
550
592
  mTEPES.p = Set(doc='periods' , initialize=[pp for pp in mTEPES.pp if pPeriodWeight [pp] > 0.0 and sum(pDuration[pp,sc,n] for sc,n in mTEPES.scc*mTEPES.nn)])
551
- mTEPES.sc = Set(doc='scenarios' , initialize=[scc for scc in mTEPES.scc ])
593
+ mTEPES.sc = Set(doc='scenarios' , initialize=[scc for scc in mTEPES.scc ])
552
594
  mTEPES.ps = Set(doc='periods/scenarios' , initialize=[(p,sc) for p,sc in mTEPES.p*mTEPES.sc if pScenProb [p,sc] > 0.0 and sum(pDuration[p,sc,n ] for n in mTEPES.nn)])
553
595
  mTEPES.st = Set(doc='stages' , initialize=[stt for stt in mTEPES.stt if pStageWeight [stt] > 0.0])
554
596
  mTEPES.n = Set(doc='load levels' , initialize=[nn for nn in mTEPES.nn if sum(pDuration [p,sc,nn] for p,sc in mTEPES.ps) > 0])
@@ -612,6 +654,16 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
612
654
  mTEPES.ha = Set(doc='all real heat pipes' , initialize=[])
613
655
  mTEPES.hc = Set(doc='candidate heat pipes' , initialize=[])
614
656
 
657
+ pIndBinLinePTDF = pd.Series(index=mTEPES.la, data=0.0) # indicate if the line has a PTDF or not
658
+ if pIndVarTTC == 1:
659
+ pVariableTTCFrw = pVariableTTCFrw.reindex(columns=mTEPES.la, fill_value=0.0) # variable TTC forward direction
660
+ pVariableTTCBck = pVariableTTCBck.reindex(columns=mTEPES.la, fill_value=0.0) # variable TTC backward direction
661
+ if pIndPTDF == 1:
662
+ # get the level_3, level_4, and level_5 from multiindex of pVariablePTDF
663
+ PTDF_columns = pVariablePTDF.columns
664
+ PTDF_lines = PTDF_columns.droplevel([3]).drop_duplicates()
665
+ pIndBinLinePTDF.loc[:] = pIndBinLinePTDF.index.isin(PTDF_lines).astype(float)
666
+
615
667
  # non-RES units, they can be committed and also contribute to the operating reserves
616
668
  mTEPES.nr = mTEPES.g - mTEPES.re
617
669
  # machines able to provide reactive power
@@ -630,10 +682,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
630
682
  #%% inverse index load level to stage
631
683
  pStageToLevel = pLevelToStage.reset_index().set_index(['Period','Scenario','Stage'])['LoadLevel']
632
684
  #Filter only valid indices
633
- pStageToLevel = pStageToLevel.loc[
634
- pStageToLevel.index.isin([(p, s, st) for (p, s) in mTEPES.ps for st in mTEPES.st]) &
635
- pStageToLevel.isin(mTEPES.n)
636
- ]
685
+ pStageToLevel = pStageToLevel.loc[pStageToLevel.index.isin([(p,s,st) for (p,s) in mTEPES.ps for st in mTEPES.st]) & pStageToLevel.isin(mTEPES.n)]
637
686
  #Reorder the elements
638
687
  pStageToLevel = [(p,sc,st,n) for (p,sc,st),n in pStageToLevel.items()]
639
688
  mTEPES.s2n = Set(initialize=pStageToLevel, doc='Load level to stage')
@@ -653,7 +702,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
653
702
  '''
654
703
  Create mTEPES instrumental sets.
655
704
 
656
- This function takes a mTEPES instance and adds instrumental sets which will be used later.
705
+ This function takes an mTEPES instance and adds instrumental sets which will be used later.
657
706
 
658
707
  Parameters:
659
708
  mTEPES: The instance of mTEPES.
@@ -704,10 +753,12 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
704
753
  mTEPES.psnnd = Set(initialize = [(p,sc,n,nd) for p,sc,n,nd in mTEPES.psn*mTEPES.nd ])
705
754
  mTEPES.psnar = Set(initialize = [(p,sc,n,ar) for p,sc,n,ar in mTEPES.psn*mTEPES.ar ])
706
755
 
707
- mTEPES.psnla = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.la if (p,ni,nf,cc) in mTEPES.pla])
708
- mTEPES.psnle = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.le if (p,ni,nf,cc) in mTEPES.pla])
709
- mTEPES.psnll = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.ll if (p,ni,nf,cc) in mTEPES.pll])
710
- mTEPES.psnls = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.ls if (p,ni,nf,cc) in mTEPES.pla])
756
+ mTEPES.psnla = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.la if (p,ni,nf,cc) in mTEPES.pla ])
757
+ mTEPES.psnle = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.le if (p,ni,nf,cc) in mTEPES.pla ])
758
+ mTEPES.psnll = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.ll if (p,ni,nf,cc) in mTEPES.pll ])
759
+ mTEPES.psnls = Set(initialize = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psn*mTEPES.ls if (p,ni,nf,cc) in mTEPES.pla ])
760
+
761
+ mTEPES.psnehc = Set(initialize = [(p,sc,n,eh) for p,sc,n,eh in mTEPES.psneh if pRatedMaxCharge[eh] > 0.0 ])
711
762
 
712
763
  if pIndHydroTopology == 1:
713
764
  mTEPES.prs = Set(initialize = [(p, rs) for p, rs in mTEPES.p *mTEPES.rs if pRsrPeriodIni[rs] <= p and pRsrPeriodFin[rs] >= p])
@@ -737,10 +788,13 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
737
788
  else:
738
789
  mTEPES.phc = Set(initialize = [])
739
790
 
791
+ if pIndPTDF == 1:
792
+ mTEPES.psnland = Set(initialize = [(p,sc,n,ni,nf,cc,nd) for p,sc,n,ni,nf,cc,nd in mTEPES.psnla*mTEPES.nd if (ni,nf,cc,nd) in pVariablePTDF.columns])
793
+
740
794
  # assigning a node to an area
741
795
  mTEPES.ndar = Set(initialize = [(nd,ar) for (nd,zn,ar) in mTEPES.ndzn*mTEPES.ar if (zn,ar) in mTEPES.znar])
742
796
 
743
- # assigning a line to an area. Both nodes are in the same area. Cross-area lines not included
797
+ # assigning a line to an area. Both nodes are in the same area. Cross-area lines are not included
744
798
  mTEPES.laar = Set(initialize = [(ni,nf,cc,ar) for ni,nf,cc,ar in mTEPES.la*mTEPES.ar if (ni,ar) in mTEPES.ndar and (nf,ar) in mTEPES.ndar])
745
799
 
746
800
 
@@ -767,10 +821,31 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
767
821
  pIndBinStorInvest = pIndBinStorInvest.map (idxDict)
768
822
  pIndBinLineInvest = pIndBinLineInvest.map (idxDict)
769
823
  pIndBinLineSwitch = pIndBinLineSwitch.map (idxDict)
770
- pIndOperReserve = pIndOperReserve.map (idxDict)
824
+ # pIndOperReserve = pIndOperReserve.map (idxDict)
771
825
  pIndOutflowIncomp = pIndOutflowIncomp.map (idxDict)
772
826
  pMustRun = pMustRun.map (idxDict)
773
827
 
828
+ # Operating reserves can be provided while generating or while consuming
829
+ # So there is need for two options to decide if the unit is able to provide them
830
+ # Due to backwards compatibility reasons instead of adding a new column to Data_Generation NoOperatingReserve column now accepts two inputs
831
+ # They are separated by "|", if only one input is detected, both parameters are set to whatever the input is
832
+
833
+ def split_and_map(val):
834
+ # Handle new format with double input. Detect if there is a | character
835
+ if isinstance(val, str) and '|' in val:
836
+ gen, cons = val.split('|', 1)
837
+ return pd.Series([idxDict.get(gen.strip(), 0), idxDict.get(cons.strip(), 0)])
838
+ else:
839
+ # If no | character is found, both options are set to the inputted value
840
+ mapped = idxDict.get(val, 0)
841
+ return pd.Series([mapped, mapped])
842
+
843
+ # Split the columns in pIndOperReserve and group them in Generation and Consumption tuples
844
+ pIndOperReserveGen, pIndOperReserveCon = zip(*pIndOperReserve.map(split_and_map))
845
+
846
+ pIndOperReserveGen = pd.Series(pIndOperReserveGen, index=pIndOperReserve.index)
847
+ pIndOperReserveCon = pd.Series(pIndOperReserveCon, index=pIndOperReserve.index)
848
+
774
849
  if pIndHydroTopology == 1:
775
850
  pIndBinRsrvInvest = pIndBinRsrvInvest.map (idxDict)
776
851
 
@@ -849,11 +924,42 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
849
924
 
850
925
  Create_ESS_RES_Sets(mTEPES)
851
926
 
852
- #%% inverse index generator to mutually exclusive generator
853
- pExclusiveGenToGen = pGenToExclusiveGen.reset_index().set_index('MutuallyExclusive').set_axis(['Generator'], axis=1)[['Generator']]
854
- pExclusiveGenToGen = pExclusiveGenToGen.loc[pExclusiveGenToGen['Generator'].isin(mTEPES.g)].reset_index().set_index(['MutuallyExclusive', 'Generator'])
927
+ # Create mutually exclusive groups
928
+ # Store in a group-generator dictionary all the relevant data
929
+ group_dict = {}
930
+ for generator, groups in pGenToExclusiveGen.items():
931
+ if groups != 0.0:
932
+ for group in str(groups).split('|'):
933
+ group_dict.setdefault(group, []).append(generator)
934
+
935
+ # These sets store all groups and the generators in them
936
+ mTEPES.ExclusiveGroups = Set(initialize=list(group_dict.keys()))
937
+ mTEPES.GeneratorsInExclusiveGroup = Set(mTEPES.ExclusiveGroups, initialize=group_dict)
938
+
939
+ # Create filtered dictionaries for Yearly and Hourly groups
940
+ group_dict_yearly = {}
941
+ group_dict_hourly = {}
942
+
943
+ for group, generators in group_dict.items():
944
+ if all(gen in mTEPES.gc for gen in generators):
945
+ group_dict_yearly[group] = generators
946
+ else:
947
+ group_dict_hourly[group] = generators
948
+
949
+ # The exclusive groups sets have all groups which are mutually exclusive in that time scope
950
+ # Generators in group sets are sets with the corresponding generators to a given group
951
+ mTEPES.ExclusiveGroupsYearly = Set(initialize=list(group_dict_yearly.keys()))
952
+ mTEPES.GeneratorsInYearlyGroup = Set(mTEPES.ExclusiveGroupsYearly, initialize=group_dict_yearly)
855
953
 
856
- mTEPES.g2g = Set(doc='mutually exclusive generator to generator', initialize=[(gg,g) for gg,g in mTEPES.g*mTEPES.g if (gg,g) in pExclusiveGenToGen])
954
+ mTEPES.ExclusiveGroupsHourly = Set(initialize=list(group_dict_hourly.keys()))
955
+ mTEPES.GeneratorsInHourlyGroup = Set(mTEPES.ExclusiveGroupsHourly, initialize=group_dict_hourly)
956
+
957
+ # All exclusive generators (sorting to ensure deterministic behavior)
958
+ mTEPES.ExclusiveGenerators = Set(initialize=sorted(sum(group_dict.values(), [])))
959
+ # All yearly exclusive generators (sorting to ensure deterministic behavior)
960
+ mTEPES.ExclusiveGeneratorsYearly = Set(initialize=sorted(sum(group_dict_yearly.values(), [])))
961
+ # All hourly exclusive generators (sorting to ensure deterministic behavior)
962
+ mTEPES.ExclusiveGeneratorsHourly = Set(initialize=sorted(sum(group_dict_hourly.values(), [])))
857
963
 
858
964
  # minimum and maximum variable power, charge, and storage capacity
859
965
  pMinPowerElec = pVariableMinPowerElec.replace(0.0, pRatedMinPowerElec)
@@ -1034,6 +1140,11 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1034
1140
  pDemandHeat = pDemandHeat.loc [mTEPES.psn ]
1035
1141
  pDemandHeatAbs = pDemandHeat.where(pDemandHeat > 0.0, 0.0)
1036
1142
 
1143
+ if pIndPTDF == 1:
1144
+ pVariableTTCFrw = pVariableTTCFrw.loc [mTEPES.psn]
1145
+ pVariableTTCBck = pVariableTTCBck.loc [mTEPES.psn]
1146
+ pVariablePTDF = pVariablePTDF.loc [mTEPES.psn]
1147
+
1037
1148
  # separate positive and negative demands to avoid converting negative values to 0
1038
1149
  pDemandElecPos = pDemandElec.where(pDemandElec >= 0.0, 0.0)
1039
1150
  pDemandElecNeg = pDemandElec.where(pDemandElec < 0.0, 0.0)
@@ -1047,6 +1158,11 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1047
1158
  for ar,es in mTEPES.ar*mTEPES.es:
1048
1159
  if (ar,es) in mTEPES.a2g:
1049
1160
  e2a[ar].append(es)
1161
+ r2a = defaultdict(list)
1162
+ for ar,rs in mTEPES.ar*mTEPES.rs:
1163
+ for h in mTEPES.h:
1164
+ if (ar,h) in mTEPES.a2g 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) and rs not in r2a[ar]:
1165
+ r2a[ar].append(rs)
1050
1166
  n2a = defaultdict(list)
1051
1167
  for ar,nr in mTEPES.ar*mTEPES.nr:
1052
1168
  if (ar,nr) in mTEPES.a2g:
@@ -1072,7 +1188,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1072
1188
  pOperReserveUp [pOperReserveUp [[ ar ]] < pEpsilonElec] = 0.0
1073
1189
  pOperReserveDw [pOperReserveDw [[ ar ]] < pEpsilonElec] = 0.0
1074
1190
 
1075
- if len(g2a[ar]):
1191
+ if g2a[ar]:
1076
1192
  pMinPowerElec [pMinPowerElec [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1077
1193
  pMaxPowerElec [pMaxPowerElec [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1078
1194
  pMinCharge [pMinCharge [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
@@ -1084,6 +1200,12 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1084
1200
  pMaxStorage [pMaxStorage [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1085
1201
  pIniInventory [pIniInventory [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1086
1202
 
1203
+ if pIndHydroTopology == 1:
1204
+ pMinVolume [pMinVolume [[rs for rs in r2a[ar]]] < pEpsilonElec] = 0.0
1205
+ pMaxVolume [pMaxVolume [[rs for rs in r2a[ar]]] < pEpsilonElec] = 0.0
1206
+ pHydroInflows [pHydroInflows [[rs for rs in r2a[ar]]] < pEpsilonElec] = 0.0
1207
+ pHydroOutflows[pHydroOutflows[[rs for rs in r2a[ar]]] < pEpsilonElec] = 0.0
1208
+
1087
1209
  # pInitialInventory.update(pd.Series([0.0 for es in e2a[ar] if pInitialInventory[es] < pEpsilonElec], index=[es for es in e2a[ar] if pInitialInventory[es] < pEpsilonElec], dtype='float64'))
1088
1210
 
1089
1211
  # merging positive and negative values of the demand
@@ -1103,25 +1225,25 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1103
1225
 
1104
1226
  pMaxCapacity = pMaxPowerElec.where(pMaxPowerElec > pMaxCharge, pMaxCharge)
1105
1227
 
1106
- if len(g2a[ar]):
1228
+ if g2a[ar]:
1107
1229
  pMaxPower2ndBlock [pMaxPower2ndBlock [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1108
1230
  pMaxCharge2ndBlock[pMaxCharge2ndBlock[[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1109
1231
 
1110
- pLineNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1111
- pLineNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1232
+ pLineNTCFrw.update(pd.Series([0.0 for la in mTEPES.la if pLineNTCFrw[la] < pEpsilonElec], index=[la for la in mTEPES.la if pLineNTCFrw[la] < pEpsilonElec], dtype='float64'))
1233
+ pLineNTCBck.update(pd.Series([0.0 for la in mTEPES.la if pLineNTCBck[la] < pEpsilonElec], index=[la for la in mTEPES.la if pLineNTCBck[la] < pEpsilonElec], dtype='float64'))
1112
1234
  pLineNTCMax = pLineNTCFrw.where(pLineNTCFrw > pLineNTCBck, pLineNTCBck)
1113
1235
 
1114
1236
  if pIndHydrogen == 1:
1115
1237
  pDemandH2[pDemandH2[[nd for nd in d2a[ar]]] < pEpsilonElec] = 0.0
1116
- pH2PipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1117
- pH2PipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1238
+ pH2PipeNTCFrw.update(pd.Series([0.0 for pa in mTEPES.pa if pH2PipeNTCFrw[pa] < pEpsilonElec], index=[pa for pa in mTEPES.pa if pH2PipeNTCFrw[pa] < pEpsilonElec], dtype='float64'))
1239
+ pH2PipeNTCBck.update(pd.Series([0.0 for pa in mTEPES.pa if pH2PipeNTCBck[pa] < pEpsilonElec], index=[pa for pa in mTEPES.pa if pH2PipeNTCBck[pa] < pEpsilonElec], dtype='float64'))
1118
1240
 
1119
1241
  if pIndHeat == 1:
1120
1242
  pDemandHeatPeak[p,ar] = pDemandHeat.loc[p,:,:][[nd for nd in d2a[ar]]].sum(axis=1).max()
1121
1243
  pEpsilonHeat = pDemandHeatPeak[p,ar]*1e-5
1122
1244
  pDemandHeat [pDemandHeat [[nd for nd in d2a[ar]]] < pEpsilonHeat] = 0.0
1123
- pHeatPipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilonHeat], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilonHeat], dtype='float64'))
1124
- pHeatPipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilonHeat], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilonHeat], dtype='float64'))
1245
+ pHeatPipeNTCFrw.update(pd.Series([0.0 for ha in mTEPES.ha if pHeatPipeNTCFrw[ha] < pEpsilonHeat], index=[ha for ha in mTEPES.ha if pHeatPipeNTCFrw[ha] < pEpsilonHeat], dtype='float64'))
1246
+ pHeatPipeNTCBck.update(pd.Series([0.0 for ha in mTEPES.ha if pHeatPipeNTCBck[ha] < pEpsilonHeat], index=[ha for ha in mTEPES.ha if pHeatPipeNTCBck[ha] < pEpsilonHeat], dtype='float64'))
1125
1247
 
1126
1248
  # drop generators not g or es or eh or ch
1127
1249
  pMinPowerElec = pMinPowerElec.loc [:,mTEPES.g ]
@@ -1147,7 +1269,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1147
1269
  pMaxPower2ndBlock = pMaxPower2ndBlock.where (pMaxPower2ndBlock > pEpsilon, 0.0)
1148
1270
  pMaxCharge2ndBlock = pMaxCharge2ndBlock.where(pMaxCharge2ndBlock > pEpsilon, 0.0)
1149
1271
 
1150
- # computation of the power to heat ratio of the CHP units
1272
+ # computation of the power-to-heat ratio of the CHP units
1151
1273
  # heat ratio of boiler units is fixed to 1.0
1152
1274
  pPower2HeatRatio = pd.Series([1.0 if ch in mTEPES.bo else (pRatedMaxPowerElec[ch]-pRatedMinPowerElec[ch])/(pRatedMaxPowerHeat[ch]-pRatedMinPowerHeat[ch]) for ch in mTEPES.ch], index=mTEPES.ch)
1153
1275
  pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.ch)
@@ -1206,6 +1328,17 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1206
1328
  pNetUpInvest = pNetUpInvest.loc [mTEPES.lc]
1207
1329
  pLineLossFactor = pLineLossFactor.loc [mTEPES.ll]
1208
1330
 
1331
+ pMaxNTCFrw = pd.DataFrame([[pLineNTCFrw[la] for la in mTEPES.la] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.la)
1332
+ pMaxNTCBck = pd.DataFrame([[pLineNTCBck[la] for la in mTEPES.la] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.la)
1333
+ if pIndVarTTC == 1:
1334
+ pMaxNTCFrw = pVariableTTCFrw.replace(0.0, pLineNTCFrw / dfNetwork['SecurityFactor']) * dfNetwork['SecurityFactor'].loc[dfNetwork.index.isin(set(mTEPES.la))]
1335
+ pMaxNTCBck = pVariableTTCBck.replace(0.0, pLineNTCBck / dfNetwork['SecurityFactor']) * dfNetwork['SecurityFactor'].loc[dfNetwork.index.isin(set(mTEPES.la))]
1336
+ pMaxNTCMax = pMaxNTCFrw.where(pMaxNTCFrw > pMaxNTCBck, pMaxNTCBck)
1337
+
1338
+ pMaxNTCBck = pMaxNTCBck.loc [:,mTEPES.la]
1339
+ pMaxNTCFrw = pMaxNTCFrw.loc [:,mTEPES.la]
1340
+ pMaxNTCMax = pMaxNTCFrw.loc [:,mTEPES.la]
1341
+
1209
1342
  if pIndHydroTopology == 1:
1210
1343
  # drop generators not h
1211
1344
  pProductionFunctionHydro = pProductionFunctionHydro.loc[mTEPES.h ]
@@ -1310,6 +1443,16 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1310
1443
  pOperReserveUp = filter_rows(pOperReserveUp , mTEPES.psnar)
1311
1444
  pOperReserveDw = filter_rows(pOperReserveDw , mTEPES.psnar)
1312
1445
 
1446
+ pMaxNTCBck = filter_rows(pMaxNTCBck , mTEPES.psnla)
1447
+ pMaxNTCFrw = filter_rows(pMaxNTCFrw , mTEPES.psnla)
1448
+ pMaxNTCMax = filter_rows(pMaxNTCMax , mTEPES.psnla)
1449
+
1450
+ if pIndPTDF == 1:
1451
+ pPTDF = pVariablePTDF.stack(level=list(range(pVariablePTDF.columns.nlevels)), future_stack=True)
1452
+ pPTDF.index.set_names(['Period', 'Scenario', 'LoadLevel', 'InitialNode', 'FinalNode', 'Circuit', 'Node'], inplace=True)
1453
+ # filter rows to keep the same as mTEPES.psnland
1454
+ pPTDF = pPTDF[pPTDF.index.isin(mTEPES.psnland)]
1455
+
1313
1456
  # %% parameters
1314
1457
  mTEPES.pIndBinGenInvest = Param(initialize=pIndBinGenInvest , within=NonNegativeIntegers, doc='Indicator of binary generation investment decisions', mutable=True)
1315
1458
  mTEPES.pIndBinGenRetire = Param(initialize=pIndBinGenRetire , within=NonNegativeIntegers, doc='Indicator of binary generation retirement decisions', mutable=True)
@@ -1326,19 +1469,20 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1326
1469
  mTEPES.pIndHydroTopology = Param(initialize=pIndHydroTopology , within=Binary, doc='Indicator of reservoir and hydropower topology' )
1327
1470
  mTEPES.pIndHydrogen = Param(initialize=pIndHydrogen , within=Binary, doc='Indicator of hydrogen demand and pipeline network' )
1328
1471
  mTEPES.pIndHeat = Param(initialize=pIndHeat , within=Binary, doc='Indicator of heat demand and pipe network' )
1329
-
1330
- mTEPES.pENSCost = Param(initialize=pENSCost , within=NonNegativeReals, doc='ENS cost' )
1331
- mTEPES.pH2NSCost = Param(initialize=pH2NSCost , within=NonNegativeReals, doc='HNS cost' )
1332
- mTEPES.pHeatNSCost = Param(initialize=pHeatNSCost , within=NonNegativeReals, doc='HTNS cost' )
1333
- mTEPES.pCO2Cost = Param(initialize=pCO2Cost , within=NonNegativeReals, doc='CO2 emission cost' )
1334
- mTEPES.pAnnualDiscRate = Param(initialize=pAnnualDiscRate , within=UnitInterval, doc='Annual discount rate' )
1335
- mTEPES.pUpReserveActivation = Param(initialize=pUpReserveActivation, within=UnitInterval, doc='Proportion of upward reserve activation' )
1336
- mTEPES.pDwReserveActivation = Param(initialize=pDwReserveActivation, within=UnitInterval, doc='Proportion of downward reserve activation' )
1337
- mTEPES.pMinRatioDwUp = Param(initialize=pMinRatioDwUp , within=UnitInterval, doc='Minimum ration between upward and downward reserve')
1338
- mTEPES.pMaxRatioDwUp = Param(initialize=pMaxRatioDwUp , within=UnitInterval, doc='Maximum ration between upward and downward reserve')
1339
- mTEPES.pSBase = Param(initialize=pSBase , within=PositiveReals, doc='Base power' )
1340
- mTEPES.pTimeStep = Param(initialize=pTimeStep , within=PositiveIntegers, doc='Unitary time step' )
1341
- mTEPES.pEconomicBaseYear = Param(initialize=pEconomicBaseYear , within=PositiveIntegers, doc='Base year' )
1472
+ mTEPES.pIndPTDF = Param(initialize=pIndPTDF , within=Binary, doc='Indicator of using or not the Flow-based method' )
1473
+
1474
+ mTEPES.pENSCost = Param(initialize=pENSCost , within=NonNegativeReals, doc='ENS cost' )
1475
+ mTEPES.pH2NSCost = Param(initialize=pH2NSCost , within=NonNegativeReals, doc='HNS cost' )
1476
+ mTEPES.pHeatNSCost = Param(initialize=pHeatNSCost , within=NonNegativeReals, doc='HTNS cost' )
1477
+ mTEPES.pCO2Cost = Param(initialize=pCO2Cost , within=NonNegativeReals, doc='CO2 emission cost' )
1478
+ mTEPES.pAnnualDiscRate = Param(initialize=pAnnualDiscRate , within=UnitInterval, doc='Annual discount rate' )
1479
+ mTEPES.pUpReserveActivation = Param(initialize=pUpReserveActivation, within=UnitInterval, doc='Proportion of upward reserve activation' )
1480
+ mTEPES.pDwReserveActivation = Param(initialize=pDwReserveActivation, within=UnitInterval, doc='Proportion of downward reserve activation' )
1481
+ mTEPES.pMinRatioDwUp = Param(initialize=pMinRatioDwUp , within=UnitInterval, doc='Minimum ratio downward to upward operating reserves')
1482
+ mTEPES.pMaxRatioDwUp = Param(initialize=pMaxRatioDwUp , within=UnitInterval, doc='Maximum ratio downward to upward operating reserves')
1483
+ mTEPES.pSBase = Param(initialize=pSBase , within=PositiveReals, doc='Base power' )
1484
+ mTEPES.pTimeStep = Param(initialize=pTimeStep , within=PositiveIntegers, doc='Unitary time step' )
1485
+ mTEPES.pEconomicBaseYear = Param(initialize=pEconomicBaseYear , within=PositiveIntegers, doc='Base year' )
1342
1486
 
1343
1487
  mTEPES.pReserveMargin = Param(mTEPES.par, initialize=pReserveMargin.to_dict() , within=NonNegativeReals, doc='Adequacy reserve margin' )
1344
1488
  mTEPES.pEmission = Param(mTEPES.par, initialize=pEmission.to_dict() , within=NonNegativeReals, doc='Maximum CO2 emission' )
@@ -1399,7 +1543,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1399
1543
  mTEPES.pIndBinUnitRetire = Param(mTEPES.gd, initialize=pIndBinUnitRetire.to_dict() , within=Binary , doc='Binary retirement decision' )
1400
1544
  mTEPES.pIndBinUnitCommit = Param(mTEPES.nr, initialize=pIndBinUnitCommit.to_dict() , within=Binary , doc='Binary commitment decision' )
1401
1545
  mTEPES.pIndBinStorInvest = Param(mTEPES.ec, initialize=pIndBinStorInvest.to_dict() , within=Binary , doc='Storage linked to generation investment' )
1402
- mTEPES.pIndOperReserve = Param(mTEPES.gg, initialize=pIndOperReserve.to_dict() , within=Binary , doc='Indicator of operating reserve' )
1546
+ mTEPES.pIndOperReserveGen = Param(mTEPES.gg, initialize=pIndOperReserveGen.to_dict() , within=Binary , doc='Indicator of operating reserve when generating power')
1547
+ mTEPES.pIndOperReserveCon = Param(mTEPES.gg, initialize=pIndOperReserveCon.to_dict() , within=Binary , doc='Indicator of operating reserve when consuming power' )
1403
1548
  mTEPES.pIndOutflowIncomp = Param(mTEPES.gg, initialize=pIndOutflowIncomp.to_dict() , within=Binary , doc='Indicator of outflow incompatibility with charging' )
1404
1549
  mTEPES.pEfficiency = Param(mTEPES.eh, initialize=pEfficiency.to_dict() , within=UnitInterval , doc='Round-trip efficiency' )
1405
1550
  mTEPES.pStorageTimeStep = Param(mTEPES.es, initialize=pStorageTimeStep.to_dict() , within=PositiveIntegers, doc='ESS Storage cycle' )
@@ -1427,8 +1572,10 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1427
1572
  mTEPES.pProductionFunctionHeat = Param(mTEPES.hp, initialize=pProductionFunctionHeat.to_dict() , within=NonNegativeReals, doc='Production function of an CHP plant' )
1428
1573
  mTEPES.pProductionFunctionH2ToHeat = Param(mTEPES.hh, initialize=pProductionFunctionH2ToHeat.to_dict(), within=NonNegativeReals, doc='Production function of an boiler using H2')
1429
1574
 
1430
- if pIndHydroTopology == 1:
1575
+ if pIndPTDF == 1:
1576
+ mTEPES.pPTDF = Param(mTEPES.psnland, initialize=pPTDF.to_dict() , within=Reals , doc='Power transfer distribution factor' )
1431
1577
 
1578
+ if pIndHydroTopology == 1:
1432
1579
  pHydroInflows = filter_rows(pHydroInflows , mTEPES.psnrs)
1433
1580
  pHydroOutflows = filter_rows(pHydroOutflows, mTEPES.psnrs)
1434
1581
  pMaxOutflows = filter_rows(pMaxOutflows , mTEPES.psnrs)
@@ -1502,6 +1649,10 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1502
1649
  mTEPES.pAngMax = Param(mTEPES.la, initialize=pAngMax.to_dict() , within= Reals, doc='Maximum phase angle difference', mutable=True)
1503
1650
  mTEPES.pNetLoInvest = Param(mTEPES.lc, initialize=pNetLoInvest.to_dict() , within=NonNegativeReals, doc='Lower bound of the electric line investment decision', mutable=True)
1504
1651
  mTEPES.pNetUpInvest = Param(mTEPES.lc, initialize=pNetUpInvest.to_dict() , within=NonNegativeReals, doc='Upper bound of the electric line investment decision', mutable=True)
1652
+ mTEPES.pIndBinLinePTDF = Param(mTEPES.la, initialize=pIndBinLinePTDF.to_dict() , within=Binary , doc='Binary indicator of line with' )
1653
+ mTEPES.pMaxNTCFrw = Param(mTEPES.psnla, initialize=pMaxNTCFrw.to_dict() , within= Reals, doc='Maximum NTC forward capacity' )
1654
+ mTEPES.pMaxNTCBck = Param(mTEPES.psnla, initialize=pMaxNTCBck.to_dict() , within= Reals, doc='Maximum NTC backward capacity' )
1655
+ mTEPES.pMaxNTCMax = Param(mTEPES.psnla, initialize=pMaxNTCMax.to_dict() , within= Reals, doc='Maximum NTC capacity' )
1505
1656
 
1506
1657
  if pIndHydrogen == 1:
1507
1658
  mTEPES.pH2PipeLength = Param(mTEPES.pn, initialize=pH2PipeLength.to_dict() , within=NonNegativeReals, doc='Hydrogen pipeline length', mutable=True)
@@ -1599,7 +1750,7 @@ def SettingUpVariables(OptModel, mTEPES):
1599
1750
  '''
1600
1751
  Create all mTEPES variables.
1601
1752
 
1602
- This function takes a mTEPES instance with all parameters and sets created and adds variables to it.
1753
+ This function takes an mTEPES instance with all parameters and sets created and adds variables to it.
1603
1754
 
1604
1755
  Parameters:
1605
1756
  mTEPES: The instance of mTEPES.
@@ -1608,126 +1759,137 @@ def SettingUpVariables(OptModel, mTEPES):
1608
1759
  None: Variables are added directly to the mTEPES object.
1609
1760
  '''
1610
1761
  #%% variables
1611
- OptModel.vTotalSCost = Var( within=NonNegativeReals, doc='total system cost [MEUR]')
1612
- OptModel.vTotalICost = Var( within=NonNegativeReals, doc='total system investment cost [MEUR]')
1613
- OptModel.vTotalFCost = Var(mTEPES.p, within=NonNegativeReals, doc='total system fixed cost [MEUR]')
1614
- OptModel.vTotalGCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable generation operation cost [MEUR]')
1615
- OptModel.vTotalCCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable consumption operation cost [MEUR]')
1616
- OptModel.vTotalECost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system emission cost [MEUR]')
1617
- OptModel.vTotalRCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system reliability cost [MEUR]')
1618
- OptModel.vTotalNCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total network loss penalty operation cost [MEUR]')
1619
- OptModel.vTotalEmissionArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission [MtCO2]')
1620
- OptModel.vTotalECostArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission cost [MEUR]')
1621
- OptModel.vTotalRESEnergyArea = Var(mTEPES.psnar, within=NonNegativeReals, doc=' RES energy [GWh]')
1622
-
1623
- OptModel.vTotalOutput = Var(mTEPES.psng , within=NonNegativeReals, doc='total output of the unit [GW]')
1624
- OptModel.vOutput2ndBlock = Var(mTEPES.psnnr, within=NonNegativeReals, doc='second block of the unit [GW]')
1625
- OptModel.vReserveUp = Var(mTEPES.psnnr, within=NonNegativeReals, doc='upward operating reserve [GW]')
1626
- OptModel.vReserveDown = Var(mTEPES.psnnr, within=NonNegativeReals, doc='downward operating reserve [GW]')
1627
- OptModel.vEnergyInflows = Var(mTEPES.psnec, within=NonNegativeReals, doc='unscheduled inflows of candidate ESS units [GW]')
1628
- OptModel.vEnergyOutflows = Var(mTEPES.psnes, within=NonNegativeReals, doc='scheduled outflows of all ESS units [GW]')
1629
- OptModel.vESSInventory = Var(mTEPES.psnes, within=NonNegativeReals, doc='ESS inventory [GWh]')
1630
- OptModel.vESSSpillage = Var(mTEPES.psnes, within=NonNegativeReals, doc='ESS spillage [GWh]')
1631
- OptModel.vIniInventory = Var(mTEPES.psnec, within=NonNegativeReals, doc='initial inventory for ESS candidate [GWh]')
1632
-
1633
- OptModel.vESSTotalCharge = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS total charge power [GW]')
1634
- OptModel.vCharge2ndBlock = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS charge power [GW]')
1635
- OptModel.vESSReserveUp = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS upward operating reserve [GW]')
1636
- OptModel.vESSReserveDown = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS downward operating reserve [GW]')
1637
- OptModel.vENS = Var(mTEPES.psnnd, within=NonNegativeReals, doc='energy not served in node [GW]')
1762
+ OptModel.vTotalSCost = Var( within=NonNegativeReals, doc='total system cost [MEUR]')
1763
+ OptModel.vTotalICost = Var( within=NonNegativeReals, doc='total system investment cost [MEUR]')
1764
+ OptModel.vTotalFCost = Var(mTEPES.p, within=NonNegativeReals, doc='total system fixed cost [MEUR]')
1765
+ OptModel.vTotalGCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable generation operation cost [MEUR]')
1766
+ OptModel.vTotalCCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable consumption operation cost [MEUR]')
1767
+ OptModel.vTotalECost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system emission cost [MEUR]')
1768
+ OptModel.vTotalRCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system reliability cost [MEUR]')
1769
+ OptModel.vTotalNCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total network loss penalty operation cost [MEUR]')
1770
+ OptModel.vTotalEmissionArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission [MtCO2]')
1771
+ OptModel.vTotalECostArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission cost [MEUR]')
1772
+ OptModel.vTotalRESEnergyArea = Var(mTEPES.psnar, within=NonNegativeReals, doc=' RES energy [GWh]')
1773
+
1774
+ OptModel.vTotalOutput = Var(mTEPES.psng , within=NonNegativeReals, doc='total output of the unit [GW]')
1775
+ OptModel.vOutput2ndBlock = Var(mTEPES.psnnr, within=NonNegativeReals, doc='second block of the unit [GW]')
1776
+ OptModel.vReserveUp = Var(mTEPES.psnnr, within=NonNegativeReals, doc='upward operating reserve [GW]')
1777
+ OptModel.vReserveDown = Var(mTEPES.psnnr, within=NonNegativeReals, doc='downward operating reserve [GW]')
1778
+ OptModel.vEnergyInflows = Var(mTEPES.psnec, within=NonNegativeReals, doc='unscheduled inflows of candidate ESS units [GW]')
1779
+ OptModel.vEnergyOutflows = Var(mTEPES.psnes, within=NonNegativeReals, doc='scheduled outflows of all ESS units [GW]')
1780
+ OptModel.vESSInventory = Var(mTEPES.psnes, within=NonNegativeReals, doc='ESS inventory [GWh]')
1781
+ OptModel.vESSSpillage = Var(mTEPES.psnes, within=NonNegativeReals, doc='ESS spillage [GWh]')
1782
+ OptModel.vIniInventory = Var(mTEPES.psnec, within=NonNegativeReals, doc='initial inventory for ESS candidate [GWh]')
1783
+
1784
+ OptModel.vESSTotalCharge = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS total charge power [GW]')
1785
+ OptModel.vCharge2ndBlock = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS charge power [GW]')
1786
+ OptModel.vESSReserveUp = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS upward operating reserve [GW]')
1787
+ OptModel.vESSReserveDown = Var(mTEPES.psneh, within=NonNegativeReals, doc='ESS downward operating reserve [GW]')
1788
+ OptModel.vENS = Var(mTEPES.psnnd, within=NonNegativeReals, doc='energy not served in node [GW]')
1638
1789
 
1639
1790
  if mTEPES.pIndHydroTopology == 1:
1640
- OptModel.vHydroInflows = Var(mTEPES.psnrc, within=NonNegativeReals, doc='unscheduled inflows of candidate hydro units [m3/s]')
1641
- OptModel.vHydroOutflows = Var(mTEPES.psnrs, within=NonNegativeReals, doc='scheduled outflows of all hydro units [m3/s]')
1642
- OptModel.vReservoirVolume = Var(mTEPES.psnrs, within=NonNegativeReals, doc='Reservoir volume [hm3]')
1643
- OptModel.vReservoirSpillage = Var(mTEPES.psnrs, within=NonNegativeReals, doc='Reservoir spillage [hm3]')
1791
+ OptModel.vHydroInflows = Var(mTEPES.psnrc, within=NonNegativeReals, doc='unscheduled inflows of candidate hydro units [m3/s]')
1792
+ OptModel.vHydroOutflows = Var(mTEPES.psnrs, within=NonNegativeReals, doc='scheduled outflows of all hydro units [m3/s]')
1793
+ OptModel.vReservoirVolume = Var(mTEPES.psnrs, within=NonNegativeReals, doc='Reservoir volume [hm3]')
1794
+ OptModel.vReservoirSpillage = Var(mTEPES.psnrs, within=NonNegativeReals, doc='Reservoir spillage [hm3]')
1644
1795
 
1645
1796
  if mTEPES.pIndHeat == 1:
1646
- OptModel.vTotalOutputHeat = Var(mTEPES.psng , within=NonNegativeReals, doc='total heat output of the boiler unit [GW]')
1797
+ OptModel.vTotalOutputHeat = Var(mTEPES.psng , within=NonNegativeReals, doc='total heat output of the boiler unit [GW]')
1647
1798
  [OptModel.vTotalOutputHeat[p,sc,n,ch].setub(mTEPES.pMaxPowerHeat[p,sc,n,ch]) for p,sc,n,ch in mTEPES.psnch]
1648
1799
  [OptModel.vTotalOutputHeat[p,sc,n,ch].setlb(mTEPES.pMinPowerHeat[p,sc,n,ch]) for p,sc,n,ch in mTEPES.psnch]
1649
1800
  # only boilers are forced to produce at their minimum heat power. CHPs are not forced to produce at their minimum heat power, they are committed or not to produce electricity
1650
1801
 
1651
1802
  if mTEPES.pIndBinGenInvest() != 1:
1652
- OptModel.vGenerationInvest = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1653
- OptModel.vGenerationInvPer = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1803
+ OptModel.vGenerationInvest = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1804
+ OptModel.vGenerationInvPer = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1654
1805
  else:
1655
- OptModel.vGenerationInvest = Var(mTEPES.peb, within=Binary, doc='generation investment decision exists in a year {0,1}')
1656
- OptModel.vGenerationInvPer = Var(mTEPES.peb, within=Binary, doc='generation investment decision done in a year {0,1}')
1806
+ OptModel.vGenerationInvest = Var(mTEPES.peb, within=Binary, doc='generation investment decision exists in a year {0,1}')
1807
+ OptModel.vGenerationInvPer = Var(mTEPES.peb, within=Binary, doc='generation investment decision done in a year {0,1}')
1657
1808
 
1658
1809
  if mTEPES.pIndBinGenRetire() != 1:
1659
- OptModel.vGenerationRetire = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1660
- OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1810
+ OptModel.vGenerationRetire = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1811
+ OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1661
1812
  else:
1662
- OptModel.vGenerationRetire = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1663
- OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1813
+ OptModel.vGenerationRetire = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1814
+ OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1664
1815
 
1665
1816
  if mTEPES.pIndBinNetElecInvest() != 1:
1666
- OptModel.vNetworkInvest = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1667
- OptModel.vNetworkInvPer = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1817
+ OptModel.vNetworkInvest = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1818
+ OptModel.vNetworkInvPer = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1668
1819
  else:
1669
- OptModel.vNetworkInvest = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
1670
- OptModel.vNetworkInvPer = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
1820
+ OptModel.vNetworkInvest = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
1821
+ OptModel.vNetworkInvPer = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
1671
1822
 
1672
1823
  if mTEPES.pIndHydroTopology == 1:
1673
1824
  if mTEPES.pIndBinRsrInvest() != 1:
1674
- OptModel.vReservoirInvest = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1675
- OptModel.vReservoirInvPer = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1825
+ OptModel.vReservoirInvest = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1826
+ OptModel.vReservoirInvPer = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1676
1827
  else:
1677
- OptModel.vReservoirInvest = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
1678
- OptModel.vReservoirInvPer = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
1828
+ OptModel.vReservoirInvest = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
1829
+ OptModel.vReservoirInvPer = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
1679
1830
 
1680
1831
  if mTEPES.pIndHydrogen == 1:
1681
1832
  if mTEPES.pIndBinNetH2Invest() != 1:
1682
- OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1683
- OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1833
+ OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1834
+ OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1684
1835
  else:
1685
- OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
1686
- OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
1836
+ OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
1837
+ OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
1687
1838
 
1688
1839
  if mTEPES.pIndHeat == 1:
1689
1840
  if mTEPES.pIndBinGenInvest() != 1:
1690
- OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1691
- OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1841
+ OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1842
+ OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1692
1843
  else:
1693
- OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision exists in a year {0,1}')
1694
- OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision done in a year {0,1}')
1844
+ OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision exists in a year {0,1}')
1845
+ OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision done in a year {0,1}')
1695
1846
  if mTEPES.pIndBinNetHeatInvest() != 1:
1696
- OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1697
- OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1847
+ OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1848
+ OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1698
1849
  else:
1699
- OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=Binary, doc='heat network investment decision exists in a year {0,1}' )
1700
- OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=Binary, doc='heat network investment decision exists in a year {0,1}' )
1850
+ OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=Binary, doc='heat network investment decision exists in a year {0,1}' )
1851
+ OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=Binary, doc='heat network investment decision exists in a year {0,1}' )
1701
1852
 
1702
1853
  if mTEPES.pIndBinGenOperat() == 0:
1703
1854
  OptModel.vCommitment = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='commitment of the unit [0,1]')
1704
1855
  OptModel.vStartUp = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='startup of the unit [0,1]')
1705
1856
  OptModel.vShutDown = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='shutdown of the unit [0,1]')
1706
- OptModel.vMaxCommitment = Var(mTEPES.psnr , within=UnitInterval, initialize=0.0, doc='maximum commitment of the unit [0,1]')
1707
1857
  OptModel.vStableState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='stable state of the unit [0,1]')
1708
1858
  OptModel.vRampUpState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp up state of the unit [0,1]')
1709
1859
  OptModel.vRampDwState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp down state of the unit [0,1]')
1860
+
1861
+ OptModel.vMaxCommitmentYearly = Var(mTEPES.psnr ,mTEPES.ExclusiveGroupsYearly, within=UnitInterval, initialize=0.0, doc='maximum commitment of the unit yearly [0,1]')
1862
+ OptModel.vMaxCommitmentHourly = Var(mTEPES.psnnr,mTEPES.ExclusiveGroupsHourly, within=UnitInterval, initialize=0.0, doc='maximum commitment of the unit hourly [0,1]')
1863
+
1864
+ if mTEPES.pIndHydroTopology == 1:
1865
+ OptModel.vCommitmentCons = Var(mTEPES.psnh, within=UnitInterval, doc='consumption commitment of the unit [0,1]')
1866
+
1710
1867
  else:
1711
1868
  OptModel.vCommitment = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='commitment of the unit {0,1}')
1712
1869
  OptModel.vStartUp = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='startup of the unit {0,1}')
1713
1870
  OptModel.vShutDown = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='shutdown of the unit {0,1}')
1714
- OptModel.vMaxCommitment = Var(mTEPES.psnr , within=Binary, initialize=0 , doc='maximum commitment of the unit {0,1}')
1715
1871
  OptModel.vStableState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='stable state of the unit {0,1}')
1716
1872
  OptModel.vRampUpState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp up state of the unit {0,1}')
1717
1873
  OptModel.vRampDwState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp down state of the unit {0,1}')
1718
1874
 
1875
+ OptModel.vMaxCommitmentYearly = Var(mTEPES.psnr ,mTEPES.ExclusiveGroupsYearly, within=Binary, initialize=0.0, doc='maximum commitment of the unit yearly [0,1]')
1876
+ OptModel.vMaxCommitmentHourly = Var(mTEPES.psnnr,mTEPES.ExclusiveGroupsHourly, within=Binary, initialize=0.0, doc='maximum commitment of the unit hourly [0,1]')
1877
+ if mTEPES.pIndHydroTopology == 1:
1878
+ OptModel.vCommitmentCons = Var(mTEPES.psnh, within=Binary, doc='consumption commitment of the unit {0,1}')
1879
+
1880
+
1719
1881
  if mTEPES.pIndBinLineCommit() == 0:
1720
- OptModel.vLineCommit = Var(mTEPES.psnla, within=UnitInterval, initialize=0.0, doc='line switching of the electric line [0,1]')
1882
+ OptModel.vLineCommit = Var(mTEPES.psnla, within=UnitInterval, doc='line switching of the electric line [0,1]')
1721
1883
  else:
1722
- OptModel.vLineCommit = Var(mTEPES.psnla, within=Binary, initialize=0 , doc='line switching of the electric line {0,1}')
1884
+ OptModel.vLineCommit = Var(mTEPES.psnla, within=Binary, doc='line switching of the electric line {0,1}')
1723
1885
 
1724
1886
  if sum(mTEPES.pIndBinLineSwitch[:,:,:]):
1725
1887
  if mTEPES.pIndBinSingleNode() == 0 and mTEPES.pIndBinLineCommit() == 0:
1726
- OptModel.vLineOnState = Var(mTEPES.psnla, within=UnitInterval, initialize=0.0, doc='switching on state of the electric line [0,1]')
1727
- OptModel.vLineOffState = Var(mTEPES.psnla, within=UnitInterval, initialize=0.0, doc='switching off state of the electric line [0,1]')
1888
+ OptModel.vLineOnState = Var(mTEPES.psnla, within=UnitInterval, doc='switching on state of the electric line [0,1]')
1889
+ OptModel.vLineOffState = Var(mTEPES.psnla, within=UnitInterval, doc='switching off state of the electric line [0,1]')
1728
1890
  else:
1729
- OptModel.vLineOnState = Var(mTEPES.psnla, within=Binary, initialize=0 , doc='switching on state of the electric line {0,1}')
1730
- OptModel.vLineOffState = Var(mTEPES.psnla, within=Binary, initialize=0 , doc='switching off state of the electric line {0,1}')
1891
+ OptModel.vLineOnState = Var(mTEPES.psnla, within=Binary, doc='switching on state of the electric line {0,1}')
1892
+ OptModel.vLineOffState = Var(mTEPES.psnla, within=Binary, doc='switching off state of the electric line {0,1}')
1731
1893
 
1732
1894
  CreateVariables(mTEPES, mTEPES)
1733
1895
  # assign lower and upper bounds to variables
@@ -1735,7 +1897,7 @@ def SettingUpVariables(OptModel, mTEPES):
1735
1897
  '''
1736
1898
  Set upper/lower bounds.
1737
1899
 
1738
- This function takes a mTEPES instance and adds lower/upper bounds to variables which are limited by a parameter.
1900
+ This function takes an mTEPES instance and adds lower/upper bounds to variables which are limited by a parameter.
1739
1901
 
1740
1902
  Parameters:
1741
1903
  mTEPES: The instance of mTEPES
@@ -1773,16 +1935,16 @@ def SettingUpVariables(OptModel, mTEPES):
1773
1935
  nFixedVariables = 0
1774
1936
  def RelaxBinaryInvestmentConditions(mTEPES, OptModel) -> int:
1775
1937
  '''
1776
- Relax binary investent variables.
1938
+ Relax binary investment variables.
1777
1939
 
1778
- This function takes a mTEPES instance relaxes binary investment conditions and calculates the amount of variables fixed in the process.
1940
+ This function takes an mTEPES instance, relaxes binary investment conditions and calculates the number of variables fixed in the process.
1779
1941
 
1780
1942
  Parameters:
1781
1943
  mTEPES: The instance of mTEPES.
1782
1944
  OptModel:
1783
1945
 
1784
1946
  Returns:
1785
- int: The amount of fixed variables.
1947
+ int: The number of fixed variables.
1786
1948
  '''
1787
1949
 
1788
1950
  nFixedBinaries = 0
@@ -1844,9 +2006,20 @@ def SettingUpVariables(OptModel, mTEPES):
1844
2006
  OptModel.vStartUp [p,sc,n,nr].domain = UnitInterval
1845
2007
  OptModel.vShutDown [p,sc,n,nr].domain = UnitInterval
1846
2008
 
1847
- for p,sc, nr in mTEPES.psnr:
1848
- if mTEPES.pIndBinUnitCommit[nr] == 0:
1849
- OptModel.vMaxCommitment[p,sc, nr].domain = UnitInterval
2009
+ for p,sc,nr, group in mTEPES.psnr * mTEPES.ExclusiveGroups:
2010
+ if mTEPES.pIndBinUnitCommit[nr] == 0 and nr in mTEPES.ExclusiveGeneratorsYearly:
2011
+ OptModel.vMaxCommitmentYearly[p,sc, nr,group].domain = UnitInterval
2012
+ for p,sc,n,nr,group in mTEPES.psnnr * mTEPES.ExclusiveGroups:
2013
+ if mTEPES.pIndBinUnitCommit[nr] == 0 and nr in mTEPES.ExclusiveGeneratorsHourly:
2014
+ OptModel.vMaxCommitmentHourly[p,sc,n,nr,group].domain = UnitInterval
2015
+ if mTEPES.pIndHydroTopology == 1:
2016
+ for p,sc,n,h in mTEPES.psnh:
2017
+ if mTEPES.pIndBinUnitCommit[h] == 0:
2018
+ OptModel.vCommitmentCons[p,sc,n,h].domain = UnitInterval
2019
+ if mTEPES.pMaxCharge[p,sc,n,h] == 0:
2020
+ OptModel.vCommitmentCons[p,sc,n,h].fix(0)
2021
+ nFixedBinaries += 1
2022
+
1850
2023
  return nFixedBinaries
1851
2024
 
1852
2025
  #Call the relaxing variables function and add its output to nFixedVariables
@@ -1856,7 +2029,7 @@ def SettingUpVariables(OptModel, mTEPES):
1856
2029
  def CreateFlowVariables(mTEPES,OptModel) -> int:
1857
2030
  #TODO use a more descriptive name for nFixedVariables
1858
2031
  '''
1859
- Create electricity, hydrogen and heat flow related variables.
2032
+ Create electricity, hydrogen and heat-flow-related variables.
1860
2033
 
1861
2034
  This function takes a mTEPES instance and adds the variables necessary to model power, hydrogen and heat flows in a network.
1862
2035
 
@@ -1865,7 +2038,7 @@ def SettingUpVariables(OptModel, mTEPES):
1865
2038
  OptModel:
1866
2039
 
1867
2040
  Returns:
1868
- int: The amount of line commitment variables fixed
2041
+ int: The number of line commitment variables fixed
1869
2042
  '''
1870
2043
  nFixedVariables = 0
1871
2044
  # existing lines are always committed if no switching decision is modeled
@@ -1882,12 +2055,15 @@ def SettingUpVariables(OptModel, mTEPES):
1882
2055
  OptModel.vFlowElec = Var(mTEPES.psnla, within=Reals, doc='electric flow [GW]')
1883
2056
  OptModel.vTheta = Var(mTEPES.psnnd, within=Reals, doc='voltage angle [rad]')
1884
2057
 
2058
+ if mTEPES.pIndPTDF == 1:
2059
+ OptModel.vNetPosition = Var(mTEPES.psnnd, within=Reals, doc='net position in node [GW]')
2060
+
1885
2061
  [OptModel.vLineLosses[p,sc,n,ni,nf,cc].setub(0.5*mTEPES.pLineLossFactor[ni,nf,cc]*mTEPES.pLineNTCMax[ni,nf,cc]) for p,sc,n,ni,nf,cc in mTEPES.psnll]
1886
2062
  if mTEPES.pIndBinSingleNode() == 0:
1887
- [OptModel.vFlowElec[p,sc,n,ni,nf,cc].setlb(-mTEPES.pLineNTCBck[ni,nf,cc] ) for p,sc,n,ni,nf,cc in mTEPES.psnla]
1888
- [OptModel.vFlowElec[p,sc,n,ni,nf,cc].setub( mTEPES.pLineNTCFrw[ni,nf,cc] ) for p,sc,n,ni,nf,cc in mTEPES.psnla]
1889
- [OptModel.vTheta [p,sc,n,nd ].setlb(-mTEPES.pMaxTheta [p,sc,n,nd]()) for p,sc,n,nd in mTEPES.psnnd]
1890
- [OptModel.vTheta [p,sc,n,nd ].setub( mTEPES.pMaxTheta [p,sc,n,nd]()) for p,sc,n,nd in mTEPES.psnnd]
2063
+ [OptModel.vFlowElec[p,sc,n,ni,nf,cc].setlb(-mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc] ) for p,sc,n,ni,nf,cc in mTEPES.psnla]
2064
+ [OptModel.vFlowElec[p,sc,n,ni,nf,cc].setub( mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] ) for p,sc,n,ni,nf,cc in mTEPES.psnla]
2065
+ [OptModel.vTheta [p,sc,n,nd ].setlb(-mTEPES.pMaxTheta [p,sc,n,nd ]() ) for p,sc,n,nd in mTEPES.psnnd]
2066
+ [OptModel.vTheta [p,sc,n,nd ].setub( mTEPES.pMaxTheta [p,sc,n,nd ]() ) for p,sc,n,nd in mTEPES.psnnd]
1891
2067
 
1892
2068
  if mTEPES.pIndHydrogen == 1:
1893
2069
  OptModel.vFlowH2 = Var(mTEPES.psnpa, within=Reals, doc='pipeline flow [tH2]')
@@ -1911,14 +2087,14 @@ def SettingUpVariables(OptModel, mTEPES):
1911
2087
  '''
1912
2088
  Fix commitment variables.
1913
2089
 
1914
- This function takes a mTEPES instance and fixes commitment related variables for must run units, ESS units and units with no minimum power.
2090
+ This function takes an mTEPES instance and fixes commitment-related variables for must run units, ESS units and units with no minimum power.
1915
2091
 
1916
2092
  Parameters:
1917
2093
  mTEPES: The instance of mTEPES.
1918
2094
  OptModel:
1919
2095
 
1920
2096
  Returns:
1921
- int: The amount of commitment variables fixed.
2097
+ int: The number of commitment variables fixed.
1922
2098
  '''
1923
2099
  nFixedVariables = 0
1924
2100
 
@@ -1930,32 +2106,31 @@ def SettingUpVariables(OptModel, mTEPES):
1930
2106
  nFixedVariables += sum( 1 for p,sc,n,g in mTEPES.psng if mTEPES.pMaxPowerElec[p,sc,n,g] == 0.0)
1931
2107
 
1932
2108
  for p,sc,n,nr in mTEPES.psnnr:
1933
- # must run units or units with no minimum power or ESS existing units are always committed and must produce at least their minimum output
2109
+ # must run units or units with no minimum power, or ESS existing units are always committed and must produce at least their minimum output
1934
2110
  # not applicable to mutually exclusive units
1935
- if len(mTEPES.g2g) == 0:
1936
- OptModel.vMaxCommitment [p,sc, nr].fix(1)
1937
- nFixedVariables += 1/len(mTEPES.n)
1938
- if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pRatedConstantVarCost[nr] == 0.0) or nr in mTEPES.es) and nr not in mTEPES.ec:
2111
+ if len(mTEPES.ExclusiveGroups) == 0:
2112
+ if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pRatedConstantVarCost[nr] == 0.0) or nr in mTEPES.es ) and nr not in mTEPES.ec and nr not in mTEPES.h:
1939
2113
  OptModel.vCommitment [p,sc,n,nr].fix(1)
1940
2114
  OptModel.vStartUp [p,sc,n,nr].fix(0)
1941
2115
  OptModel.vShutDown [p,sc,n,nr].fix(0)
1942
2116
  nFixedVariables += 3
1943
- elif len(mTEPES.g2g) > 0 and sum(1 for g in mTEPES.nr if (nr,g) in mTEPES.g2g or (g,nr) in mTEPES.g2g) == 0:
1944
- if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pRatedConstantVarCost[nr] == 0.0) or nr in mTEPES.es) and nr not in mTEPES.ec:
2117
+ # If there are mutually exclusive groups do not fix variables from ESS in mutually exclusive groups
2118
+ elif len(mTEPES.ExclusiveGroups) > 0 and nr not in mTEPES.ExclusiveGenerators:
2119
+ if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pRatedConstantVarCost[nr] == 0.0) or nr in mTEPES.es) and nr not in mTEPES.ec and nr not in mTEPES.h:
1945
2120
  OptModel.vCommitment [p,sc,n,nr].fix(1)
1946
2121
  OptModel.vStartUp [p,sc,n,nr].fix(0)
1947
2122
  OptModel.vShutDown [p,sc,n,nr].fix(0)
1948
- OptModel.vMaxCommitment [p,sc, nr].fix(1)
1949
- nFixedVariables += 3+1/len(mTEPES.n)
2123
+ nFixedVariables += 3
2124
+
1950
2125
  # if min and max power coincide there are neither second block, nor operating reserve
1951
- if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0.0:
1952
- OptModel.vOutput2ndBlock[p,sc,n,nr].fix(0.0)
1953
- OptModel.vReserveUp [p,sc,n,nr].fix(0.0)
1954
- OptModel.vReserveDown [p,sc,n,nr].fix(0.0)
2126
+ if mTEPES.pMaxPower2ndBlock [p,sc,n,nr] == 0.0:
2127
+ OptModel.vOutput2ndBlock [p,sc,n,nr].fix(0.0)
2128
+ OptModel.vReserveUp [p,sc,n,nr].fix(0.0)
2129
+ OptModel.vReserveDown [p,sc,n,nr].fix(0.0)
1955
2130
  nFixedVariables += 3
1956
- if mTEPES.pIndOperReserve [ nr] == 1:
1957
- OptModel.vReserveUp [p,sc,n,nr].fix(0.0)
1958
- OptModel.vReserveDown [p,sc,n,nr].fix(0.0)
2131
+ if mTEPES.pIndOperReserveGen[ nr] == 1:
2132
+ OptModel.vReserveUp [p,sc,n,nr].fix(0.0)
2133
+ OptModel.vReserveDown [p,sc,n,nr].fix(0.0)
1959
2134
  nFixedVariables += 2
1960
2135
 
1961
2136
  # total energy inflows per storage
@@ -1981,7 +2156,7 @@ def SettingUpVariables(OptModel, mTEPES):
1981
2156
  if mTEPES.pMaxCharge2ndBlock[p,sc,n,es] == 0.0:
1982
2157
  OptModel.vCharge2ndBlock [p,sc,n,es].fix(0.0)
1983
2158
  nFixedVariables += 1
1984
- if mTEPES.pMaxCharge2ndBlock[p,sc,n,es] == 0.0 or mTEPES.pIndOperReserve[es] == 1:
2159
+ if mTEPES.pMaxCharge2ndBlock[p,sc,n,es] == 0.0 or mTEPES.pIndOperReserveCon[es] == 1:
1985
2160
  OptModel.vESSReserveUp [p,sc,n,es].fix(0.0)
1986
2161
  OptModel.vESSReserveDown [p,sc,n,es].fix(0.0)
1987
2162
  nFixedVariables += 2
@@ -1994,11 +2169,12 @@ def SettingUpVariables(OptModel, mTEPES):
1994
2169
  # ESS with no charge capacity or not storage capacity can't charge
1995
2170
  if mTEPES.pMaxCharge [p,sc,n,h ] == 0.0:
1996
2171
  OptModel.vESSTotalCharge [p,sc,n,h ].fix(0.0)
1997
- nFixedVariables += 1
2172
+ OptModel.vCommitmentCons [p,sc,n,h ].fix(0.0)
2173
+ nFixedVariables += 2
1998
2174
  if mTEPES.pMaxCharge2ndBlock[p,sc,n,h ] == 0.0:
1999
2175
  OptModel.vCharge2ndBlock [p,sc,n,h ].fix(0.0)
2000
2176
  nFixedVariables += 1
2001
- if mTEPES.pMaxCharge2ndBlock[p,sc,n,h ] == 0.0 or mTEPES.pIndOperReserve[h ] == 1:
2177
+ if mTEPES.pMaxCharge2ndBlock[p,sc,n,h ] == 0.0 or mTEPES.pIndOperReserveCon[h ] == 1:
2002
2178
  OptModel.vESSReserveUp [p,sc,n,h ].fix(0.0)
2003
2179
  OptModel.vESSReserveDown [p,sc,n,h ].fix(0.0)
2004
2180
  nFixedVariables += 2
@@ -2006,7 +2182,7 @@ def SettingUpVariables(OptModel, mTEPES):
2006
2182
  nFixedGeneratorCommits = FixGeneratorsCommitment(mTEPES,mTEPES)
2007
2183
  nFixedVariables += nFixedGeneratorCommits
2008
2184
  # thermal, ESS, and RES units ordered by increasing variable operation cost, excluding reactive generating units
2009
- if len(mTEPES.tq):
2185
+ if mTEPES.tq:
2010
2186
  mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.sq])
2011
2187
  else:
2012
2188
  if mTEPES.pIndHydroTopology == 1:
@@ -2026,29 +2202,29 @@ def SettingUpVariables(OptModel, mTEPES):
2026
2202
  mTEPES.st = Set(doc='stages', initialize=[stt for stt in mTEPES.stt if st == stt and mTEPES.pStageWeight[stt] and sum(1 for (p,sc,st,nn) in mTEPES.s2n)])
2027
2203
  mTEPES.n = Set(doc='load levels', initialize=[nn for nn in mTEPES.nn if (p,sc,st,nn) in mTEPES.s2n ])
2028
2204
 
2029
- if len(mTEPES.n):
2205
+ if mTEPES.n:
2030
2206
  # determine the first load level of each stage
2031
2207
  n1 = (p,sc,mTEPES.n.first())
2032
2208
  # commit the units of each area and their output at the first load level of each stage
2033
2209
  for ar in mTEPES.ar:
2034
2210
  pSystemOutput = 0.0
2035
2211
  for nr in mTEPES.nr:
2036
- if nr in g2a[ar] and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[nr] == 1:
2037
- mTEPES.pInitialOutput[n1,nr] = mTEPES.pMaxPowerElec[n1,nr]
2212
+ if nr in g2a[ar] and (p,nr) in mTEPES.pnr and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[nr] == 1:
2213
+ mTEPES.pInitialOutput[n1,nr] = mTEPES.pMaxPowerElec [n1,nr]
2038
2214
  mTEPES.pInitialUC [n1,nr] = 1
2039
2215
  pSystemOutput += mTEPES.pInitialOutput[n1,nr]()
2040
2216
 
2041
- # determine the initial committed units and their output at the first load level of each period, scenario, and stage
2217
+ # determine the initially committed units and their output at the first load level of each period, scenario, and stage
2042
2218
  for go in mTEPES.go:
2043
- if go in g2a[ar] and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[go] == 0:
2219
+ if go in g2a[ar] and (p,go) in mTEPES.pg and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[go] == 0:
2044
2220
  if go in mTEPES.re:
2045
- mTEPES.pInitialOutput[n1,go] = mTEPES.pMaxPowerElec[n1,go]
2221
+ mTEPES.pInitialOutput[n1,go] = mTEPES.pMaxPowerElec [n1,go]
2046
2222
  else:
2047
- mTEPES.pInitialOutput[n1,go] = mTEPES.pMinPowerElec[n1,go]
2223
+ mTEPES.pInitialOutput[n1,go] = mTEPES.pMinPowerElec [n1,go]
2048
2224
  mTEPES.pInitialUC[n1,go] = 1
2049
2225
  pSystemOutput += mTEPES.pInitialOutput[n1,go]()
2050
2226
 
2051
- # determine the initial committed lines
2227
+ # determine the initially committed lines
2052
2228
  for la in mTEPES.la:
2053
2229
  if la in mTEPES.lc:
2054
2230
  mTEPES.pInitialSwitch[n1,la] = 0
@@ -2057,7 +2233,7 @@ def SettingUpVariables(OptModel, mTEPES):
2057
2233
 
2058
2234
  # fixing the ESS inventory at the last load level of the stage for every period and scenario if between storage limits
2059
2235
  for es in mTEPES.es:
2060
- if es not in mTEPES.ec:
2236
+ if es not in mTEPES.ec and (p,es) in mTEPES.pes:
2061
2237
  OptModel.vESSInventory[p,sc,mTEPES.n.last(),es].fix(mTEPES.pIniInventory[p,sc,mTEPES.n.last(),es])
2062
2238
 
2063
2239
  if mTEPES.pIndHydroTopology == 1:
@@ -2112,7 +2288,7 @@ def SettingUpVariables(OptModel, mTEPES):
2112
2288
  OptModel.vEnergyInflows [p,sc,n,ec].fix (0.0)
2113
2289
  nFixedVariables += 1
2114
2290
 
2115
- # if no operating reserve is required no variables are needed
2291
+ # if no operating reserve is required, no variables are needed
2116
2292
  for p,sc,n,ar,nr in mTEPES.psnar*mTEPES.nr:
2117
2293
  if (ar,nr) in mTEPES.a2g and (p,nr) in mTEPES.pnr:
2118
2294
  if mTEPES.pOperReserveUp [p,sc,n,ar] == 0.0:
@@ -2130,7 +2306,7 @@ def SettingUpVariables(OptModel, mTEPES):
2130
2306
  OptModel.vESSReserveDown[p,sc,n,es].fix(0.0)
2131
2307
  nFixedVariables += 1
2132
2308
 
2133
- # if there are no energy outflows no variable is needed
2309
+ # if there are no energy outflows, no variable is needed
2134
2310
  for es in mTEPES.es:
2135
2311
  if sum(mTEPES.pEnergyOutflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) == 0.0:
2136
2312
  for p,sc,n in mTEPES.psn:
@@ -2138,6 +2314,15 @@ def SettingUpVariables(OptModel, mTEPES):
2138
2314
  OptModel.vEnergyOutflows[p,sc,n,es].fix(0.0)
2139
2315
  nFixedVariables += 1
2140
2316
 
2317
+ if mTEPES.pIndHydroTopology == 1:
2318
+ # if there are no hydro outflows, no variable is needed
2319
+ for rs in mTEPES.rs:
2320
+ if sum(mTEPES.pHydroOutflows[p,sc,n,rs]() for p,sc,n in mTEPES.psn if (p,rs) in mTEPES.prs) == 0.0:
2321
+ for p,sc,n in mTEPES.psn:
2322
+ if (p,rs) in mTEPES.prs:
2323
+ OptModel.vHydroOutflows[p,sc,n,rs].fix(0.0)
2324
+ nFixedVariables += 1
2325
+
2141
2326
  # fixing the voltage angle of the reference node for each scenario, period, and load level
2142
2327
  if mTEPES.pIndBinSingleNode() == 0:
2143
2328
  for p,sc,n in mTEPES.psn:
@@ -2168,14 +2353,14 @@ def SettingUpVariables(OptModel, mTEPES):
2168
2353
  '''
2169
2354
  Fix installations/retirements forbidden by period.
2170
2355
 
2171
- This function takes a mTEPES instance and fixes all installation and retirement variables to 0 if they are not allowed in the corresponding period.
2356
+ This function takes an mTEPES instance and fixes all installation and retirement variables to 0 if they are not allowed in the corresponding period.
2172
2357
 
2173
2358
  Parameters:
2174
2359
  mTEPES: The instance of mTEPES.
2175
2360
  OptModel:
2176
2361
 
2177
2362
  Returns:
2178
- int: The amount of variables fixed.
2363
+ int: The number of variables fixed.
2179
2364
  '''
2180
2365
  nFixedVariables = 0
2181
2366
  # do not install/retire power plants and lines if not allowed in this period
@@ -2273,7 +2458,7 @@ def SettingUpVariables(OptModel, mTEPES):
2273
2458
  if mTEPES.pIndHydroTopology == 1:
2274
2459
  for p,sc,n,rs in mTEPES.psnrs:
2275
2460
  if rs not in mTEPES.rn and mTEPES.pRsrPeriodIni[rs] > p:
2276
- OptModel.vEnergyOutflows [p,sc,n,rs].fix(0.0)
2461
+ OptModel.vHydroOutflows [p,sc,n,rs].fix(0.0)
2277
2462
  OptModel.vReservoirVolume [p,sc,n,rs].fix(0.0)
2278
2463
  OptModel.vReservoirSpillage [p,sc,n,rs].fix(0.0)
2279
2464
  mTEPES.pIniVolume [p,sc,n,rs] = 0.0
@@ -2295,18 +2480,18 @@ def SettingUpVariables(OptModel, mTEPES):
2295
2480
  [OptModel.vLineCommit [p,sc,n,ni,nf,cc].fix(0 ) for p,sc,n,ni,nf,cc in mTEPES.psnla if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p]
2296
2481
  [OptModel.vLineOnState [p,sc,n,ni,nf,cc].fix(0 ) for p,sc,n,ni,nf,cc in mTEPES.psnla if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p]
2297
2482
  [OptModel.vLineOffState[p,sc,n,ni,nf,cc].fix(0 ) for p,sc,n,ni,nf,cc in mTEPES.psnla if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p]
2298
- nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnla if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p)
2483
+ nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnla if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p)
2299
2484
 
2300
2485
  [OptModel.vLineLosses [p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnll if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p]
2301
- nFixedVariables += sum( 1 for p,sc,n,ni,nf,cc in mTEPES.psnll if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p)
2486
+ nFixedVariables += sum( 1 for p,sc,n,ni,nf,cc in mTEPES.psnll if (ni,nf,cc) not in mTEPES.lc and mTEPES.pElecNetPeriodIni [ni,nf,cc] > p)
2302
2487
 
2303
2488
  if mTEPES.pIndHydrogen == 1:
2304
- [OptModel.vFlowH2 [p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnpa if (ni,nf,cc) not in mTEPES.pc and mTEPES.pH2PipePeriodIni[ni,nf,cc] > p]
2305
- nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnpa if (ni,nf,cc) not in mTEPES.pc and mTEPES.pH2PipePeriodIni[ni,nf,cc] > p)
2489
+ [OptModel.vFlowH2 [p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnpa if (ni,nf,cc) not in mTEPES.pc and mTEPES.pH2PipePeriodIni [ni,nf,cc] > p]
2490
+ nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnpa if (ni,nf,cc) not in mTEPES.pc and mTEPES.pH2PipePeriodIni [ni,nf,cc] > p)
2306
2491
 
2307
2492
  if mTEPES.pIndHeat == 1:
2308
2493
  [OptModel.vFlowHeat[p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnha if (ni,nf,cc) not in mTEPES.hc and mTEPES.pHeatPipePeriodIni[ni,nf,cc] > p]
2309
- nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnha if (ni,nf,cc) not in mTEPES.hc and mTEPES.pHeatPipePeriodIni[ni,nf,cc] > p)
2494
+ nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnha if (ni,nf,cc) not in mTEPES.hc and mTEPES.pHeatPipePeriodIni[ni,nf,cc] > p)
2310
2495
 
2311
2496
  # tolerance to consider 0 an investment decision
2312
2497
  pEpsilon = 1e-4
@@ -2314,7 +2499,7 @@ def SettingUpVariables(OptModel, mTEPES):
2314
2499
  '''
2315
2500
  Set small numbers to 0.
2316
2501
 
2317
- This function takes a mTEPES instance and sets values under a certain threshold to be 0.
2502
+ This function takes an mTEPES instance and sets values under a certain threshold to be 0.
2318
2503
 
2319
2504
  Parameters:
2320
2505
  mTEPES: The instance of mTEPES.