openTEPES 4.17.7__py3-none-any.whl → 4.17.9__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/__init__.py +1 -1
- openTEPES/openTEPES.py +10 -10
- openTEPES/openTEPES_InputData.py +46 -30
- openTEPES/openTEPES_Main.py +8 -6
- openTEPES/openTEPES_ModelFormulation.py +226 -198
- openTEPES/openTEPES_OutputResults.py +290 -291
- openTEPES/openTEPES_ProblemSolving.py +11 -11
- {opentepes-4.17.7.dist-info → opentepes-4.17.9.dist-info}/METADATA +5 -6
- {opentepes-4.17.7.dist-info → opentepes-4.17.9.dist-info}/RECORD +12 -12
- {opentepes-4.17.7.dist-info → opentepes-4.17.9.dist-info}/WHEEL +1 -1
- {opentepes-4.17.7.dist-info → opentepes-4.17.9.dist-info}/LICENSE +0 -0
- {opentepes-4.17.7.dist-info → opentepes-4.17.9.dist-info}/entry_points.txt +0 -0
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.17.
|
|
17
|
+
__version__ = "4.17.9"
|
|
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) - November 19, 2024
|
|
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.17.
|
|
43
|
-
print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.
|
|
42
|
+
mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.9 - November 19, 2024')
|
|
43
|
+
print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.9 - November 19, 2024', file=open(_path+f'/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]
|
|
@@ -115,7 +115,7 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
115
115
|
else:
|
|
116
116
|
mTEPES.na = mTEPES.na | mTEPES.n
|
|
117
117
|
|
|
118
|
-
print('Period
|
|
118
|
+
print(f'Period {p}, Scenario {sc}, Stage {st}')
|
|
119
119
|
|
|
120
120
|
# operation model objective function and constraints by stage
|
|
121
121
|
GenerationOperationModelFormulationObjFunct (mTEPES, mTEPES, pIndLogConsole, p, sc, st)
|
|
@@ -142,7 +142,7 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
142
142
|
|
|
143
143
|
if pIndLogConsole == 1:
|
|
144
144
|
StartTime = time.time()
|
|
145
|
-
mTEPES.write(_path+'/openTEPES_
|
|
145
|
+
mTEPES.write(_path+f'/openTEPES_{CaseName}_{p}_{sc}_{st}.lp', io_options={'symbolic_solver_labels': True})
|
|
146
146
|
WritingLPFileTime = time.time() - StartTime
|
|
147
147
|
StartTime = time.time()
|
|
148
148
|
print('Writing LP file ... ', round(WritingLPFileTime), 's')
|
|
@@ -187,7 +187,7 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
187
187
|
|
|
188
188
|
if pIndLogConsole == 1:
|
|
189
189
|
StartTime = time.time()
|
|
190
|
-
mTEPES.write(_path+'/openTEPES_
|
|
190
|
+
mTEPES.write(_path+f'/openTEPES_{CaseName}_{p}_{sc}_{st}.lp', io_options={'symbolic_solver_labels': True})
|
|
191
191
|
WritingLPFileTime = time.time() - StartTime
|
|
192
192
|
StartTime = time.time()
|
|
193
193
|
print('Writing LP file ... ', round(WritingLPFileTime), 's')
|
|
@@ -230,7 +230,7 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
230
230
|
mTEPES.pScenProb[p,sc] = 1.0
|
|
231
231
|
|
|
232
232
|
# pickle the case study data
|
|
233
|
-
# with open(dump_folder+'/oT_Case_
|
|
233
|
+
# with open(dump_folder+f'/oT_Case_{CaseName}.pkl','wb') as f:
|
|
234
234
|
# pickle.dump(mTEPES, f, pickle.HIGHEST_PROTOCOL)
|
|
235
235
|
|
|
236
236
|
# output results only for every unit (0), only for every technology (1), or for both (2)
|
|
@@ -261,7 +261,7 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
261
261
|
pIndEconomicResults = 1
|
|
262
262
|
else:
|
|
263
263
|
pIndDumpRawResults = 0
|
|
264
|
-
pIndInvestmentResults =
|
|
264
|
+
pIndInvestmentResults = 1
|
|
265
265
|
pIndGenerationOperationResults = 0
|
|
266
266
|
pIndESSOperationResults = 0
|
|
267
267
|
pIndReservoirOperationResults = 0
|
|
@@ -271,8 +271,8 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
271
271
|
pIndReliabilityResults = 0
|
|
272
272
|
pIndNetworkOperationResults = 0
|
|
273
273
|
pIndNetworkMapResults = 0
|
|
274
|
-
pIndOperationSummaryResults =
|
|
275
|
-
pIndCostSummaryResults =
|
|
274
|
+
pIndOperationSummaryResults = 1
|
|
275
|
+
pIndCostSummaryResults = 1
|
|
276
276
|
pIndMarginalResults = 0
|
|
277
277
|
pIndEconomicResults = 0
|
|
278
278
|
|
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) - November 12, 2024
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
@@ -498,16 +498,21 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
498
498
|
pNetLoInvest = dfNetwork ['InvestmentLo' ] # Lower bound of the investment decision [p.u.]
|
|
499
499
|
pNetUpInvest = dfNetwork ['InvestmentUp' ] # Upper bound of the investment decision [p.u.]
|
|
500
500
|
|
|
501
|
+
# replace PeriodFin = 0.0 by year 3000
|
|
502
|
+
pElecGenPeriodFin = pElecGenPeriodFin.where(pElecGenPeriodFin != 0.0, 3000 )
|
|
503
|
+
if pIndHydroTopology == 1:
|
|
504
|
+
pRsrPeriodFin = pRsrPeriodFin.where (pRsrPeriodFin != 0.0, 3000 )
|
|
505
|
+
pElecNetPeriodFin = pElecNetPeriodFin.where(pElecNetPeriodFin != 0.0, 3000 )
|
|
501
506
|
# replace pLineNTCBck = 0.0 by pLineNTCFrw
|
|
502
|
-
pLineNTCBck
|
|
507
|
+
pLineNTCBck = pLineNTCBck.where (pLineNTCBck > 0.0, pLineNTCFrw)
|
|
503
508
|
# replace pLineNTCFrw = 0.0 by pLineNTCBck
|
|
504
|
-
pLineNTCFrw
|
|
509
|
+
pLineNTCFrw = pLineNTCFrw.where (pLineNTCFrw > 0.0, pLineNTCBck)
|
|
505
510
|
# replace pGenUpInvest = 0.0 by 1.0
|
|
506
|
-
pGenUpInvest
|
|
511
|
+
pGenUpInvest = pGenUpInvest.where (pGenUpInvest > 0.0, 1.0 )
|
|
507
512
|
# replace pGenUpRetire = 0.0 by 1.0
|
|
508
|
-
pGenUpRetire
|
|
513
|
+
pGenUpRetire = pGenUpRetire.where (pGenUpRetire > 0.0, 1.0 )
|
|
509
514
|
# replace pNetUpInvest = 0.0 by 1.0
|
|
510
|
-
pNetUpInvest
|
|
515
|
+
pNetUpInvest = pNetUpInvest.where (pNetUpInvest > 0.0, 1.0 )
|
|
511
516
|
|
|
512
517
|
# minimum up- and downtime converted to an integer number of time steps
|
|
513
518
|
pSwitchOnTime = round(pSwitchOnTime /pTimeStep).astype('int')
|
|
@@ -524,6 +529,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
524
529
|
pH2PipeLoInvest = dfNetworkHydrogen['InvestmentLo' ] # Lower bound of the investment decision [p.u.]
|
|
525
530
|
pH2PipeUpInvest = dfNetworkHydrogen['InvestmentUp' ] # Upper bound of the investment decision [p.u.]
|
|
526
531
|
|
|
532
|
+
pH2PipePeriodFin = pH2PipePeriodFin.where(pH2PipePeriodFin != 0.0, 3000 )
|
|
527
533
|
# replace pH2PipeNTCBck = 0.0 by pH2PipeNTCFrw
|
|
528
534
|
pH2PipeNTCBck = pH2PipeNTCBck.where (pH2PipeNTCBck > 0.0, pH2PipeNTCFrw)
|
|
529
535
|
# replace pH2PipeNTCFrw = 0.0 by pH2PipeNTCBck
|
|
@@ -542,6 +548,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
542
548
|
pHeatPipeLoInvest = dfNetworkHeat['InvestmentLo' ] # Lower bound of the investment decision [p.u.]
|
|
543
549
|
pHeatPipeUpInvest = dfNetworkHeat['InvestmentUp' ] # Upper bound of the investment decision [p.u.]
|
|
544
550
|
|
|
551
|
+
pHeatPipePeriodFin = pHeatPipePeriodFin.where(pHeatPipePeriodFin != 0.0, 3000 )
|
|
545
552
|
# replace pHeatPipeNTCBck = 0.0 by pHeatPipeNTCFrw
|
|
546
553
|
pHeatPipeNTCBck = pHeatPipeNTCBck.where (pHeatPipeNTCBck > 0.0, pHeatPipeNTCFrw)
|
|
547
554
|
# replace pHeatPipeNTCFrw = 0.0 by pHeatPipeNTCBck
|
|
@@ -581,6 +588,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
581
588
|
mTEPES.bc = Set(doc='candidate boiler units' , initialize=[bo for bo in mTEPES.bo if pGenInvestCost [bo] > 0.0])
|
|
582
589
|
mTEPES.br = Set(doc='all input electric branches', initialize=sBrList )
|
|
583
590
|
mTEPES.ln = Set(doc='all input electric lines' , initialize=dfNetwork.index)
|
|
591
|
+
if len(mTEPES.ln) != len(dfNetwork.index):
|
|
592
|
+
raise ValueError('### Some electric lines are invalid ', len(mTEPES.ln), len(dfNetwork.index))
|
|
584
593
|
mTEPES.la = Set(doc='all real electric lines' , initialize=[ln for ln in mTEPES.ln if pLineX [ln] != 0.0 and pLineNTCFrw[ln] > 0.0 and pLineNTCBck[ln] > 0.0 and pElecNetPeriodIni[ln] <= mTEPES.p.last() and pElecNetPeriodFin[ln] >= mTEPES.p.first()])
|
|
585
594
|
mTEPES.ls = Set(doc='all real switch electric lines' , initialize=[la for la in mTEPES.la if pIndBinLineSwitch [la] ])
|
|
586
595
|
mTEPES.lc = Set(doc='candidate electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] > 0.0])
|
|
@@ -598,6 +607,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
598
607
|
mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[] )
|
|
599
608
|
if pIndHydrogen == 1:
|
|
600
609
|
mTEPES.pn = Set(doc='all input hydrogen pipes' , initialize=dfNetworkHydrogen.index )
|
|
610
|
+
if len(mTEPES.pn) != len(dfNetworkHydrogen.index):
|
|
611
|
+
raise ValueError('### Some hydrogen pipes are invalid ', len(mTEPES.pn), len(dfNetworkHydrogen.index))
|
|
601
612
|
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()])
|
|
602
613
|
mTEPES.pc = Set(doc='candidate hydrogen pipes' , initialize=[pa for pa in mTEPES.pa if pH2PipeFixedCost [pa] > 0.0])
|
|
603
614
|
# existing hydrogen pipelines (pe)
|
|
@@ -609,6 +620,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
609
620
|
|
|
610
621
|
if pIndHeat == 1:
|
|
611
622
|
mTEPES.hn = Set(doc='all input heat pipes' , initialize=dfNetworkHeat.index)
|
|
623
|
+
if len(mTEPES.hn) != len(dfNetworkHeat.index):
|
|
624
|
+
raise ValueError('### Some heat pipes are invalid ', len(mTEPES.hn), len(dfNetworkHeat.index))
|
|
612
625
|
mTEPES.ha = Set(doc='all real heat pipes' , initialize=[hn for hn in mTEPES.hn if pHeatPipeNTCFrw [hn] > 0.0 and pHeatPipeNTCBck[hn] > 0.0 and pHeatPipePeriodIni[hn] <= mTEPES.p.last() and pHeatPipePeriodFin[hn] >= mTEPES.p.first()])
|
|
613
626
|
mTEPES.hc = Set(doc='candidate heat pipes' , initialize=[ha for ha in mTEPES.ha if pHeatPipeFixedCost [ha] > 0.0])
|
|
614
627
|
# existing heat pipes (he)
|
|
@@ -1063,7 +1076,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1063
1076
|
pDemandElecPeak = pd.Series([0.0 for p,ar in mTEPES.par], index=mTEPES.par)
|
|
1064
1077
|
for p,ar in mTEPES.par:
|
|
1065
1078
|
# values < 1e-5 times the maximum demand for each area (an area is related to operating reserves procurement, i.e., country) are converted to 0
|
|
1066
|
-
pDemandElecPeak[p,ar] = pDemandElec[[nd for nd in d2a[ar]]].sum(axis=1).max()
|
|
1079
|
+
pDemandElecPeak[p,ar] = pDemandElec.loc[p,:,:][[nd for nd in d2a[ar]]].sum(axis=1).max()
|
|
1067
1080
|
pEpsilon = pDemandElecPeak[p,ar]*1e-5
|
|
1068
1081
|
|
|
1069
1082
|
# these parameters are in GW
|
|
@@ -1096,8 +1109,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1096
1109
|
pMaxCapacity = pMaxPowerElec.where(pMaxPowerElec > pMaxCharge, pMaxCharge)
|
|
1097
1110
|
|
|
1098
1111
|
if len(g2a[ar]):
|
|
1099
|
-
pMaxPower2ndBlock [pMaxPower2ndBlock [[
|
|
1100
|
-
pMaxCharge2ndBlock[pMaxCharge2ndBlock[[
|
|
1112
|
+
pMaxPower2ndBlock [pMaxPower2ndBlock [[g for g in g2a[ar]]] < pEpsilon] = 0.0
|
|
1113
|
+
pMaxCharge2ndBlock[pMaxCharge2ndBlock[[g for g in g2a[ar]]] < pEpsilon] = 0.0
|
|
1101
1114
|
|
|
1102
1115
|
pLineNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilon], dtype='float64'))
|
|
1103
1116
|
pLineNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilon], dtype='float64'))
|
|
@@ -1133,16 +1146,18 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1133
1146
|
pEmissionVarCost = pEmissionVarCost.loc [:,mTEPES.g ]
|
|
1134
1147
|
|
|
1135
1148
|
# replace < 0.0 by 0.0
|
|
1136
|
-
|
|
1137
|
-
|
|
1149
|
+
pEpsilon = 1e-6
|
|
1150
|
+
pMaxPower2ndBlock = pMaxPower2ndBlock.where (pMaxPower2ndBlock > pEpsilon, 0.0)
|
|
1151
|
+
pMaxCharge2ndBlock = pMaxCharge2ndBlock.where(pMaxCharge2ndBlock > pEpsilon, 0.0)
|
|
1138
1152
|
|
|
1139
1153
|
# computation of the power to heat ratio of the CHP units
|
|
1140
1154
|
# heat ratio of boiler units is fixed to 1.0
|
|
1141
1155
|
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)
|
|
1142
|
-
pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch]
|
|
1143
|
-
pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch]
|
|
1144
|
-
pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo]
|
|
1145
|
-
pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo]
|
|
1156
|
+
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)
|
|
1157
|
+
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)
|
|
1158
|
+
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))
|
|
1159
|
+
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))
|
|
1160
|
+
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))
|
|
1146
1161
|
|
|
1147
1162
|
# drop values not par, p, or ps
|
|
1148
1163
|
pReserveMargin = pReserveMargin.loc [mTEPES.par]
|
|
@@ -1235,8 +1250,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1235
1250
|
pStartUpCost.update (pd.Series([0.0 for nr in mTEPES.nr if pStartUpCost [nr] < pEpsilon], index=[nr for nr in mTEPES.nr if pStartUpCost [nr] < pEpsilon]))
|
|
1236
1251
|
pShutDownCost.update (pd.Series([0.0 for nr in mTEPES.nr if pShutDownCost [nr] < pEpsilon], index=[nr for nr in mTEPES.nr if pShutDownCost [nr] < pEpsilon]))
|
|
1237
1252
|
|
|
1238
|
-
# this rated linear variable cost
|
|
1239
|
-
# we include a small term to avoid stochastic behavior due to equal values
|
|
1253
|
+
# this rated linear variable cost is going to be used to order the generating units
|
|
1254
|
+
# we include a small term to avoid a stochastic behavior due to equal values
|
|
1240
1255
|
for g in mTEPES.g:
|
|
1241
1256
|
pRatedLinearVarCost[g] += 1e-3*pEpsilon*mTEPES.g.ord(g)
|
|
1242
1257
|
|
|
@@ -1739,11 +1754,11 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1739
1754
|
[OptModel.vIniInventory [p,sc,n,ec].setlb(mTEPES.pMinStorage [p,sc,n,ec] ) for p,sc,n,ec in mTEPES.psnec]
|
|
1740
1755
|
[OptModel.vIniInventory [p,sc,n,ec].setub(mTEPES.pMaxStorage [p,sc,n,ec] ) for p,sc,n,ec in mTEPES.psnec]
|
|
1741
1756
|
|
|
1742
|
-
[OptModel.vESSTotalCharge[p,sc,n,eh].setub(mTEPES.pMaxCharge [p,sc,n,eh]) for p,sc,n,eh in mTEPES.psneh]
|
|
1743
|
-
[OptModel.vCharge2ndBlock[p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh]) for p,sc,n,eh in mTEPES.psneh]
|
|
1744
|
-
[OptModel.vESSReserveUp [p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh]) for p,sc,n,eh in mTEPES.psneh]
|
|
1745
|
-
[OptModel.vESSReserveDown[p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh]) for p,sc,n,eh in mTEPES.psneh]
|
|
1746
|
-
[OptModel.vENS [p,sc,n,nd].setub(mTEPES.pDemandElecAbs [p,sc,n,nd]) for p,sc,n,nd in mTEPES.psnnd]
|
|
1757
|
+
[OptModel.vESSTotalCharge[p,sc,n,eh].setub(mTEPES.pMaxCharge [p,sc,n,eh] ) for p,sc,n,eh in mTEPES.psneh]
|
|
1758
|
+
[OptModel.vCharge2ndBlock[p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] ) for p,sc,n,eh in mTEPES.psneh]
|
|
1759
|
+
[OptModel.vESSReserveUp [p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] ) for p,sc,n,eh in mTEPES.psneh]
|
|
1760
|
+
[OptModel.vESSReserveDown[p,sc,n,eh].setub(mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] ) for p,sc,n,eh in mTEPES.psneh]
|
|
1761
|
+
[OptModel.vENS [p,sc,n,nd].setub(mTEPES.pDemandElecAbs [p,sc,n,nd] ) for p,sc,n,nd in mTEPES.psnnd]
|
|
1747
1762
|
|
|
1748
1763
|
if mTEPES.pIndHydroTopology == 1:
|
|
1749
1764
|
[OptModel.vHydroInflows [p,sc,n,rc].setub(mTEPES.pHydroInflows[p,sc,n,rc]()) for p,sc,n,rc in mTEPES.psnrc]
|
|
@@ -2137,6 +2152,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2137
2152
|
if mTEPES.pDemandHeat[p,sc,n,nd] == 0.0:
|
|
2138
2153
|
OptModel.vHeatNS [p,sc,n,nd].fix (0.0)
|
|
2139
2154
|
nFixedVariables += 1
|
|
2155
|
+
|
|
2140
2156
|
def AvoidForbiddenInstallationsAndRetirements(mTEPES, OptModel) -> int:
|
|
2141
2157
|
'''
|
|
2142
2158
|
Fix installations/retirements forbidden by period.
|
|
@@ -2281,7 +2297,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2281
2297
|
[OptModel.vFlowHeat[p,sc,n,ni,nf,cc].fix(0.0) for p,sc,n,ni,nf,cc in mTEPES.psnha if (ni,nf,cc) not in mTEPES.hc and mTEPES.pHeatPipePeriodIni[ni,nf,cc] > p]
|
|
2282
2298
|
nFixedVariables += sum( 4 for p,sc,n,ni,nf,cc in mTEPES.psnha if (ni,nf,cc) not in mTEPES.hc and mTEPES.pHeatPipePeriodIni[ni,nf,cc] > p)
|
|
2283
2299
|
|
|
2284
|
-
# tolerance to consider 0
|
|
2300
|
+
# tolerance to consider 0 an investment decision
|
|
2285
2301
|
pEpsilon = 1e-4
|
|
2286
2302
|
def SetToZero(mTEPES, OptModel, pEpsilon) -> None:
|
|
2287
2303
|
'''
|
|
@@ -2311,7 +2327,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2311
2327
|
[OptModel.vGenerationInvest[p,eb ].setlb(mTEPES.pGenLoInvest[eb ]()) for p,eb in mTEPES.peb]
|
|
2312
2328
|
[OptModel.vGenerationInvest[p,eb ].setub(mTEPES.pGenUpInvest[eb ]()) for p,eb in mTEPES.peb]
|
|
2313
2329
|
for p,gd in mTEPES.pgd:
|
|
2314
|
-
if mTEPES.pGenLoRetire[ gd ]() <
|
|
2330
|
+
if mTEPES.pGenLoRetire[ gd ]() < pEpsilon:
|
|
2315
2331
|
mTEPES.pGenLoRetire[ gd ] = 0
|
|
2316
2332
|
if mTEPES.pGenUpRetire[ gd ]() < pEpsilon:
|
|
2317
2333
|
mTEPES.pGenUpRetire[ gd ] = 0
|
|
@@ -2405,9 +2421,9 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2405
2421
|
for es in mTEPES.es:
|
|
2406
2422
|
# detecting infeasibility: total min ESS output greater than total inflows, total max ESS charge lower than total outflows
|
|
2407
2423
|
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:
|
|
2408
|
-
raise ValueError('### Total minimum output greater than total inflows for ESS unit ', es)
|
|
2424
|
+
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))
|
|
2409
2425
|
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:
|
|
2410
|
-
raise ValueError('### Total maximum charge lower than total outflows for ESS unit ', es)
|
|
2426
|
+
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))
|
|
2411
2427
|
|
|
2412
2428
|
# detect inventory infeasibility
|
|
2413
2429
|
for p,sc,n,es in mTEPES.ps*mTEPES.nesc:
|
|
@@ -2415,22 +2431,22 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2415
2431
|
if mTEPES.pMaxCapacity[p,sc,n,es]:
|
|
2416
2432
|
if mTEPES.n.ord(n) == mTEPES.pStorageTimeStep[es]:
|
|
2417
2433
|
if mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - mTEPES.pMinPowerElec[p,sc,n2,es] + mTEPES.pEfficiency[es]*mTEPES.pMaxCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) < mTEPES.pMinStorage[p,sc,n,es]:
|
|
2418
|
-
raise ValueError('### Inventory equation violation ', p, sc, n, es)
|
|
2434
|
+
raise ValueError('### Inventory equation violation ', p, sc, n, es, mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - mTEPES.pMinPowerElec[p,sc,n2,es] + mTEPES.pEfficiency[es]*mTEPES.pMaxCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]), mTEPES.pMinStorage[p,sc,n,es])
|
|
2419
2435
|
elif mTEPES.n.ord(n) > mTEPES.pStorageTimeStep[es]:
|
|
2420
2436
|
if mTEPES.pMaxStorage[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - mTEPES.pMinPowerElec[p,sc,n2,es] + mTEPES.pEfficiency[es]*mTEPES.pMaxCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) < mTEPES.pMinStorage[p,sc,n,es]:
|
|
2421
|
-
raise ValueError('### Inventory equation violation ', p, sc, n, es)
|
|
2437
|
+
raise ValueError('### Inventory equation violation ', p, sc, n, es, mTEPES.pMaxStorage[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - mTEPES.pMinPowerElec[p,sc,n2,es] + mTEPES.pEfficiency[es]*mTEPES.pMaxCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]), mTEPES.pMinStorage[p,sc,n,es])
|
|
2422
2438
|
|
|
2423
2439
|
# detect minimum energy infeasibility
|
|
2424
2440
|
for p,sc,n,g in mTEPES.ps*mTEPES.ngen:
|
|
2425
2441
|
if (p,g) in mTEPES.pg:
|
|
2426
2442
|
if (p,sc,g) in mTEPES.gm:
|
|
2427
2443
|
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:
|
|
2428
|
-
raise ValueError('### Minimum energy violation ', p, sc, n, g)
|
|
2444
|
+
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)]))
|
|
2429
2445
|
|
|
2430
2446
|
# detecting reserve margin infeasibility
|
|
2431
2447
|
for p,ar in mTEPES.p*mTEPES.ar:
|
|
2432
2448
|
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]:
|
|
2433
|
-
raise ValueError('### Reserve margin infeasibility ', p, ar)
|
|
2449
|
+
raise ValueError('### 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])
|
|
2434
2450
|
|
|
2435
2451
|
DetectInfeasibilities(mTEPES)
|
|
2436
2452
|
|
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) - November 19, 2024
|
|
664
664
|
# simplicity and transparency in power systems planning
|
|
665
665
|
|
|
666
666
|
# Developed by
|
|
@@ -685,7 +685,7 @@ import time
|
|
|
685
685
|
# import pkg_resources
|
|
686
686
|
from .openTEPES import openTEPES_run
|
|
687
687
|
|
|
688
|
-
print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.
|
|
688
|
+
print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.9 - November 19, 2024\033[0m')
|
|
689
689
|
print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m \n')
|
|
690
690
|
|
|
691
691
|
parser = argparse.ArgumentParser(description='Introducing main parameters...')
|
|
@@ -733,16 +733,18 @@ def main():
|
|
|
733
733
|
import sys
|
|
734
734
|
print(sys.argv)
|
|
735
735
|
print(args)
|
|
736
|
-
openTEPES_run(args.dir, args.case, args.solver, args.result, args.log)
|
|
736
|
+
model = openTEPES_run(args.dir, args.case, args.solver, args.result, args.log)
|
|
737
737
|
# Computing the elapsed time
|
|
738
738
|
ElapsedTime = round(time.time() - StartTime)
|
|
739
739
|
print('Total time ... {} s'.format(ElapsedTime))
|
|
740
|
-
path_to_write_time = os.path.join(args.dir,args.case,'openTEPES_time_
|
|
740
|
+
path_to_write_time = os.path.join(args.dir,args.case,f'openTEPES_time_{args.case}.log')
|
|
741
741
|
with open(path_to_write_time, 'w') as f:
|
|
742
|
-
f
|
|
742
|
+
f'Elapsed time {ElapsedTime} s'
|
|
743
743
|
# Final message
|
|
744
744
|
print('End of the run ************')
|
|
745
745
|
print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m')
|
|
746
746
|
|
|
747
|
+
return model
|
|
748
|
+
|
|
747
749
|
if __name__ == '__main__':
|
|
748
|
-
main()
|
|
750
|
+
model = main()
|