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 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.7"
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) - September 20, 2024
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.7 - September 20, 2024')
43
- print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.17.7 - September 20, 2024', file=open(_path+'/openTEPES_version_'+CaseName+'.log','w'))
42
+ mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.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 '+str(p)+', Scenario '+str(sc)+', Stage '+str(st))
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_'+CaseName+'_'+str(p)+'_'+str(sc)+'_'+str(st)+'.lp', io_options={'symbolic_solver_labels': True})
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_'+CaseName+'_'+str(p)+'_'+str(sc)+'_'+str(st)+'.lp', io_options={'symbolic_solver_labels': True})
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_'+CaseName+'.pkl','wb') as f:
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 = 0
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 = 0
275
- pIndCostSummaryResults = 0
274
+ pIndOperationSummaryResults = 1
275
+ pIndCostSummaryResults = 1
276
276
  pIndMarginalResults = 0
277
277
  pIndEconomicResults = 0
278
278
 
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - September 20, 2024
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 = pLineNTCBck.where (pLineNTCBck > 0.0, pLineNTCFrw)
507
+ pLineNTCBck = pLineNTCBck.where (pLineNTCBck > 0.0, pLineNTCFrw)
503
508
  # replace pLineNTCFrw = 0.0 by pLineNTCBck
504
- pLineNTCFrw = pLineNTCFrw.where (pLineNTCFrw > 0.0, pLineNTCBck)
509
+ pLineNTCFrw = pLineNTCFrw.where (pLineNTCFrw > 0.0, pLineNTCBck)
505
510
  # replace pGenUpInvest = 0.0 by 1.0
506
- pGenUpInvest = pGenUpInvest.where(pGenUpInvest > 0.0, 1.0 )
511
+ pGenUpInvest = pGenUpInvest.where (pGenUpInvest > 0.0, 1.0 )
507
512
  # replace pGenUpRetire = 0.0 by 1.0
508
- pGenUpRetire = pGenUpRetire.where(pGenUpRetire > 0.0, 1.0 )
513
+ pGenUpRetire = pGenUpRetire.where (pGenUpRetire > 0.0, 1.0 )
509
514
  # replace pNetUpInvest = 0.0 by 1.0
510
- pNetUpInvest = pNetUpInvest.where(pNetUpInvest > 0.0, 1.0 )
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 [[nr for nr in n2a[ar]]] < pEpsilon] = 0.0
1100
- pMaxCharge2ndBlock[pMaxCharge2ndBlock[[eh for eh in g2a[ar]]] < pEpsilon] = 0.0
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
- pMaxPower2ndBlock = pMaxPower2ndBlock.where (pMaxPower2ndBlock > 0.0, 0.0)
1137
- pMaxCharge2ndBlock = pMaxCharge2ndBlock.where(pMaxCharge2ndBlock > 0.0, 0.0)
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] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.ch)
1143
- 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)
1144
- 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))
1145
- 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))
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 y going to be used to order the generating units
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 a number
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 ]() < pEpsilon:
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
 
@@ -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) - September 20, 2024
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.7 - September 20, 2024\033[0m')
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_'+args.case+'.log')
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.write('Elapsed time '+str(ElapsedTime)+' s')
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()