openTEPES 4.18.0__py3-none-any.whl → 4.18.2__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.18.0"
17
+ __version__ = "4.18.2"
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) - December 04, 2024
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 15, 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.18.0 - December 04, 2024')
43
- print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.0 - December 04, 2024', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
42
+ mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024')
43
+ print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024', file=open(f'{_path}/openTEPES_version_{CaseName}.log','w'))
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]
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - December 04, 2024
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - December 10, 2024
3
3
  """
4
4
 
5
5
  import datetime
@@ -68,6 +68,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
68
68
 
69
69
  try:
70
70
  dfDemandHeat = pd.read_csv(f'{_path}/oT_Data_DemandHeat_' f'{CaseName}.csv', header=0, index_col=[0,1,2])
71
+ dfReserveMarginHeat = pd.read_csv(f'{_path}/oT_Data_ReserveMarginHeat_' f'{CaseName}.csv', header=0, index_col=[0,1 ])
71
72
  dfNetworkHeat = pd.read_csv(f'{_path}/oT_Data_NetworkHeat_' f'{CaseName}.csv', header=0, index_col=[0,1,2])
72
73
  pIndHeat = 1
73
74
  except:
@@ -117,6 +118,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
117
118
 
118
119
  if pIndHeat == 1:
119
120
  dfDemandHeat.fillna (0.0, inplace=True)
121
+ dfReserveMarginHeat.fillna(0.0, inplace=True)
120
122
  dfNetworkHeat.fillna (0.0, inplace=True)
121
123
 
122
124
  dfReserveMargin = dfReserveMargin.where (dfReserveMargin > 0.0, 0.0)
@@ -146,7 +148,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
146
148
 
147
149
  # show some statistics of the data
148
150
  if pIndLogConsole == 1:
149
- print('Reserve margin \n', dfReserveMargin.describe (), '\n')
151
+ print('Reserve margin electricity \n', dfReserveMargin.describe (), '\n')
150
152
  print('Maximum CO2 emission \n', dfEmission.describe (), '\n')
151
153
  print('Minimum RES energy \n', dfRESEnergy.describe (), '\n')
152
154
  print('Electricity demand \n', dfDemand.describe (), '\n')
@@ -181,6 +183,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
181
183
 
182
184
  if pIndHeat == 1:
183
185
  print('Heat demand \n', dfDemandHeat.describe (), '\n')
186
+ print('Reserve margin heat \n', dfReserveMarginHeat.describe (), '\n')
184
187
  print('Heat pipe network \n', dfNetworkHeat.describe (), '\n')
185
188
 
186
189
  #%% reading the sets
@@ -329,6 +332,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
329
332
  pDemandH2 = dfDemandHydrogen [mTEPES.nd] # hydrogen demand [tH2/h]
330
333
 
331
334
  if pIndHeat == 1:
335
+ pReserveMarginHeat = dfReserveMarginHeat ['ReserveMargin'] # minimum adequacy reserve margin [p.u.]
332
336
  pDemandHeat = dfDemandHeat [mTEPES.nd] * 1e-3 # heat demand [GW]
333
337
 
334
338
  if pTimeStep > 1:
@@ -563,8 +567,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
563
567
  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()])
564
568
  mTEPES.ls = Set(doc='all real switch electric lines' , initialize=[la for la in mTEPES.la if pIndBinLineSwitch [la] ])
565
569
  mTEPES.lc = Set(doc='candidate electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] > 0.0])
566
- mTEPES.cd = Set(doc=' DC electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] > 0.0 and pLineType[la] == 'DC'])
567
- mTEPES.ed = Set(doc=' DC electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] == 0.0 and pLineType[la] == 'DC'])
570
+ mTEPES.cd = Set(doc='candidate DC electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] > 0.0 and pLineType[la] == 'DC'])
571
+ mTEPES.ed = Set(doc='existing DC electric lines' , initialize=[la for la in mTEPES.la if pNetFixedCost [la] == 0.0 and pLineType[la] == 'DC'])
568
572
  mTEPES.ll = Set(doc='loss electric lines' , initialize=[la for la in mTEPES.la if pLineLossFactor [la] > 0.0 and pIndBinNetLosses > 0 ])
569
573
  mTEPES.rf = Set(doc='reference node' , initialize=[pReferenceNode])
570
574
  mTEPES.gq = Set(doc='gen reactive units' , initialize=[gg for gg in mTEPES.gg if pRMaxReactivePower [gg] > 0.0 and pElecGenPeriodIni[gg] <= mTEPES.p.last() and pElecGenPeriodFin[gg] >= mTEPES.p.first()])
@@ -1048,30 +1052,32 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1048
1052
 
1049
1053
  # small values are converted to 0
1050
1054
  pDemandElecPeak = pd.Series([0.0 for p,ar in mTEPES.par], index=mTEPES.par)
1055
+ pDemandHeatPeak = pd.Series([0.0 for p,ar in mTEPES.par], index=mTEPES.par)
1051
1056
  for p,ar in mTEPES.par:
1052
1057
  # 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
1053
1058
  pDemandElecPeak[p,ar] = pDemandElec.loc[p,:,:][[nd for nd in d2a[ar]]].sum(axis=1).max()
1054
- pEpsilon = pDemandElecPeak[p,ar]*1e-5
1059
+ pEpsilonElec = pDemandElecPeak[p,ar]*1e-5
1055
1060
 
1056
1061
  # these parameters are in GW
1057
- pDemandElecPos [pDemandElecPos [[nd for nd in d2a[ar]]] < pEpsilon] = 0.0
1058
- pDemandElecNeg [pDemandElecNeg [[nd for nd in d2a[ar]]] > -pEpsilon] = 0.0
1059
- pSystemInertia [pSystemInertia [[ ar ]] < pEpsilon] = 0.0
1060
- pOperReserveUp [pOperReserveUp [[ ar ]] < pEpsilon] = 0.0
1061
- pOperReserveDw [pOperReserveDw [[ ar ]] < pEpsilon] = 0.0
1062
+ pDemandElecPos [pDemandElecPos [[nd for nd in d2a[ar]]] < pEpsilonElec] = 0.0
1063
+ pDemandElecNeg [pDemandElecNeg [[nd for nd in d2a[ar]]] > -pEpsilonElec] = 0.0
1064
+ pSystemInertia [pSystemInertia [[ ar ]] < pEpsilonElec] = 0.0
1065
+ pOperReserveUp [pOperReserveUp [[ ar ]] < pEpsilonElec] = 0.0
1066
+ pOperReserveDw [pOperReserveDw [[ ar ]] < pEpsilonElec] = 0.0
1067
+
1062
1068
  if len(g2a[ar]):
1063
- pMinPowerElec [pMinPowerElec [[g for g in g2a[ar]]] < pEpsilon] = 0.0
1064
- pMaxPowerElec [pMaxPowerElec [[g for g in g2a[ar]]] < pEpsilon] = 0.0
1065
- pMinCharge [pMinCharge [[es for es in e2a[ar]]] < pEpsilon] = 0.0
1066
- pMaxCharge [pMaxCharge [[eh for eh in g2a[ar]]] < pEpsilon] = 0.0
1067
- pEnergyInflows [pEnergyInflows [[es for es in e2a[ar]]] < pEpsilon] = 0.0
1068
- pEnergyOutflows[pEnergyOutflows[[es for es in e2a[ar]]] < pEpsilon] = 0.0
1069
+ pMinPowerElec [pMinPowerElec [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1070
+ pMaxPowerElec [pMaxPowerElec [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1071
+ pMinCharge [pMinCharge [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1072
+ pMaxCharge [pMaxCharge [[eh for eh in g2a[ar]]] < pEpsilonElec] = 0.0
1073
+ pEnergyInflows [pEnergyInflows [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1074
+ pEnergyOutflows[pEnergyOutflows[[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1069
1075
  # these parameters are in GWh
1070
- pMinStorage [pMinStorage [[es for es in e2a[ar]]] < pEpsilon] = 0.0
1071
- pMaxStorage [pMaxStorage [[es for es in e2a[ar]]] < pEpsilon] = 0.0
1072
- pIniInventory [pIniInventory [[es for es in e2a[ar]]] < pEpsilon] = 0.0
1076
+ pMinStorage [pMinStorage [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1077
+ pMaxStorage [pMaxStorage [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1078
+ pIniInventory [pIniInventory [[es for es in e2a[ar]]] < pEpsilonElec] = 0.0
1073
1079
 
1074
- # pInitialInventory.update(pd.Series([0.0 for es in e2a[ar] if pInitialInventory[es] < pEpsilon], index=[es for es in e2a[ar] if pInitialInventory[es] < pEpsilon], dtype='float64'))
1080
+ # pInitialInventory.update(pd.Series([0.0 for es in e2a[ar] if pInitialInventory[es] < pEpsilonElec], index=[es for es in e2a[ar] if pInitialInventory[es] < pEpsilonElec], dtype='float64'))
1075
1081
 
1076
1082
  # merging positive and negative values of the demand
1077
1083
  pDemandElec = pDemandElecPos.where(pDemandElecNeg >= 0.0, pDemandElecNeg)
@@ -1080,9 +1086,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1080
1086
  # pMaxPowerElec = pMaxPowerElec.where(pMaxPowerElec >= pMinPowerElec, pMinPowerElec)
1081
1087
  # pMaxCharge = pMaxCharge.where (pMaxCharge >= pMinCharge, pMinCharge )
1082
1088
 
1083
- #Decrease Minimum to reach maximum
1089
+ # Decrease Minimum to reach maximum
1084
1090
  pMinPowerElec = pMinPowerElec.where(pMinPowerElec <= pMaxPowerElec, pMaxPowerElec)
1085
- pMinCharge = pMinCharge.where (pMinCharge <= pMaxCharge, pMaxCharge)
1091
+ pMinCharge = pMinCharge.where (pMinCharge <= pMaxCharge, pMaxCharge )
1086
1092
 
1087
1093
  #Calculate 2nd Blocks
1088
1094
  pMaxPower2ndBlock = pMaxPowerElec - pMinPowerElec
@@ -1091,22 +1097,24 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1091
1097
  pMaxCapacity = pMaxPowerElec.where(pMaxPowerElec > pMaxCharge, pMaxCharge)
1092
1098
 
1093
1099
  if len(g2a[ar]):
1094
- pMaxPower2ndBlock [pMaxPower2ndBlock [[g for g in g2a[ar]]] < pEpsilon] = 0.0
1095
- pMaxCharge2ndBlock[pMaxCharge2ndBlock[[g for g in g2a[ar]]] < pEpsilon] = 0.0
1100
+ pMaxPower2ndBlock [pMaxPower2ndBlock [[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1101
+ pMaxCharge2ndBlock[pMaxCharge2ndBlock[[g for g in g2a[ar]]] < pEpsilonElec] = 0.0
1096
1102
 
1097
- 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'))
1098
- 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'))
1103
+ pLineNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCFrw[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1104
+ pLineNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.la if pLineNTCBck[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1099
1105
  pLineNTCMax = pLineNTCFrw.where(pLineNTCFrw > pLineNTCBck, pLineNTCBck)
1100
1106
 
1101
1107
  if pIndHydrogen == 1:
1102
- pDemandH2[pDemandH2[[nd for nd in d2a[ar]]] < pEpsilon] = 0.0
1103
- pH2PipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilon], dtype='float64'))
1104
- pH2PipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilon], dtype='float64'))
1108
+ pDemandH2[pDemandH2[[nd for nd in d2a[ar]]] < pEpsilonElec] = 0.0
1109
+ pH2PipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCFrw[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1110
+ pH2PipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilonElec], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.pa if pH2PipeNTCBck[ni,nf,cc] < pEpsilonElec], dtype='float64'))
1105
1111
 
1106
1112
  if pIndHeat == 1:
1107
- pDemandHeat[pDemandHeat[[nd for nd in d2a[ar]]] < pEpsilon] = 0.0
1108
- pHeatPipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilon], dtype='float64'))
1109
- pHeatPipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilon], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilon], dtype='float64'))
1113
+ pDemandHeatPeak[p,ar] = pDemandHeat.loc[p,:,:][[nd for nd in d2a[ar]]].sum(axis=1).max()
1114
+ pEpsilonHeat = pDemandHeatPeak[p,ar]*1e-5
1115
+ pDemandHeat [pDemandHeat [[nd for nd in d2a[ar]]] < pEpsilonHeat] = 0.0
1116
+ pHeatPipeNTCFrw.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilonHeat], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCFrw[ni,nf,cc] < pEpsilonHeat], dtype='float64'))
1117
+ pHeatPipeNTCBck.update(pd.Series([0.0 for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilonHeat], index=[(ni,nf,cc) for ni,nf,cc in mTEPES.ha if pHeatPipeNTCBck[ni,nf,cc] < pEpsilonHeat], dtype='float64'))
1110
1118
 
1111
1119
  # drop generators not g or es or eh or ch
1112
1120
  pMinPowerElec = pMinPowerElec.loc [:,mTEPES.g ]
@@ -1135,17 +1143,18 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1135
1143
  # computation of the power to heat ratio of the CHP units
1136
1144
  # heat ratio of boiler units is fixed to 1.0
1137
1145
  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)
1138
- 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)
1139
- 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)
1140
- 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))
1141
- 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))
1142
- 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
+ pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.ch)
1147
+ pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.ch)
1148
+ pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.bo))
1149
+ pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.bo))
1150
+ pMaxPowerHeat.update(pd.DataFrame([[pMaxCharge[hp][p,sc,n]/pProductionFunctionHeat[hp] for hp in mTEPES.hp] for p,sc,n in mTEPES.psn], index=pd.MultiIndex.from_tuples(mTEPES.psn), columns=mTEPES.hp))
1143
1151
 
1144
1152
  # drop values not par, p, or ps
1145
1153
  pReserveMargin = pReserveMargin.loc [mTEPES.par]
1146
1154
  pEmission = pEmission.loc [mTEPES.par]
1147
1155
  pRESEnergy = pRESEnergy.loc [mTEPES.par]
1148
1156
  pDemandElecPeak = pDemandElecPeak.loc[mTEPES.par]
1157
+ pDemandHeatPeak = pDemandHeatPeak.loc[mTEPES.par]
1149
1158
  pPeriodWeight = pPeriodWeight.loc [mTEPES.p ]
1150
1159
  pScenProb = pScenProb.loc [mTEPES.ps ]
1151
1160
 
@@ -1208,6 +1217,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1208
1217
  pH2PipeUpInvest = pH2PipeUpInvest.loc [mTEPES.pc]
1209
1218
 
1210
1219
  if pIndHeat == 1:
1220
+ pReserveMarginHeat = pReserveMarginHeat.loc [mTEPES.par]
1211
1221
  # drop generators not hp
1212
1222
  pProductionFunctionHeat = pProductionFunctionHeat.loc [mTEPES.hp]
1213
1223
  # drop generators not hh
@@ -1303,7 +1313,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1303
1313
  mTEPES.pIndBinGenOperat = Param(initialize=pIndBinGenOperat , within=Binary, doc='Indicator of binary generation operation decisions', mutable=True)
1304
1314
  mTEPES.pIndBinSingleNode = Param(initialize=pIndBinSingleNode , within=Binary, doc='Indicator of single node within a electric network case', mutable=True)
1305
1315
  mTEPES.pIndBinGenRamps = Param(initialize=pIndBinGenRamps , within=Binary, doc='Indicator of using or not the ramp constraints', mutable=True)
1306
- mTEPES.pIndBinGenMinTime = Param(initialize=pIndBinGenMinTime , within=Binary, doc='Indicator of using or not the min time constraints', mutable=True)
1316
+ mTEPES.pIndBinGenMinTime = Param(initialize=pIndBinGenMinTime , within=Binary, doc='Indicator of using or not the min up/dw time constraints', mutable=True)
1307
1317
  mTEPES.pIndBinLineCommit = Param(initialize=pIndBinLineCommit , within=Binary, doc='Indicator of binary electric network switching decisions', mutable=True)
1308
1318
  mTEPES.pIndBinNetLosses = Param(initialize=pIndBinNetLosses , within=Binary, doc='Indicator of binary electric network ohmic losses', mutable=True)
1309
1319
  mTEPES.pIndHydroTopology = Param(initialize=pIndHydroTopology , within=Binary, doc='Indicator of reservoir and hydropower topology' )
@@ -1402,6 +1412,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1402
1412
  pMinPowerHeat = filter_rows(pMinPowerHeat, mTEPES.psnch)
1403
1413
  pMaxPowerHeat = filter_rows(pMaxPowerHeat, mTEPES.psnch)
1404
1414
 
1415
+ mTEPES.pReserveMarginHeat = Param(mTEPES.par, initialize=pReserveMarginHeat.to_dict() , within=NonNegativeReals, doc='Adequacy reserve margin' )
1405
1416
  mTEPES.pRatedMaxPowerHeat = Param(mTEPES.gg, initialize=pRatedMaxPowerHeat.to_dict() , within=NonNegativeReals, doc='Rated maximum heat' )
1406
1417
  mTEPES.pMinPowerHeat = Param(mTEPES.psnch, initialize=pMinPowerHeat.to_dict() , within=NonNegativeReals, doc='Minimum heat power' )
1407
1418
  mTEPES.pMaxPowerHeat = Param(mTEPES.psnch, initialize=pMaxPowerHeat.to_dict() , within=NonNegativeReals, doc='Maximum heat power' )
@@ -1442,17 +1453,18 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
1442
1453
  mTEPES.pDemandH2Abs = Param(mTEPES.psnnd, initialize=pDemandH2Abs.to_dict(), within=NonNegativeReals, doc='Hydrogen demand' )
1443
1454
 
1444
1455
  if pIndHeat == 1:
1445
- pDemandHeat = filter_rows(pDemandHeat , mTEPES.psnnd)
1446
- pDemandHeatAbs = filter_rows(pDemandHeatAbs, mTEPES.psnnd)
1456
+ pDemandHeat = filter_rows(pDemandHeat , mTEPES.psnnd)
1457
+ pDemandHeatAbs = filter_rows(pDemandHeatAbs , mTEPES.psnnd)
1447
1458
 
1448
- mTEPES.pDemandHeat = Param(mTEPES.psnnd, initialize=pDemandHeat.to_dict() , within=NonNegativeReals, doc='Heat demand per hour' )
1449
- mTEPES.pDemandHeatAbs = Param(mTEPES.psnnd, initialize=pDemandHeatAbs.to_dict(), within=NonNegativeReals, doc='Heat demand' )
1459
+ mTEPES.pDemandHeatPeak = Param(mTEPES.par, initialize=pDemandHeatPeak.to_dict(), within=NonNegativeReals, doc='Peak heat demand' )
1460
+ mTEPES.pDemandHeat = Param(mTEPES.psnnd, initialize=pDemandHeat.to_dict() , within=NonNegativeReals, doc='Heat demand per hour' )
1461
+ mTEPES.pDemandHeatAbs = Param(mTEPES.psnnd, initialize=pDemandHeatAbs.to_dict(), within=NonNegativeReals, doc='Heat demand' )
1450
1462
 
1451
- mTEPES.pLoadLevelDuration = Param(mTEPES.psn, initialize=0 , within=NonNegativeIntegers, doc='Load level duration', mutable=True)
1463
+ mTEPES.pLoadLevelDuration = Param(mTEPES.psn, initialize=0 , within=NonNegativeIntegers, doc='Load level duration', mutable=True)
1452
1464
  for p,sc,n in mTEPES.psn:
1453
1465
  mTEPES.pLoadLevelDuration[p,sc,n] = mTEPES.pLoadLevelWeight[p,sc,n]() * mTEPES.pDuration[p,sc,n]()
1454
1466
 
1455
- mTEPES.pPeriodProb = Param(mTEPES.ps, initialize=0.0 , within=NonNegativeReals, doc='Period probability', mutable=True)
1467
+ mTEPES.pPeriodProb = Param(mTEPES.ps, initialize=0.0 , within=NonNegativeReals, doc='Period probability', mutable=True)
1456
1468
  for p,sc in mTEPES.ps:
1457
1469
  # periods and scenarios are going to be solved together with their weight and probability
1458
1470
  mTEPES.pPeriodProb[p,sc] = mTEPES.pPeriodWeight[p] * mTEPES.pScenProb[p,sc]
@@ -2427,8 +2439,12 @@ def SettingUpVariables(OptModel, mTEPES):
2427
2439
 
2428
2440
  # detecting reserve margin infeasibility
2429
2441
  for p,ar in mTEPES.p*mTEPES.ar:
2430
- 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]:
2431
- 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])
2442
+ 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]:
2443
+ raise ValueError('### Electricity reserve margin infeasibility ', p, ar, sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g), mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2444
+
2445
+ if mTEPES.pIndHeat == 1:
2446
+ if sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g) < mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMarginHeat[p,ar]:
2447
+ raise ValueError('### Heat reserve margin infeasibility ', p, ar, sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (p,g) in mTEPES.pg and (ar,g) in mTEPES.a2g), mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
2432
2448
 
2433
2449
  DetectInfeasibilities(mTEPES)
2434
2450
 
@@ -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) - December 04, 2024
663
+ # Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - December 23, 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.18.0 - December 04, 2024\033[0m')
688
+ print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.2 - January 15, 2024\033[0m')
689
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...')
@@ -737,9 +737,9 @@ def main():
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,f'openTEPES_time_{args.case}.log')
740
+ path_to_write_time = os.path.join(args.dir,args.case,f'openTEPES_time_{CASE}.log')
741
741
  with open(path_to_write_time, 'w') as f:
742
- f'Elapsed time {ElapsedTime} s'
742
+ f.write('Elapsed time '+str(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')
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - November 08, 2024
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 07, 2025
3
3
  """
4
4
 
5
5
  import time
@@ -291,17 +291,32 @@ def GenerationOperationModelFormulationInvestment(OptModel, mTEPES, pIndLogConso
291
291
  if pIndLogConsole == 1:
292
292
  print('eUninstallGenCap ... ', len(getattr(OptModel, f'eUninstallGenCap_{p}_{sc}_{st}')), ' rows')
293
293
 
294
- def eAdequacyReserveMargin(OptModel,ar):
294
+ def eAdequacyReserveMarginElec(OptModel,ar):
295
295
  if mTEPES.pReserveMargin[p,ar] and st == mTEPES.Last_st and sum(1 for gc in mTEPES.gc if (ar,gc) in mTEPES.a2g) and sum(mTEPES.pRatedMaxPowerElec[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (ar,g) in mTEPES.a2g and g not in (mTEPES.gc or mTEPES.gd)) <= mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar]:
296
296
  return ((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 and g not in (mTEPES.gc or mTEPES.gd)) +
297
297
  sum( OptModel.vGenerationInvest[p,gc] * mTEPES.pRatedMaxPowerElec[gc] * mTEPES.pAvailability[gc]() / (1.0-mTEPES.pEFOR[gc]) for gc in mTEPES.gc if (p,gc) in mTEPES.pgc and (ar,gc) in mTEPES.a2g ) +
298
298
  sum((1-OptModel.vGenerationRetire[p,gd]) * mTEPES.pRatedMaxPowerElec[gd] * mTEPES.pAvailability[gd]() / (1.0-mTEPES.pEFOR[gd]) for gd in mTEPES.gd if (p,gd) in mTEPES.pgd and (ar,gd) in mTEPES.a2g ) ) >= mTEPES.pDemandElecPeak[p,ar] * mTEPES.pReserveMargin[p,ar])
299
299
  else:
300
300
  return Constraint.Skip
301
- setattr(OptModel, f'eAdequacyReserveMargin_{p}_{sc}_{st}', Constraint(mTEPES.ar, rule=eAdequacyReserveMargin, doc='system adequacy reserve margin [p.u.]'))
301
+ setattr(OptModel, f'eAdequacyReserveMarginElec_{p}_{sc}_{st}', Constraint(mTEPES.ar, rule=eAdequacyReserveMarginElec, doc='electricity system adequacy reserve margin [p.u.]'))
302
302
 
303
303
  if pIndLogConsole == 1:
304
- print('eAdequacyReserveMargin... ', len(getattr(OptModel, f'eAdequacyReserveMargin_{p}_{sc}_{st}')), ' rows')
304
+ print('eAdeqReserveMarginElec... ', len(getattr(OptModel, f'eAdequacyReserveMarginElec_{p}_{sc}_{st}')), ' rows')
305
+
306
+ def eAdequacyReserveMarginHeat(OptModel,ar):
307
+ if mTEPES.pIndHeat == 1:
308
+ if mTEPES.pReserveMarginHeat[p,ar] and st == mTEPES.Last_st and sum(1 for gc in mTEPES.gc if (ar,gc) in mTEPES.a2g) and sum(mTEPES.pRatedMaxPowerHeat[g] * mTEPES.pAvailability[g]() / (1.0-mTEPES.pEFOR[g]) for g in mTEPES.g if (ar,g) in mTEPES.a2g and g not in (mTEPES.gc or mTEPES.gd)) <= mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMarginHeat[p,ar]:
309
+ return ((sum( mTEPES.pRatedMaxPowerHeat[g ] * mTEPES.pAvailability[g ]() / (1.0-mTEPES.pEFOR[g ]) for g in mTEPES.g if (p,g ) in mTEPES.pg and (ar,g ) in mTEPES.a2g and g not in (mTEPES.gc or mTEPES.gd)) +
310
+ sum( OptModel.vGenerationInvest[p,gc] * mTEPES.pRatedMaxPowerHeat[gc] * mTEPES.pAvailability[gc]() / (1.0-mTEPES.pEFOR[gc]) for gc in mTEPES.gc if (p,gc) in mTEPES.pgc and (ar,gc) in mTEPES.a2g ) +
311
+ sum((1-OptModel.vGenerationRetire[p,gd]) * mTEPES.pRatedMaxPowerHeat[gd] * mTEPES.pAvailability[gd]() / (1.0-mTEPES.pEFOR[gd]) for gd in mTEPES.gd if (p,gd) in mTEPES.pgd and (ar,gd) in mTEPES.a2g ) ) >= mTEPES.pDemandHeatPeak[p,ar] * mTEPES.pReserveMarginHeat[p,ar])
312
+ else:
313
+ return Constraint.Skip
314
+ else:
315
+ return Constraint.Skip
316
+ setattr(OptModel, f'eAdequacyReserveMarginHeat_{p}_{sc}_{st}', Constraint(mTEPES.ar, rule=eAdequacyReserveMarginHeat, doc='heat system adequacy reserve margin [p.u.]'))
317
+
318
+ if pIndLogConsole == 1:
319
+ print('eAdeqReserveMarginHeat... ', len(getattr(OptModel, f'eAdequacyReserveMarginHeat_{p}_{sc}_{st}')), ' rows')
305
320
 
306
321
  def eMaxSystemEmission(OptModel,ar):
307
322
  if mTEPES.pEmission[p,ar] < math.inf and st == mTEPES.Last_st and sum(mTEPES.pEmissionVarCost[p,sc,na,nr] for na,nr in mTEPES.na*mTEPES.nr if (ar,nr) in mTEPES.a2g):
@@ -1064,7 +1079,7 @@ def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogCons
1064
1079
  else:
1065
1080
  pEpsilon = 1e-4
1066
1081
 
1067
- def eRampUpState(OptModel, n, nr):
1082
+ def eRampUpState(OptModel,n,nr):
1068
1083
  if mTEPES.pStableTime[nr] and mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and (p,nr) in mTEPES.pnr and mTEPES.pDuration[p,sc,n]():
1069
1084
  if pIndStableTimeDeadBand:
1070
1085
  if mTEPES.pRampUp[nr]:
@@ -1095,7 +1110,7 @@ def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogCons
1095
1110
  if pIndLogConsole == 1:
1096
1111
  print('eRampUpState ... ', len(getattr(OptModel, f'eRampUpState_{p}_{sc}_{st}')), ' rows')
1097
1112
 
1098
- def eRampDwState(OptModel, n, nr):
1113
+ def eRampDwState(OptModel,n,nr):
1099
1114
  if mTEPES.pStableTime[nr] and mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and (p,nr) in mTEPES.pnr and mTEPES.pDuration[p,sc,n]():
1100
1115
  if pIndStableTimeDeadBand:
1101
1116
  if mTEPES.pRampDw[nr]:
@@ -1153,7 +1168,7 @@ def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogCons
1153
1168
  print('eMinDownTime ... ', len(getattr(OptModel, f'eMinDownTime_{p}_{sc}_{st}')), ' rows')
1154
1169
 
1155
1170
  if pIndSimplexFormulation:
1156
- def eMinStableTime(OptModel, n, nr):
1171
+ def eMinStableTime(OptModel,n,nr):
1157
1172
  if (mTEPES.pStableTime[nr] and mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.n.ord(n) >= mTEPES.pStableTime[nr] + 2):
1158
1173
  return OptModel.vRampUpState[p,sc,n,nr] + sum(OptModel.vRampDwState[p,sc,n2,nr] for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStableTime[nr]-1:mTEPES.n.ord(n)-1]) <= 1
1159
1174
  else:
@@ -1166,9 +1181,9 @@ def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogCons
1166
1181
  for nr in mTEPES.nr:
1167
1182
  if (mTEPES.pStableTime[nr] and mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.n.ord(n) >= mTEPES.pStableTime[nr] + 2):
1168
1183
  for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStableTime[nr]-1:mTEPES.n.ord(n)-1]:
1169
- MinStableTimeLoadLevels.append((n, n2, nr))
1184
+ MinStableTimeLoadLevels.append((n,n2,nr))
1170
1185
 
1171
- def eMinStableTime(OptModel, n, n2, nr):
1186
+ def eMinStableTime(OptModel,n,n2,nr):
1172
1187
  return OptModel.vRampUpState[p,sc,n,nr] + OptModel.vRampDwState[p,sc,n2,nr] <= 1
1173
1188
  setattr(OptModel, f'eMinStableTime_{p}_{sc}_{st}', Constraint(MinStableTimeLoadLevels, rule=eMinStableTime, doc='minimum stable time [p.u.]'))
1174
1189
 
@@ -1493,30 +1508,30 @@ def NetworkHeatOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc
1493
1508
  lout[ni].append((nf,cc))
1494
1509
 
1495
1510
  # nodes to CHPs (c2n)
1496
- c2n = defaultdict(list)
1497
- for nd,ch in mTEPES.nd*mTEPES.ch:
1498
- if (nd,ch) in mTEPES.n2g:
1499
- c2n[nd].append(ch)
1511
+ chp2n = defaultdict(list)
1512
+ for nd,chp in mTEPES.nd*mTEPES.chp:
1513
+ if (nd,chp) in mTEPES.n2g:
1514
+ chp2n[nd].append(chp)
1500
1515
 
1501
- # nodes to CHPs (b2n)
1502
- b2n = defaultdict(list)
1503
- for nd,bo in mTEPES.nd*mTEPES.bo:
1504
- if (nd,bo) in mTEPES.n2g:
1505
- b2n[nd].append(bo)
1516
+ def eEnergy2Heat(OptModel,n,chp):
1517
+ if (p,chp) in mTEPES.pchp and chp in mTEPES.ch and chp not in mTEPES.bo:
1518
+ return OptModel.vTotalOutputHeat[p,sc,n,chp] == OptModel.vTotalOutput [p,sc,n,chp] / mTEPES.pPower2HeatRatio [chp]
1519
+ elif (p,chp) in mTEPES.pchp and chp in mTEPES.hp:
1520
+ return OptModel.vTotalOutputHeat[p,sc,n,chp] == OptModel.vESSTotalCharge[p,sc,n,chp] / mTEPES.pProductionFunctionHeat[chp]
1521
+ else:
1522
+ return Constraint.Skip
1523
+ setattr(OptModel, f'eEnergy2Heat_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.chp, rule=eEnergy2Heat, doc='Energy to heat conversion [GW]'))
1506
1524
 
1507
- # nodes to heat pumps (h2n)
1508
- h2n = defaultdict(list)
1509
- for nd,hp in mTEPES.nd*mTEPES.hp:
1510
- if (nd,hp) in mTEPES.n2g:
1511
- h2n[nd].append(hp)
1525
+ if pIndLogConsole == 1:
1526
+ print('eEnergy2Heat ... ', len(getattr(OptModel, f'eEnergy2Heat_{p}_{sc}_{st}')), ' rows')
1512
1527
 
1513
1528
  def eBalanceHeat(OptModel,n,nd):
1514
- if sum(1 for ch in c2n[nd]) + sum(1 for hp in h2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd]):
1515
- return (sum(OptModel.vTotalOutput[p,sc,n,ch]/mTEPES.pPower2HeatRatio[ch] for ch in c2n[nd] if (p,ch) in mTEPES.pch and ch not in mTEPES.bo) + sum(OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in b2n[nd] if (p,bo) in mTEPES.pbo) + sum(OptModel.vESSTotalCharge[p,sc,n,hp]/mTEPES.pProductionFunctionHeat[hp] for hp in h2n[nd]) + OptModel.vHeatNS[p,sc,n,nd] -
1529
+ if sum(1 for ch in chp2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd]):
1530
+ return (sum(OptModel.vTotalOutputHeat[p,sc,n,chp] for chp in chp2n[nd] if (p,chp) in mTEPES.pchp) + OptModel.vHeatNS[p,sc,n,nd] -
1516
1531
  sum(OptModel.vFlowHeat[p,sc,n,nd,nf,cc] for nf,cc in lout[nd] if (p,nd,nf,cc) in mTEPES.pha) + sum(OptModel.vFlowHeat[p,sc,n,ni,nd,cc] for ni,cc in lin[nd] if (p,ni,nd,cc) in mTEPES.pha)) == mTEPES.pDemandHeat[p,sc,n,nd]
1517
1532
  else:
1518
1533
  return Constraint.Skip
1519
- setattr(OptModel, f'eBalanceHeat_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.nd, rule=eBalanceHeat, doc='Heat load generation balance [Tcal/h]'))
1534
+ setattr(OptModel, f'eBalanceHeat_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.nd, rule=eBalanceHeat, doc='Heat load generation balance [GW]'))
1520
1535
 
1521
1536
  if pIndLogConsole == 1:
1522
1537
  print('eBalanceHeat ... ', len(getattr(OptModel, f'eBalanceHeat_{p}_{sc}_{st}')), ' rows')