openTEPES 4.18.12__py3-none-any.whl → 4.18.13__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
- Stage,Weight
1
+ Stage,Weight
2
2
  st1,1
@@ -8,3 +8,5 @@ PumpedStorageHydro
8
8
  Solar_PV
9
9
  Subbituminous
10
10
  SynchronousCondenser
11
+ Diesel
12
+ Coal
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.12"
17
+ __version__ = "4.18.13"
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 28, 2026
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - February 05, 2026
3
3
  """
4
4
 
5
5
  # import dill as pickle
@@ -38,8 +38,8 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
38
38
  idxDict['y' ] = 1
39
39
 
40
40
  #%% model declaration
41
- mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.12 - January 28, 2026')
42
- print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.12 - January 28, 2026', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
41
+ mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.13 - February 05, 2026')
42
+ print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.13 - February 05, 2026', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
43
43
 
44
44
  pIndOutputResults = [j for i,j in idxDict.items() if i == pIndOutputResults][0]
45
45
  pIndLogConsole = [j for i,j in idxDict.items() if i == pIndLogConsole ][0]
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 28, 2026
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - February 05, 2026
3
3
  """
4
4
 
5
5
  import time
@@ -38,11 +38,6 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
38
38
  is_ordered = set_name not in {'gt', 'nd', 'ni', 'nf', 'cc', 'c2', 'ndzn', 'znar', 'arrg'}
39
39
  setattr(mTEPES, set_name, Set(initialize=dictSets[set_key], ordered=is_ordered, doc=f'{file_set_name}'))
40
40
 
41
- # # Defining sets in the model
42
- # for set_name, (file_set_name, set_key) in set_definitions.items():
43
- # is_ordered = set_name not in {'stt', 'gt', 'nd', 'ni', 'nf', 'cc', 'c2', 'ndzn', 'znar', 'arrg'}
44
- # setattr(model, set_name, Set(initialize=dictSets[set_key], ordered=is_ordered, doc=f'{file_set_name}'))
45
-
46
41
  # Constants
47
42
  DEFAULT_IDX_COLS = ['Period', 'Scenario', 'LoadLevel', 'Area', 'Generator', 'InitialNode', 'FinalNode', 'Circuit', 'Node', 'Stage']
48
43
  SPECIAL_IDX_COLS = {'Generation': ['Generator'], 'Reservoir': ['Reservoir']}
@@ -133,7 +128,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
133
128
  par[key] = 0
134
129
 
135
130
  # substitute NaN by 0
136
- for key, df in dfs.items():
131
+ for key,df in dfs.items():
137
132
  if 'dfEmission' in key:
138
133
  df.fillna(math.inf, inplace=True)
139
134
  elif 'dfGeneration' in key:
@@ -317,6 +312,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
317
312
  if par['pIndVarTTC'] == 1:
318
313
  par['pVariableNTCFrw'] = ProcessParameter(par['pVariableNTCFrw'], par['pTimeStep'])
319
314
  par['pVariableNTCBck'] = ProcessParameter(par['pVariableNTCBck'], par['pTimeStep'])
315
+
320
316
  if par['pIndPTDF'] == 1:
321
317
  par['pVariablePTDF'] = ProcessParameter(par['pVariablePTDF'], par['pTimeStep'])
322
318
 
@@ -336,8 +332,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
336
332
  for n in range(par['pTimeStep']-2,-1,-1):
337
333
  par['pDuration'].iloc[[range(n,len(mTEPES.pp)*len(mTEPES.scc)*len(mTEPES.nn),par['pTimeStep'])]] = 0
338
334
 
339
- for psn in par['pDuration'].index:
340
- p,sc,n = psn
335
+ for p,sc,n in par['pDuration'].index:
341
336
  if par['pPeriodWeight'][p] == 0.0:
342
337
  par['pDuration'].loc[p,sc,n] = 0
343
338
  if par['pScenProb'][p,sc] == 0.0:
@@ -542,6 +537,10 @@ def DataConfiguration(mTEPES):
542
537
  mTEPES.bc = Set(doc='candidate boiler units' , initialize=[bo for bo in mTEPES.bo if mTEPES.dPar['pGenInvestCost'] [bo] > 0.0])
543
538
  mTEPES.br = Set(doc='all input electric branches', initialize=sBrList )
544
539
  mTEPES.ln = Set(doc='all input electric lines' , initialize=mTEPES.dFrame['dfNetwork'].index)
540
+ # detect lines with undefined nodes or circuits
541
+ for ni,nf,cc in mTEPES.ln:
542
+ if ni not in mTEPES.nd or nf not in mTEPES.nd or cc not in mTEPES.cc:
543
+ raise ValueError(f'### Line {ni} {nf} {cc} has a node or a circuit not defined in the corresponding dictionary.')
545
544
  if len(mTEPES.ln) != len(mTEPES.dFrame['dfNetwork'].index):
546
545
  raise ValueError('### Some electric lines are invalid ', len(mTEPES.ln), len(mTEPES.dFrame['dfNetwork'].index))
547
546
  mTEPES.la = Set(doc='all real electric lines' , initialize=[ln for ln in mTEPES.ln if mTEPES.dPar['pLineX'] [ln] != 0.0 and mTEPES.dPar['pLineNTCFrw'][ln] > 0.0 and mTEPES.dPar['pLineNTCBck'][ln] > 0.0 and mTEPES.dPar['pElecNetPeriodIni'][ln] <= mTEPES.p.last() and mTEPES.dPar['pElecNetPeriodFin'][ln] >= mTEPES.p.first()])
@@ -576,8 +575,8 @@ def DataConfiguration(mTEPES):
576
575
  mTEPES.hn = Set(doc='all input heat pipes' , initialize=mTEPES.dFrame['dfNetworkHeat'].index)
577
576
  if len(mTEPES.hn) != len(mTEPES.dFrame['dfNetworkHeat'].index):
578
577
  raise ValueError('### Some heat pipes are invalid ', len(mTEPES.hn), len(mTEPES.dFrame['dfNetworkHeat'].index))
579
- mTEPES.ha = Set(doc='all real heat pipes' , initialize=[hn for hn in mTEPES.hn if mTEPES.dPar['pHeatPipeNTCFrw'] [hn] > 0.0 and mTEPES.dPar['pHeatPipeNTCBck'][hn] > 0.0 and mTEPES.dPar['pHeatPipePeriodIni'][hn] <= mTEPES.p.last() and mTEPES.dPar['pHeatPipePeriodFin'][hn] >= mTEPES.p.first()])
580
- mTEPES.hc = Set(doc='candidate heat pipes' , initialize=[ha for ha in mTEPES.ha if mTEPES.dPar['pHeatPipeFixedCost'] [ha] > 0.0])
578
+ mTEPES.ha = Set(doc='all real heat pipes' , initialize=[hn for hn in mTEPES.hn if mTEPES.dPar['pHeatPipeNTCFrw'] [hn] > 0.0 and mTEPES.dPar['pHeatPipeNTCBck'][hn] > 0.0 and mTEPES.dPar['pHeatPipePeriodIni'][hn] <= mTEPES.p.last() and mTEPES.dPar['pHeatPipePeriodFin'][hn] >= mTEPES.p.first()])
579
+ mTEPES.hc = Set(doc='candidate heat pipes' , initialize=[ha for ha in mTEPES.ha if mTEPES.dPar['pHeatPipeFixedCost'] [ha] > 0.0])
581
580
  # existing heat pipes (he)
582
581
  mTEPES.he = mTEPES.ha - mTEPES.hc
583
582
  else:
@@ -629,6 +628,7 @@ def DataConfiguration(mTEPES):
629
628
  mTEPES.n = Set(doc='load levels', initialize=[nn for nn in mTEPES.nn if sum(mTEPES.dPar['pDuration'][p,sc,nn] for p,sc in mTEPES.ps) > 0])
630
629
  mTEPES.n2 = Set(doc='load levels', initialize=[nn for nn in mTEPES.nn if sum(mTEPES.dPar['pDuration'][p,sc,nn] for p,sc in mTEPES.ps) > 0])
631
630
  # instrumental sets
631
+ # @profile
632
632
  def CreateInstrumentalSets(mTEPES, pIndHydroTopology, pIndHydrogen, pIndHeat, pIndPTDF) -> None:
633
633
  '''
634
634
  Create mTEPES instrumental sets.
@@ -686,12 +686,12 @@ def DataConfiguration(mTEPES):
686
686
  mTEPES.psnnd = Set(initialize = [(p,sc,n,nd) for p,sc,n,nd in mTEPES.psn*mTEPES.nd ])
687
687
  mTEPES.psnar = Set(initialize = [(p,sc,n,ar) for p,sc,n,ar in mTEPES.psn*mTEPES.ar ])
688
688
 
689
- 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 ])
690
- 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 ])
691
- 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 ])
692
- 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 ])
689
+ 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])
690
+ 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])
691
+ 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])
692
+ 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])
693
693
 
694
- mTEPES.psnehc = Set(initialize = [(p,sc,n,eh) for p,sc,n,eh in mTEPES.psneh if mTEPES.dPar['pRatedMaxCharge'][eh] > 0.0 ])
694
+ mTEPES.psnehc = Set(initialize = [(p,sc,n,eh) for p,sc,n,eh in mTEPES.psneh if mTEPES.dPar['pRatedMaxCharge'][eh] > 0.0])
695
695
 
696
696
  if pIndHydroTopology == 1:
697
697
  mTEPES.prs = Set(initialize = [(p, rs) for p, rs in mTEPES.p *mTEPES.rs if mTEPES.dPar['pRsrPeriodIni'][rs] <= p and mTEPES.dPar['pRsrPeriodFin'][rs] >= p])
@@ -829,32 +829,42 @@ def DataConfiguration(mTEPES):
829
829
 
830
830
  # mTEPES.z2g = Set(initialize = [(zn,g) for zn,g in mTEPES.zn*mTEPES.g if (zn,g) in pZone2Gen])
831
831
 
832
+ # detect technologies not declared in the technology dictionary
833
+ for g in mTEPES.g:
834
+ if mTEPES.dPar['pGenToTechnology'].loc[g] not in mTEPES.gt:
835
+ raise ValueError(f'### Generator {g} belongs to a technology not declared in the technology dictionary.')
836
+
832
837
  #%% inverse index generator to technology
833
838
  mTEPES.dPar['pTechnologyToGen'] = mTEPES.dPar['pGenToTechnology'].reset_index().set_index('Technology').set_axis(['Generator'], axis=1)[['Generator']]
834
839
  mTEPES.dPar['pTechnologyToGen'] = mTEPES.dPar['pTechnologyToGen'].loc[mTEPES.dPar['pTechnologyToGen']['Generator'].isin(mTEPES.g)].reset_index().set_index(['Technology', 'Generator'])
835
840
 
836
841
  mTEPES.t2g = Set(initialize=mTEPES.dPar['pTechnologyToGen'].index, doc='technology to generator')
837
842
 
843
+ g2t = defaultdict(list)
844
+ for gt,g in mTEPES.t2g:
845
+ g2t[gt].append(g)
846
+
838
847
  # ESS and RES technologies
848
+ # @profile
839
849
  def Create_ESS_RES_Sets(mTEPES) -> None:
840
- mTEPES.ot = Set(doc='ESS technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for es in mTEPES.es if (gt,es) in mTEPES.t2g)])
841
- mTEPES.ht = Set(doc='hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for h in mTEPES.h if (gt,h ) in mTEPES.t2g)])
842
- mTEPES.et = Set(doc='ESS & hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for eh in mTEPES.eh if (gt,eh) in mTEPES.t2g)])
843
- mTEPES.rt = Set(doc=' RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for re in mTEPES.re if (gt,re) in mTEPES.t2g)])
844
- mTEPES.nt = Set(doc='non-RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for nr in mTEPES.nr if (gt,nr) in mTEPES.t2g)])
845
-
846
- mTEPES.psgt = Set(initialize=[(p,sc, gt) for p,sc, gt in mTEPES.ps *mTEPES.gt if sum(1 for g in mTEPES.g if (p,g ) in mTEPES.pg and (gt,g ) in mTEPES.t2g)])
847
- mTEPES.psot = Set(initialize=[(p,sc, ot) for p,sc, ot in mTEPES.ps *mTEPES.ot if sum(1 for es in mTEPES.es if (p,es) in mTEPES.pes and (ot,es) in mTEPES.t2g)])
848
- mTEPES.psht = Set(initialize=[(p,sc, ht) for p,sc, ht in mTEPES.ps *mTEPES.ht if sum(1 for h in mTEPES.h if (p,h ) in mTEPES.ph and (ht,h ) in mTEPES.t2g)])
849
- mTEPES.pset = Set(initialize=[(p,sc, et) for p,sc, et in mTEPES.ps *mTEPES.et if sum(1 for eh in mTEPES.eh if (p,eh) in mTEPES.peh and (et,eh) in mTEPES.t2g)])
850
- mTEPES.psrt = Set(initialize=[(p,sc, rt) for p,sc, rt in mTEPES.ps *mTEPES.rt if sum(1 for re in mTEPES.re if (p,re) in mTEPES.pre and (rt,re) in mTEPES.t2g)])
851
- mTEPES.psnt = Set(initialize=[(p,sc, nt) for p,sc, nt in mTEPES.ps *mTEPES.nt if sum(1 for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and (nt,nr) in mTEPES.t2g)])
852
- mTEPES.psngt = Set(initialize=[(p,sc,n,gt) for p,sc,n,gt in mTEPES.psn*mTEPES.gt if (p,sc,gt) in mTEPES.psgt])
853
- mTEPES.psnot = Set(initialize=[(p,sc,n,ot) for p,sc,n,ot in mTEPES.psn*mTEPES.ot if (p,sc,ot) in mTEPES.psot])
854
- mTEPES.psnht = Set(initialize=[(p,sc,n,ht) for p,sc,n,ht in mTEPES.psn*mTEPES.ht if (p,sc,ht) in mTEPES.psht])
855
- mTEPES.psnet = Set(initialize=[(p,sc,n,et) for p,sc,n,et in mTEPES.psn*mTEPES.et if (p,sc,et) in mTEPES.pset])
856
- mTEPES.psnrt = Set(initialize=[(p,sc,n,rt) for p,sc,n,rt in mTEPES.psn*mTEPES.rt if (p,sc,rt) in mTEPES.psrt])
857
- mTEPES.psnnt = Set(initialize=[(p,sc,n,nt) for p,sc,n,nt in mTEPES.psn*mTEPES.nt if (p,sc,nt) in mTEPES.psnt])
850
+ mTEPES.ot = Set(doc='ESS technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for es in mTEPES.es if es in g2t[gt])])
851
+ mTEPES.ht = Set(doc='hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for h in mTEPES.h if h in g2t[gt])])
852
+ mTEPES.et = Set(doc='ESS & hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for eh in mTEPES.eh if eh in g2t[gt])])
853
+ mTEPES.rt = Set(doc=' RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for re in mTEPES.re if re in g2t[gt])])
854
+ mTEPES.nt = Set(doc='non-RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for nr in mTEPES.nr if nr in g2t[gt])])
855
+
856
+ mTEPES.psgt = Set(initialize=[(p,sc, gt) for p,sc, gt in mTEPES.ps *mTEPES.gt if sum(1 for g in mTEPES.g if g in g2t[gt] and (p,g ) in mTEPES.pg )])
857
+ mTEPES.psot = Set(initialize=[(p,sc, ot) for p,sc, ot in mTEPES.ps *mTEPES.ot if sum(1 for es in mTEPES.es if es in g2t[ot] and (p,es) in mTEPES.pes)])
858
+ mTEPES.psht = Set(initialize=[(p,sc, ht) for p,sc, ht in mTEPES.ps *mTEPES.ht if sum(1 for h in mTEPES.h if h in g2t[ht] and (p,h ) in mTEPES.ph )])
859
+ mTEPES.pset = Set(initialize=[(p,sc, et) for p,sc, et in mTEPES.ps *mTEPES.et if sum(1 for eh in mTEPES.eh if eh in g2t[et] and (p,eh) in mTEPES.peh)])
860
+ mTEPES.psrt = Set(initialize=[(p,sc, rt) for p,sc, rt in mTEPES.ps *mTEPES.rt if sum(1 for re in mTEPES.re if re in g2t[rt] and (p,re) in mTEPES.pre)])
861
+ mTEPES.psnt = Set(initialize=[(p,sc, nt) for p,sc, nt in mTEPES.ps *mTEPES.nt if sum(1 for nr in mTEPES.nr if nr in g2t[nt] and (p,nr) in mTEPES.pnr)])
862
+ mTEPES.psngt = Set(initialize=[(p,sc,n,gt) for p,sc,n,gt in mTEPES.psn*mTEPES.gt if (p,sc,gt) in mTEPES.psgt])
863
+ mTEPES.psnot = Set(initialize=[(p,sc,n,ot) for p,sc,n,ot in mTEPES.psn*mTEPES.ot if (p,sc,ot) in mTEPES.psot])
864
+ mTEPES.psnht = Set(initialize=[(p,sc,n,ht) for p,sc,n,ht in mTEPES.psn*mTEPES.ht if (p,sc,ht) in mTEPES.psht])
865
+ mTEPES.psnet = Set(initialize=[(p,sc,n,et) for p,sc,n,et in mTEPES.psn*mTEPES.et if (p,sc,et) in mTEPES.pset])
866
+ mTEPES.psnrt = Set(initialize=[(p,sc,n,rt) for p,sc,n,rt in mTEPES.psn*mTEPES.rt if (p,sc,rt) in mTEPES.psrt])
867
+ mTEPES.psnnt = Set(initialize=[(p,sc,n,nt) for p,sc,n,nt in mTEPES.psn*mTEPES.nt if (p,sc,nt) in mTEPES.psnt])
858
868
 
859
869
  Create_ESS_RES_Sets(mTEPES)
860
870
 
@@ -867,7 +877,7 @@ def DataConfiguration(mTEPES):
867
877
  group_dict.setdefault(group, []).append(generator)
868
878
 
869
879
  # These sets store all groups and the generators in them
870
- mTEPES.ExclusiveGroups = Set(initialize=list(group_dict.keys()))
880
+ mTEPES.ExclusiveGroups = Set(initialize=list(group_dict.keys()))
871
881
  mTEPES.GeneratorsInExclusiveGroup = Set(mTEPES.ExclusiveGroups, initialize=group_dict)
872
882
 
873
883
  # Create filtered dictionaries for Yearly and Hourly groups
@@ -882,14 +892,14 @@ def DataConfiguration(mTEPES):
882
892
 
883
893
  # The exclusive groups sets have all groups which are mutually exclusive in that time scope
884
894
  # Generators in group sets are sets with the corresponding generators to a given group
885
- mTEPES.ExclusiveGroupsYearly = Set(initialize=list(group_dict_yearly.keys()))
895
+ mTEPES.ExclusiveGroupsYearly = Set(initialize=list(group_dict_yearly.keys()))
886
896
  mTEPES.GeneratorsInYearlyGroup = Set(mTEPES.ExclusiveGroupsYearly, initialize=group_dict_yearly)
887
897
 
888
- mTEPES.ExclusiveGroupsHourly = Set(initialize=list(group_dict_hourly.keys()))
898
+ mTEPES.ExclusiveGroupsHourly = Set(initialize=list(group_dict_hourly.keys()))
889
899
  mTEPES.GeneratorsInHourlyGroup = Set(mTEPES.ExclusiveGroupsHourly, initialize=group_dict_hourly)
890
900
 
891
901
  # All exclusive generators (sorting to ensure deterministic behavior)
892
- mTEPES.ExclusiveGenerators = Set(initialize=sorted(sum(group_dict.values(), [])))
902
+ mTEPES.ExclusiveGenerators = Set(initialize=sorted(sum(group_dict.values(), [])))
893
903
  # All yearly exclusive generators (sorting to ensure deterministic behavior)
894
904
  mTEPES.ExclusiveGeneratorsYearly = Set(initialize=sorted(sum(group_dict_yearly.values(), [])))
895
905
  # All hourly exclusive generators (sorting to ensure deterministic behavior)
@@ -1508,7 +1518,7 @@ def DataConfiguration(mTEPES):
1508
1518
  mTEPES.pRampReserveDw = Param(mTEPES.psnar, initialize=mTEPES.dPar['pRampReserveDw'].to_dict() , within=NonNegativeReals, doc='Ramp down reserve' )
1509
1519
 
1510
1520
  if mTEPES.dPar['pIndHydrogen'] == 1:
1511
- mTEPES.pProductionFunctionH2 = Param(mTEPES.el, initialize=mTEPES.dPar['pProductionFunctionH2'].to_dict(), within=NonNegativeReals, doc='Production function of an electrolyzer plant')
1521
+ mTEPES.pProductionFunctionH2 = Param(mTEPES.el, initialize=mTEPES.dPar['pProductionFunctionH2'].to_dict() , within=NonNegativeReals, doc='Production function of an electrolyzer plant' )
1512
1522
 
1513
1523
  if mTEPES.dPar['pIndHeat'] == 1:
1514
1524
  mTEPES.dPar['pMinPowerHeat'] = filter_rows(mTEPES.dPar['pMinPowerHeat'], mTEPES.psnch)
@@ -1627,10 +1637,10 @@ def DataConfiguration(mTEPES):
1627
1637
  mTEPES.pHeatPipeUpInvest = Param(mTEPES.hc, initialize=mTEPES.dPar['pHeatPipeUpInvest'].to_dict() , within=NonNegativeReals, doc='Upper bound of the heat pipe investment decision', mutable=True)
1628
1638
 
1629
1639
  # load levels multiple of cycles for each ESS/generator
1630
- mTEPES.nesc = [(n,es) for n,es in mTEPES.n*mTEPES.es if mTEPES.n.ord(n) % mTEPES.pStorageTimeStep [es] == 0]
1631
- mTEPES.necc = [(n,ec) for n,ec in mTEPES.n*mTEPES.ec if mTEPES.n.ord(n) % mTEPES.pStorageTimeStep [ec] == 0]
1632
- mTEPES.neso = [(n,es) for n,es in mTEPES.n*mTEPES.es if mTEPES.n.ord(n) % mTEPES.pOutflowsTimeStep[es] == 0]
1633
- mTEPES.ngen = [(n,g ) for n,g in mTEPES.n*mTEPES.g if mTEPES.n.ord(n) % mTEPES.pEnergyTimeStep [g ] == 0]
1640
+ mTEPES.nesc = [(n,es) for n,es in mTEPES.n*mTEPES.es if mTEPES.n.ord(n) % mTEPES.pStorageTimeStep [es] == 0]
1641
+ mTEPES.necc = [(n,ec) for n,ec in mTEPES.n*mTEPES.ec if mTEPES.n.ord(n) % mTEPES.pStorageTimeStep [ec] == 0]
1642
+ mTEPES.neso = [(n,es) for n,es in mTEPES.n*mTEPES.es if mTEPES.n.ord(n) % mTEPES.pOutflowsTimeStep[es] == 0]
1643
+ mTEPES.ngen = [(n,g ) for n,g in mTEPES.n*mTEPES.g if mTEPES.n.ord(n) % mTEPES.pEnergyTimeStep [g ] == 0]
1634
1644
  if mTEPES.dPar['pIndHydroTopology'] == 1:
1635
1645
  mTEPES.nhc = [(n,h ) for n,h in mTEPES.n*mTEPES.h if mTEPES.n.ord(n) % sum(mTEPES.pReservoirTimeStep[rs] for rs in mTEPES.rs if (rs,h) in mTEPES.r2h) == 0]
1636
1646
  if sum(1 for h,rs in mTEPES.p2r):
@@ -1697,6 +1707,8 @@ def DataConfiguration(mTEPES):
1697
1707
  def SettingUpVariables(OptModel, mTEPES):
1698
1708
 
1699
1709
  StartTime = time.time()
1710
+
1711
+ # @profile
1700
1712
  def CreateVariables(mTEPES, OptModel) -> None:
1701
1713
  '''
1702
1714
  Create all mTEPES variables.
@@ -1847,6 +1859,7 @@ def SettingUpVariables(OptModel, mTEPES):
1847
1859
 
1848
1860
  CreateVariables(mTEPES, mTEPES)
1849
1861
  # assign lower and upper bounds to variables
1862
+ # @profile
1850
1863
  def setVariableBounds(mTEPES, OptModel) -> None:
1851
1864
  '''
1852
1865
  Set upper/lower bounds.
@@ -1891,6 +1904,8 @@ def SettingUpVariables(OptModel, mTEPES):
1891
1904
  setVariableBounds(mTEPES, mTEPES)
1892
1905
 
1893
1906
  nFixedVariables = 0
1907
+
1908
+ # @profile
1894
1909
  def RelaxBinaryInvestmentConditions(mTEPES, OptModel) -> int:
1895
1910
  '''
1896
1911
  Relax binary investment variables.
@@ -1963,26 +1978,26 @@ def SettingUpVariables(OptModel, mTEPES):
1963
1978
  OptModel.vHeatPipeInvest [p,ni,nf,cc].domain = UnitInterval
1964
1979
  nFixedVariables += 1
1965
1980
 
1966
- # relax binary condition in unit generation, startup and shutdown decisions
1981
+ # relax binary condition in unit generation, startup, and shutdown decisions
1967
1982
  for p,sc,n,nr in mTEPES.psnnr:
1968
1983
  if mTEPES.pIndBinUnitCommit[nr] == 0:
1969
- OptModel.vCommitment [p,sc,n,nr].domain = UnitInterval
1970
- OptModel.vStartUp [p,sc,n,nr].domain = UnitInterval
1971
- OptModel.vShutDown [p,sc,n,nr].domain = UnitInterval
1984
+ OptModel.vCommitment [p,sc,n,nr].domain = UnitInterval
1985
+ OptModel.vStartUp [p,sc,n,nr].domain = UnitInterval
1986
+ OptModel.vShutDown [p,sc,n,nr].domain = UnitInterval
1972
1987
  # fix variables for units that don't have minimum stable time
1973
1988
  if mTEPES.pStableTime[nr] == 0.0 or mTEPES.pMaxPower2ndBlock[p,sc,n,nr] == 0.0:
1974
- OptModel.vStableState [p,sc,n,nr].fix(0)
1975
- OptModel.vRampDwState [p,sc,n,nr].fix(0)
1976
- OptModel.vRampUpState [p,sc,n,nr].fix(0)
1977
- OptModel.vStableState [p,sc,n,nr].domain = UnitInterval
1978
- OptModel.vRampDwState [p,sc,n,nr].domain = UnitInterval
1979
- OptModel.vRampUpState [p,sc,n,nr].domain = UnitInterval
1980
-
1981
- for p,sc,nr, group in mTEPES.psnr * mTEPES.ExclusiveGroups:
1982
- if mTEPES.pIndBinUnitCommit[nr] == 0 and nr in mTEPES.ExclusiveGeneratorsYearly:
1989
+ OptModel.vStableState[p,sc,n,nr].fix(0)
1990
+ OptModel.vRampDwState[p,sc,n,nr].fix(0)
1991
+ OptModel.vRampUpState[p,sc,n,nr].fix(0)
1992
+ OptModel.vStableState[p,sc,n,nr].domain = UnitInterval
1993
+ OptModel.vRampDwState[p,sc,n,nr].domain = UnitInterval
1994
+ OptModel.vRampUpState[p,sc,n,nr].domain = UnitInterval
1995
+
1996
+ for p,sc,nr, group in mTEPES.ps*mTEPES.ExclusiveGeneratorsYearly*mTEPES.ExclusiveGroups:
1997
+ if mTEPES.pIndBinUnitCommit[nr] == 0:
1983
1998
  OptModel.vMaxCommitmentYearly[p,sc, nr,group].domain = UnitInterval
1984
- for p,sc,n,nr,group in mTEPES.psnnr * mTEPES.ExclusiveGroups:
1985
- if mTEPES.pIndBinUnitCommit[nr] == 0 and nr in mTEPES.ExclusiveGeneratorsHourly:
1999
+ for p,sc,n,nr,group in mTEPES.psn*mTEPES.ExclusiveGeneratorsHourly*mTEPES.ExclusiveGroups:
2000
+ if mTEPES.pIndBinUnitCommit[nr] == 0:
1986
2001
  OptModel.vMaxCommitmentHourly[p,sc,n,nr,group].domain = UnitInterval
1987
2002
  if mTEPES.pIndHydroTopology == 1:
1988
2003
  for p,sc,n,h in mTEPES.psnh:
@@ -1999,6 +2014,7 @@ def SettingUpVariables(OptModel, mTEPES):
1999
2014
  nFixedBinaries = RelaxBinaryInvestmentConditions(mTEPES, mTEPES)
2000
2015
  nFixedVariables += nFixedBinaries
2001
2016
 
2017
+ # @profile
2002
2018
  def CreateFlowVariables(mTEPES,OptModel) -> int:
2003
2019
  #TODO use a more descriptive name for nFixedVariables
2004
2020
  '''
@@ -2038,20 +2054,21 @@ def SettingUpVariables(OptModel, mTEPES):
2038
2054
 
2039
2055
  if mTEPES.pIndVarTTC == 1:
2040
2056
  # lines with TTC and TTCBck = 0 are disconnected and the flow is fixed to 0
2041
- [OptModel.vFlowElec[p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnla if mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] == 0.0 and mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc] == 0.0]
2042
- nFixedVariables += sum( 1 for p,sc,n,ni,nf,cc in mTEPES.psnla if mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] == 0.0 and mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc] == 0.0)
2057
+ sPSNLA = [(p,sc,n,ni,nf,cc) for p,sc,n,ni,nf,cc in mTEPES.psnla if mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc] == 0.0 and mTEPES.pMaxNTCBck[p,sc,n,ni,nf,cc] == 0.0]
2058
+ [OptModel.vFlowElec[p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in sPSNLA]
2059
+ nFixedVariables += sum( 1 for p,sc,n,ni,nf,cc in sPSNLA)
2043
2060
 
2044
2061
  if mTEPES.pIndPTDF == 1:
2045
2062
  OptModel.vNetPosition = Var(mTEPES.psnnd, within=Reals, doc='net position in node [GW]')
2046
2063
 
2047
2064
  if mTEPES.pIndBinSingleNode() == 0:
2048
2065
  [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]
2049
- [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]
2050
- [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]
2066
+ [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]
2067
+ [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]
2051
2068
  else:
2052
- [OptModel.vLineLosses[p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnll]
2053
- [OptModel.vTheta [p,sc,n,nd ].setlb(-mTEPES.pMaxTheta [p,sc,n,nd ]()) for p,sc,n,nd in mTEPES.psnnd]
2054
- [OptModel.vTheta [p,sc,n,nd ].setub( mTEPES.pMaxTheta [p,sc,n,nd ]()) for p,sc,n,nd in mTEPES.psnnd]
2069
+ [OptModel.vLineLosses[p,sc,n,ni,nf,cc].fix(0.0 ) for p,sc,n,ni,nf,cc in mTEPES.psnll]
2070
+ [OptModel.vTheta [p,sc,n,nd ].setlb(-mTEPES.pMaxTheta [p,sc,n,nd ]() ) for p,sc,n,nd in mTEPES.psnnd]
2071
+ [OptModel.vTheta [p,sc,n,nd ].setub( mTEPES.pMaxTheta [p,sc,n,nd ]() ) for p,sc,n,nd in mTEPES.psnnd]
2055
2072
 
2056
2073
  if mTEPES.pIndHydrogen == 1:
2057
2074
  OptModel.vFlowH2 = Var(mTEPES.psnpa, within=Reals, doc='pipeline flow [tH2]')
@@ -2071,6 +2088,7 @@ def SettingUpVariables(OptModel, mTEPES):
2071
2088
  nFixedLineCommitments = CreateFlowVariables(mTEPES, mTEPES)
2072
2089
  nFixedVariables += nFixedLineCommitments
2073
2090
 
2091
+ # @profile
2074
2092
  def FixGeneratorsCommitment(mTEPES, OptModel) -> int:
2075
2093
  '''
2076
2094
  Fix commitment variables.
@@ -2087,11 +2105,13 @@ def SettingUpVariables(OptModel, mTEPES):
2087
2105
  nFixedVariables = 0
2088
2106
 
2089
2107
  # must-run units must produce at least their minimum output
2090
- [OptModel.vTotalOutput[p,sc,n,g].setlb(mTEPES.pMinPowerElec[p,sc,n,g]) for p,sc,n,g in mTEPES.psng if mTEPES.pMustRun[g] == 1 and g not in mTEPES.gc]
2108
+ sPSNG = [(p,sc,n,g) for p,sc,n,g in mTEPES.psng if mTEPES.pMustRun[g] == 1 and g not in mTEPES.gc]
2109
+ [OptModel.vTotalOutput[p,sc,n,g].setlb(mTEPES.pMinPowerElec[p,sc,n,g]) for p,sc,n,g in sPSNG]
2091
2110
 
2092
2111
  # if no max power, no total output
2093
- [OptModel.vTotalOutput[p,sc,n,g].fix(0.0) for p,sc,n,g in mTEPES.psng if mTEPES.pMaxPowerElec[p,sc,n,g] == 0.0]
2094
- nFixedVariables += sum( 1 for p,sc,n,g in mTEPES.psng if mTEPES.pMaxPowerElec[p,sc,n,g] == 0.0)
2112
+ sPSNG = [(p,sc,n,g) for p,sc,n,g in mTEPES.psng if mTEPES.pMaxPowerElec[p,sc,n,g] == 0.0]
2113
+ [OptModel.vTotalOutput[p,sc,n,g].fix(0.0) for p,sc,n,g in sPSNG]
2114
+ nFixedVariables += sum( 1 for p,sc,n,g in sPSNG)
2095
2115
 
2096
2116
  for p,sc,n,nr in mTEPES.psnnr:
2097
2117
  # must-run existing units or units with no minimum power, or ESS existing units are always committed and must produce at least their minimum output
@@ -2107,7 +2127,7 @@ def SettingUpVariables(OptModel, mTEPES):
2107
2127
  nFixedVariables += 3
2108
2128
  # If there are mutually exclusive groups do not fix variables from ESS in mutually exclusive groups
2109
2129
  elif len(mTEPES.ExclusiveGroups) > 0 and nr not in mTEPES.ExclusiveGenerators:
2110
- if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pConstantVarCost[p,sc,n,nr] == 0.0) or nr in mTEPES.es) and nr not in mTEPES.ec and nr not in mTEPES.h:
2130
+ if (mTEPES.pMustRun[nr] == 1 or (mTEPES.pMinPowerElec[p,sc,n,nr] == 0.0 and mTEPES.pConstantVarCost[p,sc,n,nr] == 0.0) or nr in mTEPES.es) and nr not in mTEPES.ec and nr not in mTEPES.h:
2111
2131
  OptModel.vCommitment [p,sc,n,nr].fix(1)
2112
2132
  OptModel.vStartUp [p,sc,n,nr].fix(0)
2113
2133
  OptModel.vShutDown [p,sc,n,nr].fix(0)
@@ -2184,7 +2204,7 @@ def SettingUpVariables(OptModel, mTEPES):
2184
2204
  OptModel.vESSReserveDown [p,sc,n,h ].fix(0.0)
2185
2205
  nFixedVariables += 2
2186
2206
  return nFixedVariables
2187
- nFixedGeneratorCommits = FixGeneratorsCommitment(mTEPES,mTEPES)
2207
+ nFixedGeneratorCommits = FixGeneratorsCommitment(mTEPES, mTEPES)
2188
2208
  nFixedVariables += nFixedGeneratorCommits
2189
2209
  # thermal, ESS, and RES units ordered by increasing variable operation cost, excluding reactive generating units
2190
2210
  if mTEPES.tq:
@@ -2199,6 +2219,24 @@ def SettingUpVariables(OptModel, mTEPES):
2199
2219
  for ar,g in mTEPES.ar*mTEPES.g:
2200
2220
  if (ar,g) in mTEPES.a2g:
2201
2221
  g2a[ar].append(g)
2222
+ n2a = defaultdict(list)
2223
+ for ar,nr in mTEPES.ar*mTEPES.nr:
2224
+ if (ar,nr) in mTEPES.a2g:
2225
+ n2a[ar].append(nr)
2226
+ e2a = defaultdict(list)
2227
+ for ar,es in mTEPES.ar*mTEPES.es:
2228
+ if (ar,es) in mTEPES.a2g:
2229
+ e2a[ar].append(es)
2230
+ o2a = defaultdict(list)
2231
+ for ar,go in mTEPES.ar*mTEPES.go:
2232
+ if (ar,go) in mTEPES.a2g:
2233
+ o2a[ar].append(go)
2234
+
2235
+ # nodes to area (d2a)
2236
+ d2a = defaultdict(list)
2237
+ for ar,nd in mTEPES.ar*mTEPES.nd:
2238
+ if (nd,ar) in mTEPES.ndar:
2239
+ d2a[ar].append(nd)
2202
2240
 
2203
2241
  for p,sc,st in mTEPES.ps*mTEPES.stt:
2204
2242
  # activate only period, scenario, and load levels to formulate
@@ -2213,15 +2251,15 @@ def SettingUpVariables(OptModel, mTEPES):
2213
2251
  # commit the units of each area and their output at the first load level of each stage
2214
2252
  for ar in mTEPES.ar:
2215
2253
  pSystemOutput = 0.0
2216
- for nr in mTEPES.nr:
2217
- 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:
2254
+ for nr in n2a[ar]:
2255
+ if (p,nr) in mTEPES.pnr and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in d2a[ar]) and mTEPES.pMustRun[nr] == 1:
2218
2256
  mTEPES.pInitialOutput[n1,nr] = mTEPES.pMaxPowerElec [n1,nr]
2219
2257
  mTEPES.pInitialUC [n1,nr] = 1
2220
2258
  pSystemOutput += mTEPES.pInitialOutput[n1,nr]()
2221
2259
 
2222
2260
  # determine the initially committed units and their output at the first load level of each period, scenario, and stage
2223
- for go in mTEPES.go:
2224
- 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:
2261
+ for go in o2a[ar]:
2262
+ if (p,go) in mTEPES.pg and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in d2a[ar]) and mTEPES.pMustRun[go] == 0:
2225
2263
  if go in mTEPES.re:
2226
2264
  mTEPES.pInitialOutput[n1,go] = mTEPES.pMaxPowerElec [n1,go]
2227
2265
  else:
@@ -2295,16 +2333,16 @@ def SettingUpVariables(OptModel, mTEPES):
2295
2333
  nFixedVariables += 1
2296
2334
 
2297
2335
  # if no operating reserve is required, no variables are needed
2298
- for p,sc,n,ar,nr in mTEPES.psnar*mTEPES.nr:
2299
- if (ar,nr) in mTEPES.a2g and (p,nr) in mTEPES.pnr:
2336
+ for p,sc,n,ar,nr in mTEPES.psn*mTEPES.ar*mTEPES.nr:
2337
+ if nr in n2a[ar] and (p,sc,n,nr) in mTEPES.psnnr:
2300
2338
  if mTEPES.pOperReserveUp [p,sc,n,ar] == 0.0:
2301
2339
  OptModel.vReserveUp [p,sc,n,nr].fix(0.0)
2302
2340
  nFixedVariables += 1
2303
2341
  if mTEPES.pOperReserveDw [p,sc,n,ar] == 0.0:
2304
2342
  OptModel.vReserveDown [p,sc,n,nr].fix(0.0)
2305
2343
  nFixedVariables += 1
2306
- for p,sc,n,ar,es in mTEPES.psnar*mTEPES.es:
2307
- if (ar,es) in mTEPES.a2g and (p,es) in mTEPES.pes:
2344
+ for p,sc,n,ar,es in mTEPES.psn*mTEPES.ar*mTEPES.es:
2345
+ if es in e2a[ar] and (p,sc,n,es) in mTEPES.psnes:
2308
2346
  if mTEPES.pOperReserveUp [p,sc,n,ar] == 0.0:
2309
2347
  OptModel.vESSReserveUp [p,sc,n,es].fix(0.0)
2310
2348
  nFixedVariables += 1
@@ -2355,6 +2393,7 @@ def SettingUpVariables(OptModel, mTEPES):
2355
2393
  OptModel.vHeatNS [p,sc,n,nd].fix (0.0)
2356
2394
  nFixedVariables += 1
2357
2395
 
2396
+ # @profile
2358
2397
  def AvoidForbiddenInstallationsAndRetirements(mTEPES, OptModel) -> int:
2359
2398
  '''
2360
2399
  Fix installations/retirements forbidden by period.
@@ -2530,6 +2569,7 @@ def SettingUpVariables(OptModel, mTEPES):
2530
2569
 
2531
2570
  # tolerance to consider 0 an investment decision
2532
2571
  pEpsilon = 1e-4
2572
+ # @profile
2533
2573
  def SetToZero(mTEPES, OptModel, pEpsilon) -> None:
2534
2574
  '''
2535
2575
  Set small numbers to 0.
@@ -2631,6 +2671,7 @@ def SettingUpVariables(OptModel, mTEPES):
2631
2671
 
2632
2672
  SetToZero(mTEPES, OptModel, pEpsilon)
2633
2673
 
2674
+ # @profile
2634
2675
  def DetectInfeasibilities(mTEPES) -> None:
2635
2676
  '''
2636
2677
  Detect model infeasibilities.
@@ -2656,16 +2697,16 @@ def SettingUpVariables(OptModel, mTEPES):
2656
2697
  pEpsilon = 1e-6
2657
2698
  for p in mTEPES.p:
2658
2699
  if abs(sum(mTEPES.pScenProb[p,sc]() for sc in mTEPES.sc if (p,sc) in mTEPES.ps)-1.0) > pEpsilon:
2659
- raise ValueError('### Sum of scenario probabilities different from 1 in period ', p)
2700
+ raise ValueError(f'### Sum of scenario probabilities different from 1 in period {p}.')
2660
2701
 
2661
2702
  for es in mTEPES.es:
2662
2703
  # detecting infeasibility: total min ESS output greater than total inflows, total max ESS charge lower than total outflows, or no storage capacity for charging
2663
- 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:
2664
- 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')
2665
- 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:
2666
- 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')
2667
- if max(mTEPES.pMaxCharge[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) and max(mTEPES.pMaxPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) and max(mTEPES.pMaxStorage[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) == 0.0:
2668
- raise ValueError('### This ESS unit has no storage capacity for charging ', es, ' ', sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' MW ', sum(mTEPES.pMaxStorage [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
2704
+ 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:
2705
+ 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')
2706
+ 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:
2707
+ 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')
2708
+ if max(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) and max(mTEPES.pMaxPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) and max(mTEPES.pMaxStorage[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) == 0.0:
2709
+ raise ValueError('### This ESS unit has no storage capacity for charging ', es, ' ', sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' MW ', sum(mTEPES.pMaxStorage[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
2669
2710
 
2670
2711
  # detect inventory infeasibility
2671
2712
  for p,sc,n,es in mTEPES.ps*mTEPES.nesc:
@@ -2680,10 +2721,9 @@ def SettingUpVariables(OptModel, mTEPES):
2680
2721
 
2681
2722
  # detect minimum energy infeasibility
2682
2723
  for p,sc,n,g in mTEPES.ps*mTEPES.ngen:
2683
- if (p,g) in mTEPES.pg:
2684
- if (p,sc,g) in mTEPES.gm:
2685
- if sum((mTEPES.pMaxPowerElec[p,sc,n2,g] - mTEPES.pMinEnergy[p,sc,n2,g])*mTEPES.pDuration[p,sc,n2]() for n2 in list(mTEPES.n2)[mTEPES.n.ord(n) - mTEPES.pEnergyTimeStep[g]:mTEPES.n.ord(n)]) < 0.0:
2686
- raise ValueError('### Minimum energy violation ', p, sc, n, g, sum((mTEPES.pMaxPowerElec[p,sc,n2,g] - mTEPES.pMinEnergy[p,sc,n2,g])*mTEPES.pDuration[p,sc,n2]() for n2 in list(mTEPES.n2)[mTEPES.n.ord(n) - mTEPES.pEnergyTimeStep[g]:mTEPES.n.ord(n)]))
2724
+ if (p,sc,g) in mTEPES.gm and (p,g) in mTEPES.pg:
2725
+ if sum((mTEPES.pMaxPowerElec[p,sc,n2,g] - mTEPES.pMinEnergy[p,sc,n2,g])*mTEPES.pDuration[p,sc,n2]() for n2 in list(mTEPES.n2)[mTEPES.n.ord(n) - mTEPES.pEnergyTimeStep[g]:mTEPES.n.ord(n)]) < 0.0:
2726
+ raise ValueError('### Minimum energy violation ', p, sc, n, g, sum((mTEPES.pMaxPowerElec[p,sc,n2,g] - mTEPES.pMinEnergy[p,sc,n2,g])*mTEPES.pDuration[p,sc,n2]() for n2 in list(mTEPES.n2)[mTEPES.n.ord(n) - mTEPES.pEnergyTimeStep[g]:mTEPES.n.ord(n)]))
2687
2727
 
2688
2728
  # detecting infeasibility: no capacity in the transmission network
2689
2729
  if sum(mTEPES.pLineNTCMax[:,:,:]) == 0.0:
@@ -2691,16 +2731,16 @@ def SettingUpVariables(OptModel, mTEPES):
2691
2731
 
2692
2732
  # detecting reserve margin infeasibility
2693
2733
  for p,ar in mTEPES.p*mTEPES.ar:
2694
- if sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g) < mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar]:
2695
- raise ValueError('### Electricity reserve margin infeasibility ', p,ar, sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g), mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2734
+ if sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in g2a[ar] if (p,g) in mTEPES.pg) < mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar]:
2735
+ raise ValueError('### Electricity reserve margin infeasibility ', p,ar, sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and g in g2a[ar]), mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2696
2736
 
2697
2737
  if mTEPES.pIndHeat == 1:
2698
- if sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g) < mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMarginHeat[p,ar]:
2699
- raise ValueError('### Heat reserve margin infeasibility ', p,ar, sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g), mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2738
+ if sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in g2a[ar] if (p,g) in mTEPES.pg) < mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMarginHeat[p,ar]:
2739
+ raise ValueError('### Heat reserve margin infeasibility ', p,ar, sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and g in g2a[ar]), mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2700
2740
 
2701
- for p,sc,ar in mTEPES.p*mTEPES.sc*mTEPES.ar:
2702
- if mTEPES.pRESEnergy[p,ar] > sum(mTEPES.pDemandElec[p,sc,n,nd]*mTEPES.pLoadLevelDuration[p,sc,n]() for n,nd in mTEPES.n*mTEPES.nd if (nd,ar) in mTEPES.ndar and (p,sc,n,nd) in mTEPES.psnnd):
2703
- raise ValueError('### Minimum renewable energy requirement exceeds the demand ', p, sc, ar, mTEPES.pRESEnergy[p,ar], sum(mTEPES.pDemandElec[p,sc,n,nd] for n,nd in mTEPES.n*mTEPES.nd if (nd,ar) in mTEPES.ndar and (p,sc,n,nd) in mTEPES.psnnd))
2741
+ for p,sc,ar in mTEPES.ps*mTEPES.ar:
2742
+ if mTEPES.pRESEnergy[p,ar] > sum(mTEPES.pDemandElec[p,sc,n,nd]*mTEPES.pLoadLevelDuration[p,sc,n]() for n,nd in mTEPES.n*d2a[ar] if (p,sc,n,nd) in mTEPES.psnnd):
2743
+ raise ValueError('### Minimum renewable energy requirement exceeds the demand ', p, sc, ar, mTEPES.pRESEnergy[p,ar], sum(mTEPES.pDemandElec[p,sc,n,nd] for n,nd in mTEPES.n*d2a[ar] if (p,sc,n,nd) in mTEPES.psnnd))
2704
2744
 
2705
2745
  DetectInfeasibilities(mTEPES)
2706
2746
 
@@ -2745,10 +2785,10 @@ def SettingUpVariables(OptModel, mTEPES):
2745
2785
  Stage.n2 = Set(doc='load levels', initialize=[nn for pp, scc, stt, nn in mTEPES.s2n if stt == st and scc == sc and pp == p])
2746
2786
 
2747
2787
  # load levels multiple of cycles for each ESS/generator
2748
- Stage.nesc = [(n, es) for n,es in Stage.n * mTEPES.es if Stage.n.ord(n) % mTEPES.pStorageTimeStep[es] == 0]
2749
- Stage.necc = [(n, ec) for n,ec in Stage.n * mTEPES.ec if Stage.n.ord(n) % mTEPES.pStorageTimeStep[ec] == 0]
2750
- Stage.neso = [(n, es) for n,es in Stage.n * mTEPES.es if Stage.n.ord(n) % mTEPES.pOutflowsTimeStep[es] == 0]
2751
- Stage.ngen = [(n, g ) for n,g in Stage.n * mTEPES.g if Stage.n.ord(n) % mTEPES.pEnergyTimeStep[g] == 0]
2788
+ Stage.nesc = [(n,es) for n,es in Stage.n*mTEPES.es if Stage.n.ord(n) % mTEPES.pStorageTimeStep[es] == 0]
2789
+ Stage.necc = [(n,ec) for n,ec in Stage.n*mTEPES.ec if Stage.n.ord(n) % mTEPES.pStorageTimeStep[ec] == 0]
2790
+ Stage.neso = [(n,es) for n,es in Stage.n*mTEPES.es if Stage.n.ord(n) % mTEPES.pOutflowsTimeStep[es] == 0]
2791
+ Stage.ngen = [(n,g ) for n,g in Stage.n*mTEPES.g if Stage.n.ord(n) % mTEPES.pEnergyTimeStep[g] == 0]
2752
2792
 
2753
2793
  if mTEPES.pIndHydroTopology == 1:
2754
2794
  Stage.nhc = [(n,h ) for n,h in Stage.n*mTEPES.h if Stage.n.ord(n) % sum(mTEPES.pReservoirTimeStep[rs] for rs in mTEPES.rs if (rs,h) in mTEPES.r2h) == 0]
@@ -2760,9 +2800,9 @@ def SettingUpVariables(OptModel, mTEPES):
2760
2800
  Stage.npc = [(n,h ) for n,h in mTEPES.n*mTEPES.h if sum(1 for rs in mTEPES.rs if (rs,h) in mTEPES.r2p) and mTEPES.n.ord(n) % sum(mTEPES.pReservoirTimeStep[rs] for rs in mTEPES.rs if (rs,h) in mTEPES.r2p) == 0]
2761
2801
  else:
2762
2802
  Stage.npc = []
2763
- Stage.nrsc = [(n,rs) for n,rs in Stage.n*mTEPES.rs if mTEPES.n.ord(n) % mTEPES.pReservoirTimeStep[rs] == 0]
2764
- Stage.nrcc = [(n,rs) for n,rs in Stage.n*mTEPES.rn if mTEPES.n.ord(n) % mTEPES.pReservoirTimeStep[rs] == 0]
2765
- Stage.nrso = [(n,rs) for n,rs in Stage.n*mTEPES.rs if mTEPES.n.ord(n) % mTEPES.pWaterOutTimeStep [rs] == 0]
2803
+ Stage.nrsc = [(n,rs) for n,rs in Stage.n*mTEPES.rs if mTEPES.n.ord(n) % mTEPES.pReservoirTimeStep[rs] == 0]
2804
+ Stage.nrcc = [(n,rs) for n,rs in Stage.n*mTEPES.rn if mTEPES.n.ord(n) % mTEPES.pReservoirTimeStep[rs] == 0]
2805
+ Stage.nrso = [(n,rs) for n,rs in Stage.n*mTEPES.rs if mTEPES.n.ord(n) % mTEPES.pWaterOutTimeStep [rs] == 0]
2766
2806
 
2767
2807
  SettingUpVariablesTime = time.time() - StartTime
2768
2808
  print('Setting up variables ... ', round(SettingUpVariablesTime), 's')
@@ -660,7 +660,7 @@
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) - January 25, 2026
663
+ # Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - February 05, 2026
664
664
  # simplicity and transparency in power systems planning
665
665
 
666
666
  # Developed by
@@ -693,7 +693,7 @@ GREEN = "\033[32m"
693
693
  BLUE = "\033[34m"
694
694
  RESET = "\033[0m"
695
695
 
696
- print(GREEN + 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.12 - January 28, 2026' + RESET)
696
+ print(GREEN + 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.13 - February 05, 2026' + RESET)
697
697
  print(BLUE + '#### Academic research license - for non-commercial use only ####' + RESET + '\n')
698
698
 
699
699
  parser = argparse.ArgumentParser(description='Introducing main parameters...')