openTEPES 4.18.7__py3-none-any.whl → 4.18.8__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.
@@ -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) - November 22, 2025
663
+ # Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 19, 2026
664
664
  # simplicity and transparency in power systems planning
665
665
 
666
666
  # Developed by
@@ -681,13 +681,20 @@
681
681
 
682
682
  #%% libraries
683
683
  import argparse
684
+ import platform
685
+ import psutil
684
686
  import os
685
687
  import time
686
688
  # import pkg_resources
687
689
  from .openTEPES import openTEPES_run
688
690
 
689
- print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.7 - November 22, 2025\033[0m')
690
- print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m \n')
691
+
692
+ GREEN = "\033[32m"
693
+ BLUE = "\033[34m"
694
+ RESET = "\033[0m"
695
+
696
+ print(GREEN + 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.8 - January 19, 2026' + RESET)
697
+ print(BLUE + '#### Academic research license - for non-commercial use only ####' + RESET + '\n')
691
698
 
692
699
  parser = argparse.ArgumentParser(description='Introducing main parameters...')
693
700
  parser.add_argument('--case', type=str, default=None)
@@ -698,7 +705,7 @@ parser.add_argument('--result', type=str, default=None)
698
705
 
699
706
  DIR = os.path.dirname(__file__)
700
707
  CASE = '9n'
701
- SOLVER = 'glpk'
708
+ SOLVER = 'highs' # 'gams', 'appsi_highs', 'gurobi'
702
709
  RESULT = 'Yes'
703
710
  LOG = 'No'
704
711
 
@@ -746,12 +753,15 @@ def main():
746
753
  print('Total time ... {} s'.format(ElapsedTime))
747
754
  path_to_write_time = os.path.join(args.dir,args.case,f'openTEPES_time_{CASE}.log')
748
755
  with open(path_to_write_time, 'w') as f:
749
- f.write('Elapsed time '+str(ElapsedTime)+' s')
756
+ f.write(('Elapsed time ' + str(ElapsedTime) + ' s on ' + platform.node() + ' [' + platform.system() + ' ' +
757
+ platform.release() + '; ' + str(psutil.cpu_count(logical=False)) + ' ' + platform.processor() +
758
+ ' physical cores at ' + str(round(psutil.cpu_freq().current/1000,2)) + ' GHz; ' +
759
+ str(round(psutil.virtual_memory().total/(1024**3),1)) + ' GB]\n'))
750
760
  # Final message
751
761
  print('End of the run ************')
752
- print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m')
762
+ print(BLUE + '#### Academic research license - for non-commercial use only ####' + RESET)
753
763
 
754
764
  return model
755
765
 
756
766
  if __name__ == '__main__':
757
- model = main()
767
+ model = main()
@@ -1,5 +1,5 @@
1
1
  """
2
- Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - November 17, 2025
2
+ Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - January 15, 2026
3
3
  """
4
4
 
5
5
  import time
@@ -9,7 +9,10 @@ import pandas as pd
9
9
  from collections import defaultdict
10
10
  from pyomo.environ import Constraint, Objective, minimize, Set, RangeSet, Param
11
11
 
12
+ # from line_profiler import profile
12
13
 
14
+
15
+ # @profile
13
16
  def TotalObjectiveFunction(OptModel, mTEPES, pIndLogConsole):
14
17
  print('Total cost o.f. model formulation ****')
15
18
 
@@ -28,6 +31,7 @@ def TotalObjectiveFunction(OptModel, mTEPES, pIndLogConsole):
28
31
  print('Total fixed and variable costs ... ', round(GeneratingTime), 's')
29
32
 
30
33
 
34
+ # @profile
31
35
  def InvestmentModelFormulation(OptModel, mTEPES, pIndLogConsole):
32
36
  print('Investment model formulation ****')
33
37
 
@@ -108,6 +112,7 @@ def InvestmentModelFormulation(OptModel, mTEPES, pIndLogConsole):
108
112
  print('Gen&transm investment o.f./constraints ... ', round(GeneratingTime), 's')
109
113
 
110
114
 
115
+ # @profile
111
116
  def GenerationOperationModelFormulationObjFunct(OptModel, mTEPES, pIndLogConsole, p, sc, st):
112
117
  print('Generation oper o.f. model formulation ****')
113
118
 
@@ -145,22 +150,22 @@ def GenerationOperationModelFormulationObjFunct(OptModel, mTEPES, pIndLogConsole
145
150
  h2n[nd].append(hp)
146
151
 
147
152
  def eTotalGCost(OptModel,n):
148
- return OptModel.vTotalGCost[p,sc,n] == (sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pLinearVarCost [p,sc,n,nr] * OptModel.vTotalOutput [p,sc,n,nr] +
149
- mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pConstantVarCost[p,sc,n,nr] * OptModel.vCommitment [p,sc,n,nr] +
150
- mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pStartUpCost [ nr] * OptModel.vStartUp [p,sc,n,nr] +
151
- mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pShutDownCost [ nr] * OptModel.vShutDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr) +
152
- sum(mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ nr] * OptModel.vReserveUp [p,sc,n,nr] +
153
- mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ nr] * OptModel.vReserveDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and mTEPES.pIndOperReserveGen[nr] == 0) +
154
- sum(mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveUp [p,sc,n,eh] +
155
- mTEPES.pLoadLevelWeight [p,sc,n]() * mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveDown [p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh and mTEPES.pIndOperReserveCon[eh] == 0) +
156
- sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pLinearVarCost [p,sc,n,bo] * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (p,bo) in mTEPES.pbo) +
157
- sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pLinearOMCost [ re] * OptModel.vTotalOutput [p,sc,n,re] for re in mTEPES.re if (p,re) in mTEPES.pre) )
153
+ return OptModel.vTotalGCost[p,sc,n] == (mTEPES.pLoadLevelDuration[p,sc,n]() * sum(mTEPES.pLinearVarCost [p,sc,n,nr] * OptModel.vTotalOutput [p,sc,n,nr] +
154
+ mTEPES.pConstantVarCost[p,sc,n,nr] * OptModel.vCommitment [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr) +
155
+ mTEPES.pLoadLevelWeight [p,sc,n]() * sum(mTEPES.pStartUpCost [ nr] * OptModel.vStartUp [p,sc,n,nr] +
156
+ mTEPES.pShutDownCost [ nr] * OptModel.vShutDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr) +
157
+ mTEPES.pLoadLevelWeight [p,sc,n]() * sum(mTEPES.pOperReserveCost[ nr] * OptModel.vReserveUp [p,sc,n,nr] +
158
+ mTEPES.pOperReserveCost[ nr] * OptModel.vReserveDown [p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and mTEPES.pIndOperReserveGen[nr] == 0) +
159
+ mTEPES.pLoadLevelWeight [p,sc,n]() * sum(mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveUp [p,sc,n,eh] +
160
+ mTEPES.pOperReserveCost[ eh] * OptModel.vESSReserveDown [p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh and mTEPES.pIndOperReserveCon[eh] == 0) +
161
+ mTEPES.pLoadLevelDuration[p,sc,n]() * sum(mTEPES.pLinearVarCost [p,sc,n,bo] * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (p,bo) in mTEPES.pbo) +
162
+ mTEPES.pLoadLevelDuration[p,sc,n]() * sum(mTEPES.pLinearOMCost [ re] * OptModel.vTotalOutput [p,sc,n,re] for re in mTEPES.re if (p,re) in mTEPES.pre) )
158
163
  setattr(OptModel, f'eTotalGCost_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eTotalGCost, doc='system variable generation operation cost [MEUR]'))
159
164
 
160
165
  # the small tolerance pEpsilon=1e-5 is added to avoid pumping/charging with curtailment/spillage
161
166
  pEpsilon = 1e-5
162
167
  def eTotalCCost(OptModel,n):
163
- return OptModel.vTotalCCost [p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * (mTEPES.pLinearVarCost[p,sc,n,eh]+pEpsilon) * OptModel.vESSTotalCharge[p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh)
168
+ return OptModel.vTotalCCost [p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * sum((mTEPES.pLinearVarCost[p,sc,n,eh]+pEpsilon) * OptModel.vESSTotalCharge[p,sc,n,eh] for eh in mTEPES.eh if (p,eh) in mTEPES.peh)
164
169
  setattr(OptModel, f'eTotalCCost_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eTotalCCost, doc='system variable consumption operation cost [MEUR]'))
165
170
 
166
171
  def eTotalECost(OptModel,n):
@@ -172,40 +177,40 @@ def GenerationOperationModelFormulationObjFunct(OptModel, mTEPES, pIndLogConsole
172
177
 
173
178
  def eTotalEmissionArea(OptModel,n,ar):
174
179
  if mTEPES.pEmission[p,ar] < math.inf and sum(mTEPES.pEmissionRate[g] for g in mTEPES.g if (ar, g) in mTEPES.a2g and (p, g) in mTEPES.pg):
175
- return OptModel.vTotalEmissionArea[p,sc,n,ar] == (sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pEmissionRate[nr] * 1e-3 * OptModel.vTotalOutput [p,sc,n,nr] for nr in mTEPES.nr if (ar,nr) in mTEPES.a2g and (p,nr) in mTEPES.pnr) #1e-3 to change from tCO2/MWh to MtCO2/GWh
176
- + sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pEmissionRate[bo] * 1e-3 * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (ar,bo) in mTEPES.a2g and (p,bo) in mTEPES.pbo)) #1e-3 to change from tCO2/MWh to MtCO2/GWh
180
+ return OptModel.vTotalEmissionArea[p,sc,n,ar] == (mTEPES.pLoadLevelDuration[p,sc,n]() * sum(mTEPES.pEmissionRate[nr] * 1e-3 * OptModel.vTotalOutput [p,sc,n,nr] for nr in mTEPES.nr if (ar,nr) in mTEPES.a2g and (p,nr) in mTEPES.pnr) #1e-3 to change from tCO2/MWh to MtCO2/GWh
181
+ + mTEPES.pLoadLevelDuration[p,sc,n]() * sum(mTEPES.pEmissionRate[bo] * 1e-3 * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (ar,bo) in mTEPES.a2g and (p,bo) in mTEPES.pbo)) #1e-3 to change from tCO2/MWh to MtCO2/GWh
177
182
  else:
178
183
  return Constraint.Skip
179
184
  setattr(OptModel, f'eTotalEmissionArea_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eTotalEmissionArea, doc='area total emission [MtCO2 eq]'))
180
185
 
181
186
  def eTotalECostArea(OptModel,n,ar):
182
187
  if sum(mTEPES.pEmissionVarCost[p,sc,n,g] for g in mTEPES.g if (ar,g) in mTEPES.a2g and (p,g) in mTEPES.pg):
183
- return OptModel.vTotalECostArea[p,sc,n,ar] == (sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pEmissionVarCost[p,sc,n, g] * OptModel.vTotalOutput [p,sc,n, g] for g in mTEPES.g if (ar, g) in mTEPES.a2g and (p, g) in mTEPES.pg )
184
- + sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pEmissionVarCost[p,sc,n,bo] * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (ar,bo) in mTEPES.a2g and (p,bo) in mTEPES.pbo))
188
+ return OptModel.vTotalECostArea[p,sc,n,ar] == (mTEPES.pLoadLevelDuration[p,sc,n]() * (sum(mTEPES.pEmissionVarCost[p,sc,n, g] * OptModel.vTotalOutput [p,sc,n, g] for g in mTEPES.g if (ar, g) in mTEPES.a2g and (p, g) in mTEPES.pg )
189
+ + sum(mTEPES.pEmissionVarCost[p,sc,n,bo] * OptModel.vTotalOutputHeat[p,sc,n,bo] for bo in mTEPES.bo if (ar,bo) in mTEPES.a2g and (p,bo) in mTEPES.pbo)))
185
190
  else:
186
191
  return Constraint.Skip
187
192
  setattr(OptModel, f'eTotalECostArea_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eTotalECostArea, doc='area emission cost [MEUR]'))
188
193
 
189
194
  def eTotalRESEnergyArea(OptModel,n,ar):
190
195
  if mTEPES.pRESEnergy[p,ar] and st == mTEPES.Last_st:
191
- return OptModel.vTotalRESEnergyArea[p,sc,n,ar] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * OptModel.vTotalOutput[p,sc,n,re] for re in mTEPES.re if (ar,re) in mTEPES.a2g and (p,re) in mTEPES.pre)
196
+ return OptModel.vTotalRESEnergyArea[p,sc,n,ar] == mTEPES.pLoadLevelDuration[p,sc,n]() * sum(OptModel.vTotalOutput[p,sc,n,re] for re in mTEPES.re if (ar,re) in mTEPES.a2g and (p,re) in mTEPES.pre)
192
197
  else:
193
198
  return Constraint.Skip
194
199
  setattr(OptModel, f'eTotalRESEnergyArea_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eTotalRESEnergyArea, doc='area RES energy [GWh]'))
195
200
 
196
201
  def eTotalNCost(OptModel,n):
197
- return OptModel.vTotalNCost[p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * pEpsilon * OptModel.vLineLosses[p,sc,n,ni,nf,cc] for ni,nf,cc in mTEPES.ll if (p,ni,nf,cc) in mTEPES.pll)
202
+ return OptModel.vTotalNCost[p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * pEpsilon * sum(OptModel.vLineLosses[p,sc,n,ni,nf,cc] for ni,nf,cc in mTEPES.ll if (p,ni,nf,cc) in mTEPES.pll)
198
203
  setattr(OptModel, f'eTotalNCost_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eTotalNCost, doc='system variable network operation cost [MEUR]'))
199
204
 
200
205
  def eTotalRCost(OptModel,n):
201
206
  if mTEPES.pIndHydrogen == 0 and mTEPES.pIndHeat == 0:
202
- return OptModel.vTotalRCost[p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd)
207
+ return OptModel.vTotalRCost[p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * sum(OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd)
203
208
  elif mTEPES.pIndHydrogen == 1 and mTEPES.pIndHeat == 0:
204
- return OptModel.vTotalRCost[p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + sum(mTEPES.pH2NSCost * OptModel.vH2NS[p,sc,n,nd] for nd in mTEPES.nd if sum(1 for el in e2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd]))
209
+ return OptModel.vTotalRCost[p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * sum(OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + mTEPES.pH2NSCost * sum(OptModel.vH2NS[p,sc,n,nd] for nd in mTEPES.nd if sum(1 for el in e2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd]))
205
210
  elif mTEPES.pIndHydrogen == 0 and mTEPES.pIndHeat == 1:
206
- return OptModel.vTotalRCost[p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pHeatNSCost * OptModel.vHeatNS[p,sc,n,nd] for nd in mTEPES.nd 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]))
211
+ return OptModel.vTotalRCost[p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * sum(OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pHeatNSCost * sum(OptModel.vHeatNS[p,sc,n,nd] for nd in mTEPES.nd 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]))
207
212
  elif mTEPES.pIndHydrogen == 1 and mTEPES.pIndHeat == 1:
208
- return OptModel.vTotalRCost[p,sc,n] == sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + sum(mTEPES.pH2NSCost * OptModel.vH2NS[p,sc,n,nd] for nd in mTEPES.nd if sum(1 for el in e2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd])) + sum(mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pHeatNSCost * OptModel.vHeatNS[p,sc,n,nd] for nd in mTEPES.nd 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]))
213
+ return OptModel.vTotalRCost[p,sc,n] == mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pENSCost * sum(OptModel.vENS[p,sc,n,nd] for nd in mTEPES.nd) + mTEPES.pH2NSCost * sum(OptModel.vH2NS[p,sc,n,nd] for nd in mTEPES.nd if sum(1 for el in e2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd])) + mTEPES.pLoadLevelDuration[p,sc,n]() * mTEPES.pHeatNSCost * sum(OptModel.vHeatNS[p,sc,n,nd] for nd in mTEPES.nd 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]))
209
214
  setattr(OptModel, f'eTotalRCost_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eTotalRCost, doc='system reliability cost [MEUR]'))
210
215
 
211
216
  GeneratingTime = time.time() - StartTime
@@ -213,13 +218,14 @@ def GenerationOperationModelFormulationObjFunct(OptModel, mTEPES, pIndLogConsole
213
218
  print('Operation cost o.f. ... ', round(GeneratingTime), 's')
214
219
 
215
220
 
221
+ # @profile
216
222
  def GenerationOperationModelFormulationInvestment(OptModel, mTEPES, pIndLogConsole, p, sc, st):
217
223
  print('Investment & operation var constraints ****')
218
224
 
219
225
  StartTime = time.time()
220
226
 
221
227
  def eInstallGenComm(OptModel,n,gc):
222
- if gc in mTEPES.nr and gc not in mTEPES.eh and gc not in mTEPES.bc and (mTEPES.pMinPowerElec[p,sc,n,gc] or mTEPES.pConstantVarCost[p,sc,n,gc]):
228
+ if gc in mTEPES.nr and gc not in mTEPES.eh and gc not in mTEPES.bc and (p,gc) in mTEPES.pgc and (mTEPES.pMinPowerElec[p,sc,n,gc] or mTEPES.pConstantVarCost[p,sc,n,gc]):
223
229
  if mTEPES.pMustRun[gc] == 0:
224
230
  return OptModel.vCommitment[p,sc,n,gc] <= OptModel.vGenerationInvest[p,gc]
225
231
  else:
@@ -232,8 +238,11 @@ def GenerationOperationModelFormulationInvestment(OptModel, mTEPES, pIndLogConso
232
238
  print('eInstallGenComm ... ', len(getattr(OptModel, f'eInstallGenComm_{p}_{sc}_{st}')), ' rows')
233
239
 
234
240
  def eInstallESSComm(OptModel,n,ec):
235
- if mTEPES.pIndBinStorInvest[ec]:
236
- return OptModel.vCommitment[p,sc,n,ec] <= OptModel.vGenerationInvest[p,ec]
241
+ if (p,ec) in mTEPES.pec:
242
+ if mTEPES.pIndBinStorInvest[ec]:
243
+ return OptModel.vCommitment[p,sc,n,ec] <= OptModel.vGenerationInvest[p,ec]
244
+ else:
245
+ return Constraint.Skip
237
246
  else:
238
247
  return Constraint.Skip
239
248
  setattr(OptModel, f'eInstallESSComm_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ec, rule=eInstallESSComm, doc='commitment if ESS unit [p.u.]'))
@@ -359,6 +368,7 @@ def GenerationOperationModelFormulationInvestment(OptModel, mTEPES, pIndLogConso
359
368
  print('Generating operation & investment ... ', round(GeneratingTime), 's')
360
369
 
361
370
 
371
+ # @profile
362
372
  def GenerationOperationModelFormulationDemand(OptModel, mTEPES, pIndLogConsole, p, sc, st):
363
373
  print('Inertia, oper resr, demand constraints ****')
364
374
 
@@ -417,7 +427,6 @@ def GenerationOperationModelFormulationDemand(OptModel, mTEPES, pIndLogConsole,
417
427
  if (sum(1 for nr in n2a[ar] if (mTEPES.pIndOperReserveGen[nr] == 0 or mTEPES.pIndOperReserveCon[nr] == 0 ))
418
428
  + sum(1 for eh in e2a[ar] if (mTEPES.pIndOperReserveGen[eh] == 0 or mTEPES.pIndOperReserveCon[eh] == 0 )) == 0):
419
429
  return Constraint.Skip
420
-
421
430
  return sum(OptModel.vReserveUp [p,sc,n,nr] for nr in n2a[ar] if mTEPES.pIndOperReserveGen[nr] == 0 and (p,nr) in mTEPES.pnr) + sum(OptModel.vESSReserveUp [p,sc,n,eh] for eh in e2a[ar] if mTEPES.pIndOperReserveCon[eh] == 0 and (p,eh) in mTEPES.peh) == mTEPES.pOperReserveUp[p,sc,n,ar]
422
431
 
423
432
  setattr(OptModel, f'eOperReserveUp_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eOperReserveUp, doc='up operating reserve [GW]'))
@@ -433,7 +442,6 @@ def GenerationOperationModelFormulationDemand(OptModel, mTEPES, pIndLogConsole,
433
442
  if (sum(1 for nr in n2a[ar] if (mTEPES.pIndOperReserveGen[nr] == 0 or mTEPES.pIndOperReserveCon[nr] == 0 ))
434
443
  + sum(1 for eh in e2a[ar] if (mTEPES.pIndOperReserveGen[eh] == 0 or mTEPES.pIndOperReserveCon[eh] == 0 )) == 0):
435
444
  return Constraint.Skip
436
-
437
445
  return sum(OptModel.vReserveDown[p,sc,n,nr] for nr in n2a[ar] if mTEPES.pIndOperReserveGen[nr] == 0 and (p,nr) in mTEPES.pnr) + sum(OptModel.vESSReserveDown[p,sc,n,eh] for eh in e2a[ar] if mTEPES.pIndOperReserveCon[eh] == 0 and (p,eh) in mTEPES.peh) == mTEPES.pOperReserveDw[p,sc,n,ar]
438
446
 
439
447
  setattr(OptModel, f'eOperReserveDw_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ar, rule=eOperReserveDw, doc='down operating reserve [GW]'))
@@ -589,6 +597,7 @@ def GenerationOperationModelFormulationDemand(OptModel, mTEPES, pIndLogConsole,
589
597
  print('Generating inertia/reserves/balance ... ', round(GeneratingTime), 's')
590
598
 
591
599
 
600
+ # @profile
592
601
  def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole, p, sc, st):
593
602
  print('Storage scheduling constraints ****')
594
603
 
@@ -643,14 +652,14 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
643
652
  if (p,es) in mTEPES.pes and (mTEPES.pTotalMaxCharge[es] or mTEPES.pTotalEnergyInflows[es]):
644
653
  if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == mTEPES.pStorageTimeStep[es]:
645
654
  if es not in mTEPES.ec:
646
- return mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
655
+ return mTEPES.pIniInventory[p,sc,n,es]() + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) * OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
647
656
  else:
648
- return OptModel.vIniInventory[p,sc,n,es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
657
+ return OptModel.vIniInventory[p,sc,n,es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) * OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
649
658
  elif (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) > mTEPES.pStorageTimeStep[es]:
650
659
  if es not in mTEPES.ec:
651
- return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
660
+ return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(mTEPES.pEnergyInflows[p,sc,n2,es]() - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) * OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
652
661
  else:
653
- return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) *OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
662
+ return OptModel.vESSInventory[p,sc,mTEPES.n.prev(n,mTEPES.pStorageTimeStep[es]),es] + sum(mTEPES.pDuration[p,sc,n2]()*(OptModel.vEnergyInflows[p,sc,n2,es] - OptModel.vEnergyOutflows[p,sc,n2,es] - OptModel.vTotalOutput[p,sc,n2,es] / math.sqrt(mTEPES.pEfficiency[es]) + math.sqrt(mTEPES.pEfficiency[es]) * OptModel.vESSTotalCharge[p,sc,n2,es]) for n2 in list(mTEPES.n2)[mTEPES.n.ord(n)-mTEPES.pStorageTimeStep[es]:mTEPES.n.ord(n)]) == OptModel.vESSInventory[p,sc,n,es] + OptModel.vESSSpillage[p,sc,n,es]
654
663
  else:
655
664
  return Constraint.Skip
656
665
  else:
@@ -694,7 +703,6 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
694
703
  # Check if generator is available in the period and has variable charging capacity
695
704
  if (p,eh) not in mTEPES.peh or mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] == 0.0:
696
705
  return Constraint.Skip
697
-
698
706
  # Hydro units have commitment while ESS units are implicitly always committed
699
707
  if eh not in mTEPES.h:
700
708
  # ESS units only need this constraint when they can offer operating reserves and the systems demands reserves
@@ -723,9 +731,7 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
723
731
  # Skip if the ESS cannot consume at variable power
724
732
  if not mTEPES.pMaxCharge2ndBlock[p,sc,n,eh]:
725
733
  return Constraint.Skip
726
-
727
734
  return OptModel.vCharge2ndBlock[p,sc,n,eh] - OptModel.vESSReserveUp[p,sc,n,eh] >= 0.0
728
-
729
735
  setattr(OptModel, f'eMinCharge_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eMinCharge, doc='min charge of an ESS [p.u.]'))
730
736
 
731
737
  if pIndLogConsole == 1:
@@ -740,15 +746,13 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
740
746
 
741
747
  # Generators with consumption capability cannot be consuming and generating simultaneously
742
748
  def eChargeDischarge(OptModel,n,eh):
743
- # Check if generator is avaiable in the period
749
+ # Check if generator is available in the period
744
750
  if (p,eh) not in mTEPES.peh:
745
751
  return Constraint.Skip
746
752
  # Constraint only relevant to generators which can consume and generate power
747
753
  if mTEPES.pMaxPower2ndBlock [p,sc,n,eh] == 0.0 or mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] == 0.0:
748
754
  return Constraint.Skip
749
-
750
755
  #Hydro generators can have binary commitment, energy modeled ESS do not have commitment
751
-
752
756
  # ESS Generator
753
757
  if eh not in mTEPES.h:
754
758
  return ((OptModel.vOutput2ndBlock[p,sc,n,eh] + mTEPES.pUpReserveActivation * OptModel.vReserveUp [p,sc,n,eh]) / mTEPES.pMaxPower2ndBlock [p,sc,n,eh] +
@@ -756,22 +760,19 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
756
760
  # Hydro Generator
757
761
  else:
758
762
  return OptModel.vCommitment[p,sc,n,eh] + OptModel.vCommitmentCons[p,sc,n,eh] <= 1.0
759
-
760
763
  setattr(OptModel, f'eChargeDischarge_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eChargeDischarge, doc='incompatibility between charge and discharge [p.u.]'))
761
764
 
762
765
  if pIndLogConsole == 1:
763
766
  print('eChargeDischarge ... ', len(getattr(OptModel, f'eChargeDischarge_{p}_{sc}_{st}')), ' rows')
764
767
 
765
768
  def eESSTotalCharge(OptModel,n,eh):
766
- # Check if generator is avaiable in the period
769
+ # Check if generator is available in the period
767
770
  if (p,eh) not in mTEPES.peh:
768
771
  return Constraint.Skip
769
772
  # Constraint only applies to generators with charging capabilities
770
773
  if mTEPES.pMaxCharge2ndBlock[p,sc,n,eh] == 0.0:
771
774
  return Constraint.Skip
772
-
773
775
  # Hydro generators can have binary commitment, energy modeled ESS do not have commitment
774
-
775
776
  # ESS Generator
776
777
  if eh not in mTEPES.h:
777
778
  # Check minimum charge to avoid dividing by 0. Dividing by MinCharge is more numerically stable
@@ -786,7 +787,6 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
786
787
  return OptModel.vESSTotalCharge[p,sc,n,eh] == OptModel.vCharge2ndBlock[p,sc,n,eh] + mTEPES.pDwReserveActivation * OptModel.vESSReserveDown[p,sc,n,eh] - mTEPES.pUpReserveActivation * OptModel.vESSReserveUp[p,sc,n,eh]
787
788
  else:
788
789
  return OptModel.vESSTotalCharge[p,sc,n,eh] / mTEPES.pMinCharge[p,sc,n,eh] == OptModel.vCommitmentCons[p,sc,n,eh] + (OptModel.vCharge2ndBlock[p,sc,n,eh] + mTEPES.pDwReserveActivation * OptModel.vESSReserveDown[p,sc,n,eh] - mTEPES.pUpReserveActivation * OptModel.vESSReserveUp[p,sc,n,eh]) / mTEPES.pMinCharge[p,sc,n,eh]
789
-
790
790
  setattr(OptModel, f'eESSTotalCharge_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.eh, rule=eESSTotalCharge, doc='total charge of an ESS unit [GW]'))
791
791
 
792
792
  if pIndLogConsole == 1:
@@ -839,6 +839,7 @@ def GenerationOperationModelFormulationStorage(OptModel, mTEPES, pIndLogConsole,
839
839
  if pIndLogConsole == 1:
840
840
  print('Generating storage operation ... ', round(GeneratingTime), 's')
841
841
 
842
+ # @profile
842
843
  def GenerationOperationModelFormulationReservoir(OptModel, mTEPES, pIndLogConsole, p, sc, st):
843
844
  print('Reservoir scheduling constraints ****')
844
845
 
@@ -897,9 +898,9 @@ def GenerationOperationModelFormulationReservoir(OptModel, mTEPES, pIndLogConsol
897
898
 
898
899
  # Avoid division by 0 if turbine has no minimum power
899
900
  if mTEPES.pMinPowerElec[p,sc,n,h] == 0.0:
900
- return OptModel.vOutput2ndBlock[p,sc,n,h] + OptModel.vReserveUp[p,sc,n,h] <= sum(OptModel.vReservoirVolume[p,sc,n,rs] - mTEPES.pMinVolume[p,sc,n,rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]()
901
+ return OptModel.vOutput2ndBlock[p,sc,n,h] + OptModel.vReserveUp[p,sc,n,h] <= sum(OptModel.vReservoirVolume[p,sc,n,rs] - mTEPES.pMinVolume[p,sc,n,rs] for rs in mTEPES.rs if (rs,h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]()
901
902
  else:
902
- return (OptModel.vOutput2ndBlock[p,sc,n,h] + OptModel.vReserveUp[p,sc,n,h]) / mTEPES.pMinPowerElec[p,sc,n,h] + OptModel.vCommitment[p,sc,n,h] <= sum(OptModel.vReservoirVolume[p,sc,n,rs] - mTEPES.pMinVolume[p,sc,n,rs] for rs in mTEPES.rs if (rs, h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]() / mTEPES.pMinPowerElec[p,sc,n,h]
903
+ return (OptModel.vOutput2ndBlock[p,sc,n,h] + OptModel.vReserveUp[p,sc,n,h]) / mTEPES.pMinPowerElec[p,sc,n,h] + OptModel.vCommitment[p,sc,n,h] <= sum(OptModel.vReservoirVolume[p,sc,n,rs] - mTEPES.pMinVolume[p,sc,n,rs] for rs in mTEPES.rs if (rs,h) in mTEPES.r2h) * mTEPES.pProductionFunctionHydro[h] / mTEPES.pDuration[p,sc,n]() / mTEPES.pMinPowerElec[p,sc,n,h]
903
904
 
904
905
  setattr(OptModel, f'eTrbReserveUpIfUpstream_{p}_{sc}_{st}', Constraint(mTEPES.nhc, rule=eTrbReserveUpIfUpstream, doc='up operating reserve if energy available [GW]'))
905
906
 
@@ -1049,6 +1050,7 @@ def GenerationOperationModelFormulationReservoir(OptModel, mTEPES, pIndLogConsol
1049
1050
  print('Generating reservoir operation ... ', round(GeneratingTime), 's')
1050
1051
 
1051
1052
 
1053
+ # @profile
1052
1054
  def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1053
1055
  print('Unit commitment constraints ****')
1054
1056
 
@@ -1062,10 +1064,20 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1062
1064
 
1063
1065
  def eMaxOutput2ndBlock(OptModel,n,nr):
1064
1066
  if (p,nr) in mTEPES.pnr and (nr not in mTEPES.es or (nr in mTEPES.es and (mTEPES.pTotalMaxCharge[nr] or mTEPES.pTotalEnergyInflows[nr]))) and sum(mTEPES.pOperReserveUp[p,sc,n,ar] for ar in a2n[nr]):
1065
- if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n != mTEPES.n.last():
1066
- return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr]) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr] - OptModel.vShutDown[p,sc,mTEPES.n.next(n),nr]
1067
- elif mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n == mTEPES.n.last():
1068
- return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr]) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr]
1067
+ if mTEPES.pIndRampReserves == 0 or sum(mTEPES.pRampReserveUp[p,sc,n,ar] for ar in mTEPES.ar) == 0.0:
1068
+ if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n != mTEPES.n.last():
1069
+ return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr] ) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr] - OptModel.vShutDown[p,sc,mTEPES.n.next(n),nr]
1070
+ elif mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n == mTEPES.n.last():
1071
+ return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr] ) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr]
1072
+ else:
1073
+ return Constraint.Skip
1074
+ elif mTEPES.pIndRampReserves == 1 and sum(mTEPES.pRampReserveUp[p,sc,n,ar] for ar in mTEPES.ar) > 0.0:
1075
+ if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n != mTEPES.n.last():
1076
+ return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr] + OptModel.vRampReserveUp [p,sc,n,nr]) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr] - OptModel.vShutDown[p,sc,mTEPES.n.next(n),nr]
1077
+ elif mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and n == mTEPES.n.last():
1078
+ return (OptModel.vOutput2ndBlock[p,sc,n,nr] + OptModel.vReserveUp [p,sc,n,nr] + OptModel.vRampReserveUp [p,sc,n,nr]) / mTEPES.pMaxPower2ndBlock[p,sc,n,nr] <= OptModel.vCommitment[p,sc,n,nr] - OptModel.vStartUp[p,sc,n,nr]
1079
+ else:
1080
+ return Constraint.Skip
1069
1081
  else:
1070
1082
  return Constraint.Skip
1071
1083
  else:
@@ -1077,8 +1089,10 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1077
1089
 
1078
1090
  def eMinOutput2ndBlock(OptModel,n,nr):
1079
1091
  if (p,nr) in mTEPES.pnr and (nr not in mTEPES.es or (nr in mTEPES.es and (mTEPES.pTotalMaxCharge[nr] or mTEPES.pTotalEnergyInflows[nr]))) and sum(mTEPES.pOperReserveDw[p,sc,n,ar] for ar in a2n[nr]):
1080
- if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1:
1081
- return OptModel.vOutput2ndBlock[p,sc,n,nr] - OptModel.vReserveDown[p,sc,n,nr] >= 0.0
1092
+ if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and (mTEPES.pIndRampReserves == 0 or sum(mTEPES.pRampReserveDw[p,sc,n,ar] for ar in mTEPES.ar) == 0.0):
1093
+ return OptModel.vOutput2ndBlock[p,sc,n,nr] - OptModel.vReserveDown[p,sc,n,nr] >= 0.0
1094
+ if mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pIndOperReserveGen[nr] != 1 and mTEPES.pIndRampReserves == 1 and sum(mTEPES.pRampReserveDw[p,sc,n,ar] for ar in mTEPES.ar) > 0.0:
1095
+ return OptModel.vOutput2ndBlock[p,sc,n,nr] - OptModel.vReserveDown[p,sc,n,nr] - OptModel.vRampReserveDw[p,sc,n,nr] >= 0.0
1082
1096
  else:
1083
1097
  return Constraint.Skip
1084
1098
  else:
@@ -1140,10 +1154,9 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1140
1154
  # Skip if the generator is not part of the exclusive group
1141
1155
  if nr not in mTEPES.GeneratorsInYearlyGroup[group]:
1142
1156
  return Constraint.Skip
1143
- # Skip if there are one or less generators in the group
1157
+ # Skip if there are one or fewer generators in the group
1144
1158
  if len(mTEPES.GeneratorsInYearlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
1145
1159
  return Constraint.Skip
1146
-
1147
1160
  return OptModel.vCommitment[p,sc,n,nr] <= OptModel.vMaxCommitmentYearly[p,sc,nr,group]
1148
1161
 
1149
1162
  setattr(OptModel, f'eMaxCommitmentYearly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsYearly, mTEPES.nr, rule=eMaxCommitmentYearly, doc='maximum of all the commitments [p.u.]'))
@@ -1164,7 +1177,6 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1164
1177
  # Skip if there are one or less generators in the group
1165
1178
  if len(mTEPES.GeneratorsInYearlyGroup[group] & {nr for (p, nr) in mTEPES.pnr}) <= 1:
1166
1179
  return Constraint.Skip
1167
-
1168
1180
  return OptModel.vTotalOutput[p,sc,n,nr]/mTEPES.pMaxPowerElec[p,sc,n,nr] <= OptModel.vMaxCommitmentYearly[p,sc,nr,group]
1169
1181
 
1170
1182
  setattr(OptModel, f'eMaxCommitGenYearly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsYearly,mTEPES.nr, rule=eMaxCommitGenYearly, doc='maximum of all the capacity factors'))
@@ -1193,10 +1205,9 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1193
1205
  # Skip if the generator is not part of the exclusive group
1194
1206
  if nr not in mTEPES.GeneratorsInHourlyGroup[group]:
1195
1207
  return Constraint.Skip
1196
- # Skip if there are one or less generators in the group
1208
+ # Skip if there are one or fewer generators in the group
1197
1209
  if len(mTEPES.GeneratorsInHourlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
1198
1210
  return Constraint.Skip
1199
-
1200
1211
  return OptModel.vCommitment[p,sc,n,nr] <= OptModel.vMaxCommitmentHourly[p,sc,n,nr,group]
1201
1212
 
1202
1213
  setattr(OptModel, f'eMaxCommitmentHourly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsHourly, mTEPES.nr, rule=eMaxCommitmentHourly, doc='maximum of all the commitments [p.u.]'))
@@ -1217,7 +1228,6 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1217
1228
  # Skip if there are one or less generators in the group
1218
1229
  if len(mTEPES.GeneratorsInHourlyGroup[group] & {nr for (p,nr) in mTEPES.pnr}) <= 1:
1219
1230
  return Constraint.Skip
1220
-
1221
1231
  return OptModel.vTotalOutput[p,sc,n,nr]/mTEPES.pMaxPowerElec[p,sc,n,nr] <= OptModel.vMaxCommitmentHourly[p,sc,n,nr,group]
1222
1232
 
1223
1233
  setattr(OptModel, f'eMaxCommitGenHourly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsHourly,mTEPES.nr, rule=eMaxCommitGenHourly, doc='maximum of all the capacity factors'))
@@ -1226,14 +1236,13 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1226
1236
  print('eMaxCommitGenHourly ... ', len(getattr(OptModel, f'eMaxCommitGenHourly_{p}_{sc}_{st}')), ' rows')
1227
1237
 
1228
1238
  def eExclusiveGensHourly(OptModel,n,group):
1229
- # Skip if there are one or less generators in the group
1239
+ # Skip if there are one or fewer generators in the group
1230
1240
  # This is written in a different way to the rest of the code to avoid variable shadowing due to comprehension
1231
1241
  if len(mTEPES.GeneratorsInHourlyGroup[group] & {gen for (period, gen) in mTEPES.pnr if period == p}) <= 1:
1232
1242
  return Constraint.Skip
1233
-
1234
1243
  return sum(OptModel.vMaxCommitmentHourly[p,sc,n,nr,group] + (OptModel.vCommitmentCons[p,sc,n,nr] if nr in mTEPES.h else 0) for nr in mTEPES.GeneratorsInHourlyGroup[group] if (p, nr) in mTEPES.pnr ) <= 1
1235
1244
 
1236
- setattr(OptModel, f'eExclusiveGensHourly_{p}_{sc}_{st}', Constraint(mTEPES.n,mTEPES.ExclusiveGroupsHourly, rule=eExclusiveGensHourly, doc='mutually exclusive generators'))
1245
+ setattr(OptModel, f'eExclusiveGensHourly_{p}_{sc}_{st}', Constraint(mTEPES.n, mTEPES.ExclusiveGroupsHourly, rule=eExclusiveGensHourly, doc='mutually exclusive generators'))
1237
1246
 
1238
1247
  if pIndLogConsole == 1:
1239
1248
  print('eExclusiveGensHourly ... ', len(getattr(OptModel, f'eExclusiveGensHourly_{p}_{sc}_{st}')), ' rows')
@@ -1241,11 +1250,34 @@ def GenerationOperationModelFormulationCommitment(OptModel, mTEPES, pIndLogConso
1241
1250
  GeneratingTime = time.time() - StartTime
1242
1251
  if pIndLogConsole == 1:
1243
1252
  print('Generating generation commitment ... ', round(GeneratingTime), 's')
1253
+
1254
+
1255
+ # @profile
1244
1256
  def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1245
1257
  print('Ramp and min up/down time constraints ****')
1246
1258
 
1247
1259
  StartTime = time.time()
1248
1260
 
1261
+ def eSystemRampUp(OptModel,n):
1262
+ if mTEPES.pIndRampReserves == 1 and sum(mTEPES.pRampReserveUp[p,sc,n,ar] for ar in mTEPES.ar):
1263
+ return sum(OptModel.vRampReserveUp[p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and (nr not in mTEPES.es or (nr in mTEPES.es and (mTEPES.pTotalMaxCharge[nr] or mTEPES.pTotalEnergyInflows[nr])))) / mTEPES.pDuration[p,sc,n]() >= sum(mTEPES.pRampReserveUp[p,sc,n,ar] for ar in mTEPES.ar)
1264
+ else:
1265
+ return Constraint.Skip
1266
+ setattr(OptModel, f'eSystemRampUp_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eSystemRampUp, doc='minimum system ramp up [p.u.]'))
1267
+
1268
+ if pIndLogConsole == 1:
1269
+ print('eSystemRampUp ... ', len(getattr(OptModel, f'eSystemRampUp_{p}_{sc}_{st}')), ' rows')
1270
+
1271
+ def eSystemRampDw(OptModel,n):
1272
+ if mTEPES.pIndRampReserves == 1 and sum(mTEPES.pRampReserveDw[p,sc,n,ar] for ar in mTEPES.ar):
1273
+ return sum(OptModel.vRampReserveDw[p,sc,n,nr] for nr in mTEPES.nr if (p,nr) in mTEPES.pnr and (nr not in mTEPES.es or (nr in mTEPES.es and (mTEPES.pTotalMaxCharge[nr] or mTEPES.pTotalEnergyInflows[nr])))) / mTEPES.pDuration[p,sc,n]() >= sum(mTEPES.pRampReserveDw[p,sc,n,ar] for ar in mTEPES.ar)
1274
+ else:
1275
+ return Constraint.Skip
1276
+ setattr(OptModel, f'eSystemRampDw_{p}_{sc}_{st}', Constraint(mTEPES.n, rule=eSystemRampDw, doc='minimum system ramp down [p.u.]'))
1277
+
1278
+ if pIndLogConsole == 1:
1279
+ print('eSystemRampDw ... ', len(getattr(OptModel, f'eSystemRampDw_{p}_{sc}_{st}')), ' rows')
1280
+
1249
1281
  def eRampUp(OptModel,n,nr):
1250
1282
  if (p,nr) in mTEPES.pnr and (nr not in mTEPES.es or (nr in mTEPES.es and (mTEPES.pTotalMaxCharge[nr] or mTEPES.pTotalEnergyInflows[nr]))):
1251
1283
  if mTEPES.pRampUp[nr] and mTEPES.pIndBinGenRamps() == 1 and mTEPES.pRampUp[nr]*mTEPES.pDuration[p,sc,n]() < mTEPES.pMaxPower2ndBlock[p,sc,n,nr] and mTEPES.pDuration[p,sc,n]():
@@ -1434,6 +1466,7 @@ def GenerationOperationModelFormulationRampMinTime(OptModel, mTEPES, pIndLogCons
1434
1466
  print('Generating ramps & minimum time ... ', round(GeneratingTime), 's')
1435
1467
 
1436
1468
 
1469
+ # @profile
1437
1470
  def NetworkSwitchingModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1438
1471
  print('Network switching model constraints ****')
1439
1472
 
@@ -1499,6 +1532,7 @@ def NetworkSwitchingModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
1499
1532
  print('Switching minimum on/off state ... ', round(GeneratingTime), 's')
1500
1533
 
1501
1534
 
1535
+ # @profile
1502
1536
  def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1503
1537
  print('Network operation model constraints ****')
1504
1538
 
@@ -1631,6 +1665,8 @@ def NetworkOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st
1631
1665
  if pIndLogConsole == 1:
1632
1666
  print('Generating network constraints ... ', round(GeneratingTime), 's')
1633
1667
 
1668
+
1669
+ # @profile
1634
1670
  def NetworkCycles(mTEPES, pIndLogConsole):
1635
1671
  print('Network Cycles Detection ****')
1636
1672
 
@@ -1702,6 +1738,8 @@ def NetworkCycles(mTEPES, pIndLogConsole):
1702
1738
  if pIndLogConsole == 1:
1703
1739
  print('Cycles detection ... ', round(CyclesDetectionTime), 's')
1704
1740
 
1741
+
1742
+ # @profile
1705
1743
  def CycleConstraints(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1706
1744
  print('Network cycle constraints ****')
1707
1745
 
@@ -1759,6 +1797,8 @@ def CycleConstraints(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1759
1797
  if pIndLogConsole == 1:
1760
1798
  print('Generating cycle flow constraints ... ', round(CycleFlowTime), 's')
1761
1799
 
1800
+
1801
+ # @profile
1762
1802
  def NetworkH2OperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1763
1803
  print('Hydrogen scheduling constraints ****')
1764
1804
 
@@ -1785,7 +1825,7 @@ def NetworkH2OperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc,
1785
1825
 
1786
1826
  def eBalanceH2(OptModel,n,nd):
1787
1827
  if sum(1 for el in e2n[nd]) + sum(1 for nf,cc in lout[nd]) + sum(1 for ni,cc in lin[nd]):
1788
- return (sum(OptModel.vESSTotalCharge[p,sc,n,el]*mTEPES.pDuration[p,sc,n]()/mTEPES.pProductionFunctionH2[el] for el in e2n[nd]) - sum(OptModel.vTotalOutputHeat[p,sc,n,hh]*mTEPES.pDuration[p,sc,n]()*mTEPES.pProductionFunctionH2ToHeat[hh] for hh in b2n[nd] if (p,hh) in mTEPES.phh and hh in mTEPES.hh) + OptModel.vH2NS[p,sc,n,nd] -
1828
+ return (mTEPES.pDuration[p,sc,n]()*sum(OptModel.vESSTotalCharge[p,sc,n,el]/mTEPES.pProductionFunctionH2[el] for el in e2n[nd]) - mTEPES.pDuration[p,sc,n]()*sum(OptModel.vTotalOutputHeat[p,sc,n,hh]*mTEPES.pProductionFunctionH2ToHeat[hh] for hh in b2n[nd] if (p,hh) in mTEPES.phh and hh in mTEPES.hh) + OptModel.vH2NS[p,sc,n,nd] -
1789
1829
  sum(OptModel.vFlowH2[p,sc,n,nd,nf,cc] for nf,cc in lout[nd] if (p,nd,nf,cc) in mTEPES.ppa) + sum(OptModel.vFlowH2[p,sc,n,ni,nd,cc] for ni,cc in lin[nd] if (p,ni,nd,cc) in mTEPES.ppa)) == mTEPES.pDemandH2[p,sc,n,nd]*mTEPES.pDuration[p,sc,n]()
1790
1830
  else:
1791
1831
  return Constraint.Skip
@@ -1799,6 +1839,7 @@ def NetworkH2OperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc,
1799
1839
  print('Generating hydrogen operation ... ', round(GeneratingTime), 's')
1800
1840
 
1801
1841
 
1842
+ # @profile
1802
1843
  def NetworkHeatOperationModelFormulation(OptModel, mTEPES, pIndLogConsole, p, sc, st):
1803
1844
  print('Heat scheduling constraints ****')
1804
1845