openTEPES 4.18.2__py3-none-any.whl → 4.18.3__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.
@@ -1,2 +1,2 @@
1
- Period,Scenario,RESEnergy
1
+ Period,Area,RESEnergy
2
2
  2030,Area1,1000
@@ -1,2 +1,2 @@
1
- Period,Scenario,ReserveMargin
1
+ Period,Area,ReserveMargin
2
2
  2030,Area1,1.1
openTEPES/__init__.py CHANGED
@@ -14,7 +14,7 @@ Open Generation, Storage, and Transmission Operation and Expansion Planning Mode
14
14
  >>> import openTEPES as oT
15
15
  >>> oT.routine("9n", "C:\\Users\\UserName\\Documents\\GitHub\\openTEPES", "glpk")
16
16
  """
17
- __version__ = "4.18.2"
17
+ __version__ = "4.18.3"
18
18
 
19
19
  from .openTEPES_Main import main
20
20
  from .openTEPES import *
openTEPES/openTEPES.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 15, 2024
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 18, 2025
3
3
  """
4
4
 
5
5
  # import dill as pickle
@@ -39,8 +39,8 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
39
39
  idxDict['y' ] = 1
40
40
 
41
41
  #%% model declaration
42
- mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024')
43
- print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
42
+ mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025')
43
+ print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
44
44
 
45
45
  pIndOutputResults = [j for i,j in idxDict.items() if i == pIndOutputResults][0]
46
46
  pIndLogConsole = [j for i,j in idxDict.items() if i == pIndLogConsole ][0]
@@ -113,7 +113,9 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
113
113
  mTEPES.del_component(mTEPES.na)
114
114
  mTEPES.na = Set(initialize=mTEPES.n)
115
115
  else:
116
- mTEPES.na = mTEPES.na | mTEPES.n
116
+ temp_na = mTEPES.na | mTEPES.n # Create a temporary set
117
+ mTEPES.del_component(mTEPES.na) # Delete the existing set to avoid overwriting
118
+ mTEPES.na = Set(initialize=temp_na) # Assign the new set
117
119
 
118
120
  print(f'Period {p}, Scenario {sc}, Stage {st}')
119
121
 
@@ -138,7 +140,11 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
138
140
  NetworkCycles ( mTEPES, pIndLogConsole )
139
141
  CycleConstraints (mTEPES, mTEPES, pIndLogConsole, p, sc, st)
140
142
 
141
- if (len(mTEPES.gc) == 0 or (len(mTEPES.gc) > 0 and mTEPES.pIndBinGenInvest() == 2)) and (len(mTEPES.gd) == 0 or (len(mTEPES.gd) > 0 and mTEPES.pIndBinGenRetire() == 2)) and (len(mTEPES.lc) == 0 or (len(mTEPES.lc) > 0 and mTEPES.pIndBinNetElecInvest() == 2)) and (min([mTEPES.pEmission[p,ar] for ar in mTEPES.ar]) == math.inf or sum(mTEPES.pEmissionRate[nr] for nr in mTEPES.nr) == 0):
143
+ if ( (len(mTEPES.gc) == 0 or mTEPES.pIndBinGenInvest() == 2) # No generator investments
144
+ and (len(mTEPES.gd) == 0 or mTEPES.pIndBinGenRetire() == 2) # No generator retirements
145
+ and (len(mTEPES.lc) == 0 or mTEPES.pIndBinNetElecInvest() == 2) # No line investments
146
+ and (max([mTEPES.pRESEnergy[p,ar] for ar in mTEPES.ar]) == 0) # No minimum RES requirements
147
+ and (min([mTEPES.pEmission [p,ar] for ar in mTEPES.ar]) == math.inf or sum(mTEPES.pEmissionRate[nr] for nr in mTEPES.nr) == 0)): # No emission limit
142
148
 
143
149
  if pIndLogConsole == 1:
144
150
  StartTime = time.time()
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - December 10, 2024
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 17, 2025
3
3
  """
4
4
 
5
5
  import datetime
@@ -233,37 +233,37 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
233
233
  num_lines = sum(1 for _ in reader)
234
234
  return num_lines
235
235
 
236
- mTEPES.rs = Set(initialize=[], doc='reservoirs' )
237
- mTEPES.r2h = Set(initialize=[], doc='reservoir to hydro' )
238
- mTEPES.h2r = Set(initialize=[], doc='hydro to reservoir' )
239
- mTEPES.r2r = Set(initialize=[], doc='reservoir to reservoir' )
240
- mTEPES.p2r = Set(initialize=[], doc='pumped-hydro to reservoir')
241
- mTEPES.r2p = Set(initialize=[], doc='reservoir to pumped-hydro')
236
+ mTEPES.rs = Set(initialize=[], doc='reservoirs' )
237
+ mTEPES.r2h = Set(initialize=[], doc='reservoir to hydro' )
238
+ mTEPES.h2r = Set(initialize=[], doc='hydro to reservoir' )
239
+ mTEPES.r2r = Set(initialize=[], doc='reservoir 1 to reservoir 2')
240
+ mTEPES.p2r = Set(initialize=[], doc='pumped-hydro to reservoir' )
241
+ mTEPES.r2p = Set(initialize=[], doc='reservoir to pumped-hydro' )
242
242
 
243
243
  if count_lines_in_csv( f'{_path}/oT_Dict_Reservoir_' f'{CaseName}.csv') > 1:
244
244
  dictSets.load(filename=f'{_path}/oT_Dict_Reservoir_' f'{CaseName}.csv', set='rs' , format='set')
245
245
  mTEPES.del_component(mTEPES.rs)
246
- mTEPES.rs = Set(initialize=dictSets['rs' ], doc='reservoirs' )
246
+ mTEPES.rs = Set(initialize=dictSets['rs' ], doc='reservoirs' )
247
247
  if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToHydro_' f'{CaseName}.csv') > 1:
248
248
  dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToHydro_' f'{CaseName}.csv', set='r2h', format='set')
249
249
  mTEPES.del_component(mTEPES.r2h)
250
- mTEPES.r2h = Set(initialize=dictSets['r2h'], doc='reservoir to hydro' )
250
+ mTEPES.r2h = Set(initialize=dictSets['r2h'], doc='reservoir to hydro' )
251
251
  if count_lines_in_csv( f'{_path}/oT_Dict_HydroToReservoir_' f'{CaseName}.csv') > 1:
252
252
  dictSets.load(filename=f'{_path}/oT_Dict_HydroToReservoir_' f'{CaseName}.csv', set='h2r', format='set')
253
253
  mTEPES.del_component(mTEPES.h2r)
254
- mTEPES.h2r = Set(initialize=dictSets['h2r'], doc='hydro to reservoir' )
254
+ mTEPES.h2r = Set(initialize=dictSets['h2r'], doc='hydro to reservoir' )
255
255
  if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToReservoir_' f'{CaseName}.csv') > 1:
256
256
  dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToReservoir_' f'{CaseName}.csv', set='r2r', format='set')
257
257
  mTEPES.del_component(mTEPES.r2r)
258
- mTEPES.r2r = Set(initialize=dictSets['r2r'], doc='reservoir to reservoir' )
258
+ mTEPES.r2r = Set(initialize=dictSets['r2r'], doc='reservoir 1 to reservoir 2')
259
259
  if count_lines_in_csv( f'{_path}/oT_Dict_PumpedHydroToReservoir_' f'{CaseName}.csv') > 1:
260
260
  dictSets.load(filename=f'{_path}/oT_Dict_PumpedHydroToReservoir_' f'{CaseName}.csv', set='p2r', format='set')
261
261
  mTEPES.del_component(mTEPES.p2r)
262
- mTEPES.p2r = Set(initialize=dictSets['p2r'], doc='pumped-hydro to reservoir')
262
+ mTEPES.p2r = Set(initialize=dictSets['p2r'], doc='pumped-hydro to reservoir' )
263
263
  if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToPumpedHydro_' f'{CaseName}.csv') > 1:
264
264
  dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToPumpedHydro_' f'{CaseName}.csv', set='r2p', format='set')
265
265
  mTEPES.del_component(mTEPES.r2p)
266
- mTEPES.r2p = Set(initialize=dictSets['r2p'], doc='reservoir to pumped-hydro')
266
+ mTEPES.r2p = Set(initialize=dictSets['r2p'], doc='reservoir to pumped-hydro' )
267
267
  except:
268
268
  pass
269
269
 
@@ -302,10 +302,10 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
302
302
  pReserveMargin = dfReserveMargin['ReserveMargin' ] # minimum adequacy reserve margin [p.u.]
303
303
  pEmission = dfEmission ['CO2Emission' ] # maximum CO2 emission [MtCO2]
304
304
  pRESEnergy = dfRESEnergy ['RESEnergy' ] # minimum RES energy [GWh]
305
- pDemandElec = dfDemand [mTEPES.nd] * 1e-3 # electric demand [GW]
306
- pSystemInertia = dfInertia [mTEPES.ar] # inertia [s]
307
- pOperReserveUp = dfUpOperatingReserve [mTEPES.ar] * 1e-3 # upward operating reserve [GW]
308
- pOperReserveDw = dfDwOperatingReserve [mTEPES.ar] * 1e-3 # downward operating reserve [GW]
305
+ pDemandElec = dfDemand.reindex (columns=mTEPES.nd, fill_value=0.0) * 1e-3 # electric demand [GW]
306
+ pSystemInertia = dfInertia.reindex (columns=mTEPES.ar, fill_value=0.0) # inertia [s]
307
+ pOperReserveUp = dfUpOperatingReserve.reindex (columns=mTEPES.ar, fill_value=0.0) * 1e-3 # upward operating reserve [GW]
308
+ pOperReserveDw = dfDwOperatingReserve.reindex (columns=mTEPES.ar, fill_value=0.0) * 1e-3 # downward operating reserve [GW]
309
309
 
310
310
  pVariableMinPowerElec = dfVariableMinPower.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic variable minimum power [GW]
311
311
  pVariableMaxPowerElec = dfVariableMaxPower.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic variable maximum power [GW]
@@ -378,6 +378,13 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
378
378
  for n in range(pTimeStep-2,-1,-1):
379
379
  pDuration.iloc[[range(n,len(mTEPES.pp)*len(mTEPES.scc)*len(mTEPES.nn),pTimeStep)]] = 0
380
380
 
381
+ for psn in pDuration.index:
382
+ p,sc,n = psn
383
+ if pPeriodWeight[p] == 0:
384
+ pDuration.loc[p,sc,n] = 0
385
+ if pScenProb[p,sc] == 0:
386
+ pDuration.loc[p,sc,n] = 0
387
+
381
388
  #%% generation parameters
382
389
  pGenToNode = dfGeneration ['Node' ] # generator location in node
383
390
  pGenToTechnology = dfGeneration ['Technology' ] # generator association to technology
@@ -578,9 +585,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
578
585
  if pIndHydroTopology == 1:
579
586
  mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[rs for rs in mTEPES.rs if pRsrInvestCost [rs] > 0.0 and pRsrPeriodIni[rs] <= mTEPES.p.last() and pRsrPeriodFin[rs] >= mTEPES.p.first()])
580
587
  else:
581
- mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[] )
588
+ mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[])
582
589
  if pIndHydrogen == 1:
583
- mTEPES.pn = Set(doc='all input hydrogen pipes' , initialize=dfNetworkHydrogen.index )
590
+ mTEPES.pn = Set(doc='all input hydrogen pipes' , initialize=dfNetworkHydrogen.index)
584
591
  if len(mTEPES.pn) != len(dfNetworkHydrogen.index):
585
592
  raise ValueError('### Some hydrogen pipes are invalid ', len(mTEPES.pn), len(dfNetworkHydrogen.index))
586
593
  mTEPES.pa = Set(doc='all real hydrogen pipes' , initialize=[pn for pn in mTEPES.pn if pH2PipeNTCFrw [pn] > 0.0 and pH2PipeNTCBck[pn] > 0.0 and pH2PipePeriodIni[pn] <= mTEPES.p.last() and pH2PipePeriodFin[pn] >= mTEPES.p.first()])
@@ -876,9 +883,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
876
883
  pLinearVarCost = pLinearVarCost.reindex (sorted(pLinearVarCost.columns ), axis=1)
877
884
  pConstantVarCost = pConstantVarCost.reindex(sorted(pConstantVarCost.columns), axis=1)
878
885
 
879
- # variable emission cost
880
- pVariableEmissionCost = pVariableEmissionCost.replace(0.0, pCO2Cost)
881
- pEmissionVarCost = pEmissionRate * 1e-3 * pVariableEmissionCost
886
+ # variable emission cost [M€/GWh]
887
+ pVariableEmissionCost = pVariableEmissionCost.replace(0.0, pCO2Cost) #[€/tCO2]
888
+ pEmissionVarCost = pEmissionRate * 1e-3 * pVariableEmissionCost #[M€/GWh] = [tCO2/MWh] * 1e-3 * [€/tCO2]
882
889
  pEmissionVarCost = pEmissionVarCost.reindex(sorted(pEmissionVarCost.columns), axis=1)
883
890
 
884
891
  # minimum up- and downtime and maximum shift time converted to an integer number of time steps
@@ -889,8 +896,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
889
896
 
890
897
  # %% definition of the time-steps leap to observe the stored energy at an ESS
891
898
  idxCycle = dict()
892
- idxCycle[0 ] = 8736
893
- idxCycle[0.0 ] = 8736
899
+ idxCycle[0 ] = 1
900
+ idxCycle[0.0 ] = 1
894
901
  idxCycle['Hourly' ] = 1
895
902
  idxCycle['Daily' ] = 1
896
903
  idxCycle['Weekly' ] = round( 24/pTimeStep)
@@ -898,8 +905,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
898
905
  idxCycle['Yearly' ] = round( 672/pTimeStep)
899
906
 
900
907
  idxOutflows = dict()
901
- idxOutflows[0 ] = 8736
902
- idxOutflows[0.0 ] = 8736
908
+ idxOutflows[0 ] = 1
909
+ idxOutflows[0.0 ] = 1
903
910
  idxOutflows['Hourly' ] = 1
904
911
  idxOutflows['Daily' ] = round( 24/pTimeStep)
905
912
  idxOutflows['Weekly' ] = round( 168/pTimeStep)
@@ -907,17 +914,17 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
907
914
  idxOutflows['Yearly' ] = round(8736/pTimeStep)
908
915
 
909
916
  idxEnergy = dict()
910
- idxEnergy[0 ] = 8736
911
- idxEnergy[0.0 ] = 8736
917
+ idxEnergy[0 ] = 1
918
+ idxEnergy[0.0 ] = 1
912
919
  idxEnergy['Hourly' ] = 1
913
920
  idxEnergy['Daily' ] = round( 24/pTimeStep)
914
921
  idxEnergy['Weekly' ] = round( 168/pTimeStep)
915
922
  idxEnergy['Monthly'] = round( 672/pTimeStep)
916
923
  idxEnergy['Yearly' ] = round(8736/pTimeStep)
917
924
 
918
- pStorageTimeStep = pStorageType.map (idxCycle ).astype('int')
919
- pOutflowsTimeStep = pOutflowsType.map(idxOutflows).where(pEnergyOutflows.sum() > 0.0, other = 8736).astype('int')
920
- pEnergyTimeStep = pEnergyType.map (idxEnergy ).where(pVariableMinEnergy.sum() + pVariableMaxEnergy.sum() > 0.0, other = 8736).astype('int')
925
+ pStorageTimeStep = pStorageType.map (idxCycle ).astype('int')
926
+ pOutflowsTimeStep = pOutflowsType.map(idxOutflows).where(pEnergyOutflows.sum() > 0.0, other = 1).astype('int')
927
+ pEnergyTimeStep = pEnergyType.map (idxEnergy ).where(pVariableMinEnergy.sum() + pVariableMaxEnergy.sum() > 0.0, other = 1).astype('int')
921
928
 
922
929
  pStorageTimeStep = pd.concat([pStorageTimeStep, pOutflowsTimeStep, pEnergyTimeStep], axis=1).min(axis=1)
923
930
  # cycle time step can't exceed the stage duration
@@ -926,8 +933,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
926
933
  if pIndHydroTopology == 1:
927
934
  # %% definition of the time-steps leap to observe the stored energy at a reservoir
928
935
  idxCycleRsr = dict()
929
- idxCycleRsr[0 ] = 8736
930
- idxCycleRsr[0.0 ] = 8736
936
+ idxCycleRsr[0 ] = 1
937
+ idxCycleRsr[0.0 ] = 1
931
938
  idxCycleRsr['Hourly' ] = 1
932
939
  idxCycleRsr['Daily' ] = 1
933
940
  idxCycleRsr['Weekly' ] = round( 24/pTimeStep)
@@ -935,8 +942,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
935
942
  idxCycleRsr['Yearly' ] = round( 672/pTimeStep)
936
943
 
937
944
  idxWaterOut = dict()
938
- idxWaterOut[0 ] = 8736
939
- idxWaterOut[0.0 ] = 8736
945
+ idxWaterOut[0 ] = 1
946
+ idxWaterOut[0.0 ] = 1
940
947
  idxWaterOut['Hourly' ] = 1
941
948
  idxWaterOut['Daily' ] = round( 24/pTimeStep)
942
949
  idxWaterOut['Weekly' ] = round( 168/pTimeStep)
@@ -961,28 +968,28 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
961
968
  pInitialInventory.update(pd.Series([pInitialInventory[ec] if pIndBinStorInvest[ec] == 0 else pRatedMaxStorage[ec] for ec in mTEPES.ec], index=mTEPES.ec, dtype='float64'))
962
969
 
963
970
  # parameter that allows the initial inventory to change with load level
964
- pIniInventory = pd.DataFrame([pInitialInventory]*len(mTEPES.psn), index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.es)
971
+ pIniInventory = pd.DataFrame([pInitialInventory]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.es)
965
972
  if pIndHydroTopology == 1:
966
- pIniVolume = pd.DataFrame([pInitialVolume ]*len(mTEPES.psn), index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.rs)
973
+ pIniVolume = pd.DataFrame([pInitialVolume ]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.rs)
967
974
 
968
975
  # initial inventory must be between minimum and maximum
969
976
  for p,sc,n,es in mTEPES.psnes:
970
977
  if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == pStorageTimeStep[es]:
971
978
  if pIniInventory[es][p,sc,n] < pMinStorage[es][p,sc,n]:
972
979
  pIniInventory[es][p,sc,n] = pMinStorage[es][p,sc,n]
973
- print('### Initial inventory lower than minimum storage ', es)
980
+ print('### Initial inventory lower than minimum storage ', p, sc, st, es)
974
981
  if pIniInventory[es][p,sc,n] > pMaxStorage[es][p,sc,n]:
975
982
  pIniInventory[es][p,sc,n] = pMaxStorage[es][p,sc,n]
976
- print('### Initial inventory greater than maximum storage ', es)
983
+ print('### Initial inventory greater than maximum storage ', p, sc, st, es)
977
984
  if pIndHydroTopology == 1:
978
985
  for p,sc,n,rs in mTEPES.psnrs:
979
986
  if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == pReservoirTimeStep[rs]:
980
987
  if pIniVolume[rs][p,sc,n] < pMinVolume[rs][p,sc,n]:
981
988
  pIniVolume[rs][p,sc,n] = pMinVolume[rs][p,sc,n]
982
- print('### Initial volume lower than minimum volume ', rs)
989
+ print('### Initial volume lower than minimum volume ', p, sc, st, rs)
983
990
  if pIniVolume[rs][p,sc,n] > pMaxVolume[rs][p,sc,n]:
984
991
  pIniVolume[rs][p,sc,n] = pMaxVolume[rs][p,sc,n]
985
- print('### Initial volume greater than maximum volume ', rs)
992
+ print('### Initial volume greater than maximum volume ', p, sc, st, rs)
986
993
 
987
994
  # drop load levels with duration 0
988
995
  pDuration = pDuration.loc [mTEPES.psn ]
@@ -1143,11 +1150,11 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1143
1150
  # computation of the power to heat ratio of the CHP units
1144
1151
  # heat ratio of boiler units is fixed to 1.0
1145
1152
  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)
1146
- pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.ch)
1147
- pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.ch)
1148
- pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.bo))
1149
- pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.bo))
1150
- pMaxPowerHeat.update(pd.DataFrame([[pMaxCharge[hp][p,sc,n]/pProductionFunctionHeat[hp] for hp in mTEPES.hp] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.hp))
1153
+ 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)
1154
+ pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.ch)
1155
+ pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.bo))
1156
+ pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.bo))
1157
+ pMaxPowerHeat.update(pd.DataFrame([[pMaxCharge[hp][p,sc,n]/pProductionFunctionHeat[hp] for hp in mTEPES.hp] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.hp))
1151
1158
 
1152
1159
  # drop values not par, p, or ps
1153
1160
  pReserveMargin = pReserveMargin.loc [mTEPES.par]
@@ -1206,7 +1213,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1206
1213
  pIndBinRsrvInvest = pIndBinRsrvInvest.loc [mTEPES.rn]
1207
1214
  pRsrInvestCost = pRsrInvestCost.loc [mTEPES.rn]
1208
1215
  # maximum outflows depending on the downstream hydropower unit
1209
- pMaxOutflows = pd.DataFrame([[sum(pMaxPowerElec[h][p,sc,n]/pProductionFunctionHydro[h] for h in mTEPES.h if (rs,h) in mTEPES.r2h) for rs in mTEPES.rs] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.rs)
1216
+ pMaxOutflows = pd.DataFrame([[sum(pMaxPowerElec[h][p,sc,n]/pProductionFunctionHydro[h] for h in mTEPES.h if (rs,h) in mTEPES.r2h) for rs in mTEPES.rs] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.rs)
1210
1217
 
1211
1218
  if pIndHydrogen == 1:
1212
1219
  # drop generators not el
@@ -1569,9 +1576,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1569
1576
  mTEPES.pHeatPipeLength[ni,nf,cc] = 1.1 * 6371 * 2 * math.asin(math.sqrt(math.pow(math.sin((mTEPES.pNodeLat[nf]-mTEPES.pNodeLat[ni])*math.pi/180/2),2) + math.cos(mTEPES.pNodeLat[ni]*math.pi/180)*math.cos(mTEPES.pNodeLat[nf]*math.pi/180)*math.pow(math.sin((mTEPES.pNodeLon[nf]-mTEPES.pNodeLon[ni])*math.pi/180/2),2)))
1570
1577
 
1571
1578
  # initialize generation output, unit commitment and line switching
1572
- pInitialOutput = pd.DataFrame([[0.0]*len(mTEPES.g )]*len(mTEPES.psn), index=pd.MultiIndex.from_tuples(mTEPES.psn), columns= mTEPES.g )
1573
- pInitialUC = pd.DataFrame([[0 ]*len(mTEPES.g )]*len(mTEPES.psn), index=pd.MultiIndex.from_tuples(mTEPES.psn), columns= mTEPES.g )
1574
- pInitialSwitch = pd.DataFrame([[0 ]*len(mTEPES.la)]*len(mTEPES.psn), index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=pd.MultiIndex.from_tuples(mTEPES.la))
1579
+ pInitialOutput = pd.DataFrame([[0.0]*len(mTEPES.g )]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.g )
1580
+ pInitialUC = pd.DataFrame([[0 ]*len(mTEPES.g )]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.g )
1581
+ pInitialSwitch = pd.DataFrame([[0 ]*len(mTEPES.la)]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.la)
1575
1582
 
1576
1583
  pInitialOutput = filter_rows(pInitialOutput, mTEPES.psng )
1577
1584
  pInitialUC = filter_rows(pInitialUC , mTEPES.psng )
@@ -1601,14 +1608,15 @@ def SettingUpVariables(OptModel, mTEPES):
1601
1608
  None: Variables are added directly to the mTEPES object.
1602
1609
  '''
1603
1610
  #%% variables
1604
- OptModel.vTotalSCost = Var( within=NonNegativeReals, doc='total system cost [MEUR]')
1605
- OptModel.vTotalICost = Var( within=NonNegativeReals, doc='total system investment cost [MEUR]')
1611
+ OptModel.vTotalSCost = Var( within=NonNegativeReals, doc='total system cost [MEUR]')
1612
+ OptModel.vTotalICost = Var( within=NonNegativeReals, doc='total system investment cost [MEUR]')
1606
1613
  OptModel.vTotalFCost = Var(mTEPES.p, within=NonNegativeReals, doc='total system fixed cost [MEUR]')
1607
1614
  OptModel.vTotalGCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable generation operation cost [MEUR]')
1608
1615
  OptModel.vTotalCCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable consumption operation cost [MEUR]')
1609
1616
  OptModel.vTotalECost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system emission cost [MEUR]')
1610
1617
  OptModel.vTotalRCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system reliability cost [MEUR]')
1611
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]')
1612
1620
  OptModel.vTotalECostArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission cost [MEUR]')
1613
1621
  OptModel.vTotalRESEnergyArea = Var(mTEPES.psnar, within=NonNegativeReals, doc=' RES energy [GWh]')
1614
1622
 
@@ -1640,21 +1648,21 @@ def SettingUpVariables(OptModel, mTEPES):
1640
1648
  [OptModel.vTotalOutputHeat[p,sc,n,ch].setlb(mTEPES.pMinPowerHeat[p,sc,n,ch]) for p,sc,n,ch in mTEPES.psnch]
1641
1649
  # 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
1642
1650
 
1643
- if mTEPES.pIndBinGenInvest() == 0:
1651
+ if mTEPES.pIndBinGenInvest() != 1:
1644
1652
  OptModel.vGenerationInvest = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1645
1653
  OptModel.vGenerationInvPer = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1646
1654
  else:
1647
1655
  OptModel.vGenerationInvest = Var(mTEPES.peb, within=Binary, doc='generation investment decision exists in a year {0,1}')
1648
1656
  OptModel.vGenerationInvPer = Var(mTEPES.peb, within=Binary, doc='generation investment decision done in a year {0,1}')
1649
1657
 
1650
- if mTEPES.pIndBinGenRetire() == 0:
1658
+ if mTEPES.pIndBinGenRetire() != 1:
1651
1659
  OptModel.vGenerationRetire = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1652
1660
  OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
1653
1661
  else:
1654
1662
  OptModel.vGenerationRetire = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1655
1663
  OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
1656
1664
 
1657
- if mTEPES.pIndBinNetElecInvest() == 0:
1665
+ if mTEPES.pIndBinNetElecInvest() != 1:
1658
1666
  OptModel.vNetworkInvest = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1659
1667
  OptModel.vNetworkInvPer = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
1660
1668
  else:
@@ -1662,7 +1670,7 @@ def SettingUpVariables(OptModel, mTEPES):
1662
1670
  OptModel.vNetworkInvPer = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
1663
1671
 
1664
1672
  if mTEPES.pIndHydroTopology == 1:
1665
- if mTEPES.pIndBinRsrInvest() == 0:
1673
+ if mTEPES.pIndBinRsrInvest() != 1:
1666
1674
  OptModel.vReservoirInvest = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1667
1675
  OptModel.vReservoirInvPer = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
1668
1676
  else:
@@ -1670,7 +1678,7 @@ def SettingUpVariables(OptModel, mTEPES):
1670
1678
  OptModel.vReservoirInvPer = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
1671
1679
 
1672
1680
  if mTEPES.pIndHydrogen == 1:
1673
- if mTEPES.pIndBinNetH2Invest() == 0:
1681
+ if mTEPES.pIndBinNetH2Invest() != 1:
1674
1682
  OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1675
1683
  OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
1676
1684
  else:
@@ -1678,13 +1686,13 @@ def SettingUpVariables(OptModel, mTEPES):
1678
1686
  OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
1679
1687
 
1680
1688
  if mTEPES.pIndHeat == 1:
1681
- if mTEPES.pIndBinGenInvest() == 0:
1689
+ if mTEPES.pIndBinGenInvest() != 1:
1682
1690
  OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
1683
1691
  OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
1684
1692
  else:
1685
1693
  OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision exists in a year {0,1}')
1686
1694
  OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision done in a year {0,1}')
1687
- if mTEPES.pIndBinNetHeatInvest() == 0:
1695
+ if mTEPES.pIndBinNetHeatInvest() != 1:
1688
1696
  OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1689
1697
  OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
1690
1698
  else:
@@ -1696,7 +1704,7 @@ def SettingUpVariables(OptModel, mTEPES):
1696
1704
  OptModel.vStartUp = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='startup of the unit [0,1]')
1697
1705
  OptModel.vShutDown = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='shutdown of the unit [0,1]')
1698
1706
  OptModel.vMaxCommitment = Var(mTEPES.psnr , within=UnitInterval, initialize=0.0, doc='maximum commitment of the unit [0,1]')
1699
- OptModel.vStableState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='stable state of the unit [0,1]')
1707
+ OptModel.vStableState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='stable state of the unit [0,1]')
1700
1708
  OptModel.vRampUpState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp up state of the unit [0,1]')
1701
1709
  OptModel.vRampDwState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp down state of the unit [0,1]')
1702
1710
  else:
@@ -1704,7 +1712,7 @@ def SettingUpVariables(OptModel, mTEPES):
1704
1712
  OptModel.vStartUp = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='startup of the unit {0,1}')
1705
1713
  OptModel.vShutDown = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='shutdown of the unit {0,1}')
1706
1714
  OptModel.vMaxCommitment = Var(mTEPES.psnr , within=Binary, initialize=0 , doc='maximum commitment of the unit {0,1}')
1707
- OptModel.vStableState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='stable state of the unit {0,1}')
1715
+ OptModel.vStableState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='stable state of the unit {0,1}')
1708
1716
  OptModel.vRampUpState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp up state of the unit {0,1}')
1709
1717
  OptModel.vRampDwState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp down state of the unit {0,1}')
1710
1718
 
@@ -1951,7 +1959,10 @@ def SettingUpVariables(OptModel, mTEPES):
1951
1959
  nFixedVariables += 2
1952
1960
 
1953
1961
  # total energy inflows per storage
1954
- pStorageTotalEnergyInflows = pd.Series([sum(mTEPES.pEnergyInflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,sc,n,es) in mTEPES.psnes) for es in mTEPES.es], index=mTEPES.es)
1962
+ pTotalEnergyInflows = pd.Series([sum(mTEPES.pEnergyInflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,sc,n,es) in mTEPES.psnes) for es in mTEPES.es], index=mTEPES.es)
1963
+ pTotalMaxCharge = pd.Series([sum(mTEPES.pMaxCharge[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,sc,n,es) in mTEPES.psnes) for es in mTEPES.es], index=mTEPES.es)
1964
+ mTEPES.pTotalEnergyInflows = Param(mTEPES.es, initialize=pTotalEnergyInflows.to_dict(), within=NonNegativeReals, doc='Total energy outflows')
1965
+ mTEPES.pTotalMaxCharge = Param(mTEPES.es, initialize=pTotalMaxCharge.to_dict(), within=NonNegativeReals, doc='Total maximum charge' )
1955
1966
 
1956
1967
  for p,sc,n,es in mTEPES.psnes:
1957
1968
  # ESS with no charge capacity
@@ -1959,12 +1970,13 @@ def SettingUpVariables(OptModel, mTEPES):
1959
1970
  OptModel.vESSTotalCharge [p,sc,n,es].fix(0.0)
1960
1971
  nFixedVariables += 1
1961
1972
  # ESS with no charge capacity and no inflows can't produce
1962
- if mTEPES.pMaxCharge [p,sc,n,es] == 0.0 and pStorageTotalEnergyInflows[es] == 0.0:
1973
+ if pTotalMaxCharge[es] == 0.0 and pTotalEnergyInflows[es] == 0.0:
1963
1974
  OptModel.vTotalOutput [p,sc,n,es].fix(0.0)
1964
1975
  OptModel.vOutput2ndBlock [p,sc,n,es].fix(0.0)
1965
1976
  OptModel.vReserveUp [p,sc,n,es].fix(0.0)
1966
1977
  OptModel.vReserveDown [p,sc,n,es].fix(0.0)
1967
1978
  OptModel.vESSSpillage [p,sc,n,es].fix(0.0)
1979
+ OptModel.vESSInventory [p,sc,n,es].fix(mTEPES.pIniInventory[p,sc,n,es])
1968
1980
  nFixedVariables += 5
1969
1981
  if mTEPES.pMaxCharge2ndBlock[p,sc,n,es] == 0.0:
1970
1982
  OptModel.vCharge2ndBlock [p,sc,n,es].fix(0.0)
@@ -1992,12 +2004,20 @@ def SettingUpVariables(OptModel, mTEPES):
1992
2004
  nFixedVariables += 2
1993
2005
  return nFixedVariables
1994
2006
  nFixedGeneratorCommits = FixGeneratorsCommitment(mTEPES,mTEPES)
1995
- nFixedVariables += nFixedGeneratorCommits
1996
- # thermal and RES units ordered by increasing variable operation cost, excluding reactive generating units
2007
+ nFixedVariables += nFixedGeneratorCommits
2008
+ # thermal, ESS, and RES units ordered by increasing variable operation cost, excluding reactive generating units
1997
2009
  if len(mTEPES.tq):
1998
2010
  mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.sq])
1999
2011
  else:
2000
- mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.eh])
2012
+ if mTEPES.pIndHydroTopology == 1:
2013
+ mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__)])
2014
+ else:
2015
+ mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.h])
2016
+
2017
+ g2a = defaultdict(list)
2018
+ for ar,g in mTEPES.ar*mTEPES.g:
2019
+ if (ar,g) in mTEPES.a2g:
2020
+ g2a[ar].append(g)
2001
2021
 
2002
2022
  for p,sc,st in mTEPES.ps*mTEPES.stt:
2003
2023
  # activate only period, scenario, and load levels to formulate
@@ -2007,46 +2027,43 @@ def SettingUpVariables(OptModel, mTEPES):
2007
2027
  mTEPES.n = Set(doc='load levels', initialize=[nn for nn in mTEPES.nn if (p,sc,st,nn) in mTEPES.s2n ])
2008
2028
 
2009
2029
  if len(mTEPES.n):
2010
- mTEPES.psn1 = Set(initialize=[(p,sc,n) for p,sc,n in mTEPES.ps*mTEPES.n])
2011
2030
  # determine the first load level of each stage
2012
- n1 = next(iter(mTEPES.psn1))
2013
- # commit the units and their output at the first load level of each stage
2014
- pSystemOutput = 0.0
2015
- for nr in mTEPES.nr:
2016
- if pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd) and mTEPES.pMustRun[nr] == 1:
2017
- mTEPES.pInitialOutput[n1,nr] = mTEPES.pMaxPowerElec[n1,nr]
2018
- mTEPES.pInitialUC [n1,nr] = 1
2019
- pSystemOutput += mTEPES.pInitialOutput[n1,nr]()
2020
- mTEPES.del_component(mTEPES.psn1)
2021
-
2022
- # determine the initial committed units and their output at the first load level of each period, scenario, and stage
2023
- for go in mTEPES.go:
2024
- if pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd) and mTEPES.pMustRun[go] == 0:
2025
- if (n1,go) in mTEPES.psng:
2031
+ n1 = (p,sc,mTEPES.n.first())
2032
+ # commit the units of each area and their output at the first load level of each stage
2033
+ for ar in mTEPES.ar:
2034
+ pSystemOutput = 0.0
2035
+ 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]
2038
+ mTEPES.pInitialUC [n1,nr] = 1
2039
+ pSystemOutput += mTEPES.pInitialOutput[n1,nr]()
2040
+
2041
+ # determine the initial committed units and their output at the first load level of each period, scenario, and stage
2042
+ 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:
2026
2044
  if go in mTEPES.re:
2027
2045
  mTEPES.pInitialOutput[n1,go] = mTEPES.pMaxPowerElec[n1,go]
2028
2046
  else:
2029
2047
  mTEPES.pInitialOutput[n1,go] = mTEPES.pMinPowerElec[n1,go]
2030
- mTEPES.pInitialUC[n1,go] = 1
2031
- pSystemOutput += mTEPES.pInitialOutput[n1,go]()
2048
+ mTEPES.pInitialUC[n1,go] = 1
2049
+ pSystemOutput += mTEPES.pInitialOutput[n1,go]()
2032
2050
 
2033
2051
  # determine the initial committed lines
2034
2052
  for la in mTEPES.la:
2035
- if (n1,la) in mTEPES.psnla:
2036
- if la in mTEPES.lc:
2037
- mTEPES.pInitialSwitch[n1,la] = 0
2038
- else:
2039
- mTEPES.pInitialSwitch[n1,la] = 1
2053
+ if la in mTEPES.lc:
2054
+ mTEPES.pInitialSwitch[n1,la] = 0
2055
+ else:
2056
+ mTEPES.pInitialSwitch[n1,la] = 1
2040
2057
 
2041
2058
  # fixing the ESS inventory at the last load level of the stage for every period and scenario if between storage limits
2042
2059
  for es in mTEPES.es:
2043
- if es not in mTEPES.ec and (p,sc,mTEPES.n.last(),es) in mTEPES.psnes:
2060
+ if es not in mTEPES.ec:
2044
2061
  OptModel.vESSInventory[p,sc,mTEPES.n.last(),es].fix(mTEPES.pIniInventory[p,sc,mTEPES.n.last(),es])
2045
2062
 
2046
2063
  if mTEPES.pIndHydroTopology == 1:
2047
2064
  # fixing the reservoir volume at the last load level of the stage for every period and scenario if between storage limits
2048
2065
  for rs in mTEPES.rs:
2049
- if rs not in mTEPES.rn and (p,sc,mTEPES.n.last(),rs) in mTEPES.psnrs:
2066
+ if rs not in mTEPES.rn:
2050
2067
  OptModel.vReservoirVolume[p,sc,mTEPES.n.last(),rs].fix(mTEPES.pIniVolume[p,sc,mTEPES.n.last(),rs]())
2051
2068
 
2052
2069
  # activate all the periods, scenarios, and load levels again
@@ -2406,18 +2423,25 @@ def SettingUpVariables(OptModel, mTEPES):
2406
2423
  Returns:
2407
2424
  None: Raises a ValueError when an infeasibility is found.
2408
2425
  '''
2426
+
2427
+ if len(mTEPES.p ) == 0:
2428
+ raise ValueError('### No active periods in the case study' )
2429
+ if len(mTEPES.sc) == 0:
2430
+ raise ValueError('### No active scenarios in the case study')
2431
+ if len(mTEPES.st) == 0:
2432
+ raise ValueError('### No active stages in the case study' )
2433
+
2409
2434
  # detecting infeasibility: sum of scenario probabilities must be 1 in each period
2410
2435
  # for p in mTEPES.p:
2411
2436
  # if abs(sum(mTEPES.pScenProb[p,sc] for sc in mTEPES.sc)-1.0) > 1e-6:
2412
2437
  # raise ValueError('### Sum of scenario probabilities different from 1 in period ', p)
2413
2438
 
2414
-
2415
2439
  for es in mTEPES.es:
2416
2440
  # detecting infeasibility: total min ESS output greater than total inflows, total max ESS charge lower than total outflows
2417
2441
  if sum(mTEPES.pMinPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyInflows [p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) > 0.0:
2418
- raise ValueError('### Total minimum output greater than total inflows for ESS unit ', es, sum(mTEPES.pMinPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyInflows [p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes))
2442
+ raise ValueError('### Total minimum output greater than total inflows for ESS unit ', es, ' by ', sum(mTEPES.pMinPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyInflows [p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
2419
2443
  if sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyOutflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) < 0.0:
2420
- raise ValueError('### Total maximum charge lower than total outflows for ESS unit ', es, sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyOutflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes))
2444
+ raise ValueError('### Total maximum charge lower than total outflows for ESS unit ', es, ' by ', sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyOutflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
2421
2445
 
2422
2446
  # detect inventory infeasibility
2423
2447
  for p,sc,n,es in mTEPES.ps*mTEPES.nesc:
@@ -660,12 +660,12 @@
660
660
  # For more information on this, and how to apply and follow the GNU AGPL, see
661
661
  # <https://www.gnu.org/licenses/>.
662
662
 
663
- # Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - December 23, 2024
663
+ # Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 18, 2025
664
664
  # simplicity and transparency in power systems planning
665
665
 
666
666
  # Developed by
667
667
 
668
- # Andres Ramos, Erik Alvarez, Sara Lumbreras
668
+ # Andres Ramos, Erik Alvarez, Francisco Labora, Sara Lumbreras
669
669
  # Instituto de Investigacion Tecnologica
670
670
  # Escuela Tecnica Superior de Ingenieria - ICAI
671
671
  # UNIVERSIDAD PONTIFICIA COMILLAS
@@ -673,6 +673,7 @@
673
673
  # 28015 Madrid, Spain
674
674
  # Andres.Ramos@comillas.edu
675
675
  # Erik.Alvarez@comillas.edu
676
+ # Francisco.Labora@comillas.edu
676
677
  # Sara.Lumbreras@comillas.edu
677
678
  # https://pascua.iit.comillas.edu/aramos/Ramos_CV.htm
678
679
 
@@ -685,7 +686,7 @@ import time
685
686
  # import pkg_resources
686
687
  from .openTEPES import openTEPES_run
687
688
 
688
- print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024\033[0m')
689
+ print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025\033[0m')
689
690
  print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m \n')
690
691
 
691
692
  parser = argparse.ArgumentParser(description='Introducing main parameters...')