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.
- openTEPES/9n/oT_Data_Stage_9n.csv +1 -1
- openTEPES/RTS24/oT_Dict_Technology_RTS24.csv +2 -0
- openTEPES/__init__.py +1 -1
- openTEPES/openTEPES.py +3 -3
- openTEPES/openTEPES_InputData.py +143 -103
- openTEPES/openTEPES_Main.py +2 -2
- openTEPES/openTEPES_ModelFormulation.py +124 -117
- openTEPES/openTEPES_OutputResults.py +7 -7
- openTEPES/openTEPES_ProblemSolving.py +3 -3
- openTEPES/sSEP/oT_Data_RESEnergy_sSEP.csv +1 -1
- openTEPES/sSEP/oT_Data_Stage_sSEP.csv +1 -1
- {opentepes-4.18.12.dist-info → opentepes-4.18.13.dist-info}/METADATA +24 -13
- {opentepes-4.18.12.dist-info → opentepes-4.18.13.dist-info}/RECORD +16 -16
- {opentepes-4.18.12.dist-info → opentepes-4.18.13.dist-info}/WHEEL +0 -0
- {opentepes-4.18.12.dist-info → opentepes-4.18.13.dist-info}/entry_points.txt +0 -0
- {opentepes-4.18.12.dist-info → opentepes-4.18.13.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
Stage,Weight
|
|
2
2
|
st1,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.
|
|
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) -
|
|
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.
|
|
42
|
-
print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.
|
|
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]
|
openTEPES/openTEPES_InputData.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) -
|
|
2
|
+
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - 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,
|
|
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
|
|
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
|
|
580
|
-
mTEPES.hc = Set(doc='candidate heat pipes' , initialize=[ha for ha in mTEPES.ha
|
|
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
|
|
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
|
|
841
|
-
mTEPES.ht = Set(doc='hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for h in mTEPES.h if
|
|
842
|
-
mTEPES.et = Set(doc='ESS & hydro technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for eh in mTEPES.eh if
|
|
843
|
-
mTEPES.rt = Set(doc=' RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for re in mTEPES.re if
|
|
844
|
-
mTEPES.nt = Set(doc='non-RES technologies', initialize=[gt for gt in mTEPES.gt if sum(1 for nr in mTEPES.nr if
|
|
845
|
-
|
|
846
|
-
mTEPES.psgt = Set(initialize=[(p,sc, gt) for p,sc, gt in mTEPES.ps *mTEPES.gt
|
|
847
|
-
mTEPES.psot = Set(initialize=[(p,sc, ot) for p,sc, ot in mTEPES.ps *mTEPES.ot
|
|
848
|
-
mTEPES.psht = Set(initialize=[(p,sc, ht) for p,sc, ht in mTEPES.ps *mTEPES.ht
|
|
849
|
-
mTEPES.pset = Set(initialize=[(p,sc, et) for p,sc, et in mTEPES.ps *mTEPES.et
|
|
850
|
-
mTEPES.psrt = Set(initialize=[(p,sc, rt) for p,sc, rt in mTEPES.ps *mTEPES.rt
|
|
851
|
-
mTEPES.psnt = Set(initialize=[(p,sc, nt) for p,sc, nt in mTEPES.ps *mTEPES.nt
|
|
852
|
-
mTEPES.psngt = Set(initialize=[(p,sc,n,gt) for p,sc,n,gt in mTEPES.psn*mTEPES.gt
|
|
853
|
-
mTEPES.psnot = Set(initialize=[(p,sc,n,ot) for p,sc,n,ot in mTEPES.psn*mTEPES.ot
|
|
854
|
-
mTEPES.psnht = Set(initialize=[(p,sc,n,ht) for p,sc,n,ht in mTEPES.psn*mTEPES.ht
|
|
855
|
-
mTEPES.psnet = Set(initialize=[(p,sc,n,et) for p,sc,n,et in mTEPES.psn*mTEPES.et
|
|
856
|
-
mTEPES.psnrt = Set(initialize=[(p,sc,n,rt) for p,sc,n,rt in mTEPES.psn*mTEPES.rt
|
|
857
|
-
mTEPES.psnnt = Set(initialize=[(p,sc,n,nt) for p,sc,n,nt in mTEPES.psn*mTEPES.nt
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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) %
|
|
1631
|
-
mTEPES.necc = [(n,ec) for n,ec in mTEPES.n*mTEPES.ec if mTEPES.n.ord(n) %
|
|
1632
|
-
mTEPES.neso = [(n,es) for n,es in mTEPES.n*mTEPES.es if mTEPES.n.ord(n) %
|
|
1633
|
-
mTEPES.ngen = [(n,g ) for n,g in mTEPES.n*mTEPES.g if mTEPES.n.ord(n) %
|
|
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
|
|
1970
|
-
OptModel.vStartUp
|
|
1971
|
-
OptModel.vShutDown
|
|
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
|
|
1975
|
-
OptModel.vRampDwState
|
|
1976
|
-
OptModel.vRampUpState
|
|
1977
|
-
OptModel.vStableState
|
|
1978
|
-
OptModel.vRampDwState
|
|
1979
|
-
OptModel.vRampUpState
|
|
1980
|
-
|
|
1981
|
-
for p,sc,nr, group in mTEPES.
|
|
1982
|
-
if mTEPES.pIndBinUnitCommit[nr] == 0
|
|
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.
|
|
1985
|
-
if mTEPES.pIndBinUnitCommit[nr] == 0
|
|
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
|
-
[
|
|
2042
|
-
|
|
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]
|
|
2050
|
-
[OptModel.vFlowElec [p,sc,n,ni,nf,cc].setub( mTEPES.pMaxNTCFrw[p,sc,n,ni,nf,cc]
|
|
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
|
-
[
|
|
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
|
-
[
|
|
2094
|
-
|
|
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
|
|
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
|
|
2217
|
-
if
|
|
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
|
|
2224
|
-
if
|
|
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.
|
|
2299
|
-
if
|
|
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.
|
|
2307
|
-
if
|
|
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 '
|
|
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) -
|
|
2664
|
-
raise ValueError('### Total minimum output greater than total inflows for ESS unit ', es, ' by ', sum(mTEPES.pMinPowerElec[p,sc,n,es]
|
|
2665
|
-
if sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) -
|
|
2666
|
-
raise ValueError('### Total maximum charge lower than total outflows for ESS unit ', es, ' by ', sum(mTEPES.pMaxCharge [p,sc,n,es]
|
|
2667
|
-
if max(mTEPES.pMaxCharge[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) and
|
|
2668
|
-
raise ValueError('### This ESS unit has no storage capacity for charging ', es, ' ',
|
|
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.
|
|
2685
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
2702
|
-
if mTEPES.pRESEnergy[p,ar] > sum(mTEPES.pDemandElec[p,sc,n,nd]*mTEPES.pLoadLevelDuration[p,sc,n]() for n,nd in mTEPES.n*
|
|
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*
|
|
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,
|
|
2749
|
-
Stage.necc = [(n,
|
|
2750
|
-
Stage.neso = [(n,
|
|
2751
|
-
Stage.ngen = [(n,
|
|
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) %
|
|
2764
|
-
Stage.nrcc = [(n,rs) for n,rs in Stage.n*mTEPES.rn if mTEPES.n.ord(n) %
|
|
2765
|
-
Stage.nrso = [(n,rs) for n,rs in Stage.n*mTEPES.rs if mTEPES.n.ord(n) %
|
|
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')
|
openTEPES/openTEPES_Main.py
CHANGED
|
@@ -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) -
|
|
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.
|
|
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...')
|