openTEPES 4.18.2__py3-none-any.whl → 4.18.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openTEPES/9n/oT_Data_RESEnergy_9n.csv +1 -1
- openTEPES/9n/oT_Data_ReserveMargin_9n.csv +1 -1
- openTEPES/__init__.py +1 -1
- openTEPES/openTEPES.py +11 -5
- openTEPES/openTEPES_InputData.py +117 -93
- openTEPES/openTEPES_Main.py +4 -3
- openTEPES/openTEPES_ModelFormulation.py +54 -45
- openTEPES/openTEPES_OutputResults.py +178 -149
- openTEPES/openTEPES_ProblemSolving.py +4 -5
- {opentepes-4.18.2.dist-info → opentepes-4.18.3.dist-info}/METADATA +6 -3
- {opentepes-4.18.2.dist-info → opentepes-4.18.3.dist-info}/RECORD +14 -14
- {opentepes-4.18.2.dist-info → opentepes-4.18.3.dist-info}/LICENSE +0 -0
- {opentepes-4.18.2.dist-info → opentepes-4.18.3.dist-info}/WHEEL +0 -0
- {opentepes-4.18.2.dist-info → opentepes-4.18.3.dist-info}/entry_points.txt +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Period,
|
|
1
|
+
Period,Area,RESEnergy
|
|
2
2
|
2030,Area1,1000
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Period,
|
|
1
|
+
Period,Area,ReserveMargin
|
|
2
2
|
2030,Area1,1.1
|
openTEPES/__init__.py
CHANGED
|
@@ -14,7 +14,7 @@ Open Generation, Storage, and Transmission Operation and Expansion Planning Mode
|
|
|
14
14
|
>>> import openTEPES as oT
|
|
15
15
|
>>> oT.routine("9n", "C:\\Users\\UserName\\Documents\\GitHub\\openTEPES", "glpk")
|
|
16
16
|
"""
|
|
17
|
-
__version__ = "4.18.
|
|
17
|
+
__version__ = "4.18.3"
|
|
18
18
|
|
|
19
19
|
from .openTEPES_Main import main
|
|
20
20
|
from .openTEPES import *
|
openTEPES/openTEPES.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) -
|
|
2
|
+
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 18, 2025
|
|
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.
|
|
43
|
-
print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.
|
|
42
|
+
mTEPES = ConcreteModel('Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025')
|
|
43
|
+
print( 'Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025', 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]
|
|
@@ -113,7 +113,9 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
113
113
|
mTEPES.del_component(mTEPES.na)
|
|
114
114
|
mTEPES.na = Set(initialize=mTEPES.n)
|
|
115
115
|
else:
|
|
116
|
-
|
|
116
|
+
temp_na = mTEPES.na | mTEPES.n # Create a temporary set
|
|
117
|
+
mTEPES.del_component(mTEPES.na) # Delete the existing set to avoid overwriting
|
|
118
|
+
mTEPES.na = Set(initialize=temp_na) # Assign the new set
|
|
117
119
|
|
|
118
120
|
print(f'Period {p}, Scenario {sc}, Stage {st}')
|
|
119
121
|
|
|
@@ -138,7 +140,11 @@ def openTEPES_run(DirName, CaseName, SolverName, pIndOutputResults, pIndLogConso
|
|
|
138
140
|
NetworkCycles ( mTEPES, pIndLogConsole )
|
|
139
141
|
CycleConstraints (mTEPES, mTEPES, pIndLogConsole, p, sc, st)
|
|
140
142
|
|
|
141
|
-
if (len(mTEPES.gc) == 0 or
|
|
143
|
+
if ( (len(mTEPES.gc) == 0 or mTEPES.pIndBinGenInvest() == 2) # No generator investments
|
|
144
|
+
and (len(mTEPES.gd) == 0 or mTEPES.pIndBinGenRetire() == 2) # No generator retirements
|
|
145
|
+
and (len(mTEPES.lc) == 0 or mTEPES.pIndBinNetElecInvest() == 2) # No line investments
|
|
146
|
+
and (max([mTEPES.pRESEnergy[p,ar] for ar in mTEPES.ar]) == 0) # No minimum RES requirements
|
|
147
|
+
and (min([mTEPES.pEmission [p,ar] for ar in mTEPES.ar]) == math.inf or sum(mTEPES.pEmissionRate[nr] for nr in mTEPES.nr) == 0)): # No emission limit
|
|
142
148
|
|
|
143
149
|
if pIndLogConsole == 1:
|
|
144
150
|
StartTime = time.time()
|
openTEPES/openTEPES_InputData.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) -
|
|
2
|
+
Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 17, 2025
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import datetime
|
|
@@ -233,37 +233,37 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
233
233
|
num_lines = sum(1 for _ in reader)
|
|
234
234
|
return num_lines
|
|
235
235
|
|
|
236
|
-
mTEPES.rs = Set(initialize=[], doc='reservoirs'
|
|
237
|
-
mTEPES.r2h = Set(initialize=[], doc='reservoir to hydro'
|
|
238
|
-
mTEPES.h2r = Set(initialize=[], doc='hydro to reservoir'
|
|
239
|
-
mTEPES.r2r = Set(initialize=[], doc='reservoir to reservoir'
|
|
240
|
-
mTEPES.p2r = Set(initialize=[], doc='pumped-hydro to reservoir')
|
|
241
|
-
mTEPES.r2p = Set(initialize=[], doc='reservoir to pumped-hydro')
|
|
236
|
+
mTEPES.rs = Set(initialize=[], doc='reservoirs' )
|
|
237
|
+
mTEPES.r2h = Set(initialize=[], doc='reservoir to hydro' )
|
|
238
|
+
mTEPES.h2r = Set(initialize=[], doc='hydro to reservoir' )
|
|
239
|
+
mTEPES.r2r = Set(initialize=[], doc='reservoir 1 to reservoir 2')
|
|
240
|
+
mTEPES.p2r = Set(initialize=[], doc='pumped-hydro to reservoir' )
|
|
241
|
+
mTEPES.r2p = Set(initialize=[], doc='reservoir to pumped-hydro' )
|
|
242
242
|
|
|
243
243
|
if count_lines_in_csv( f'{_path}/oT_Dict_Reservoir_' f'{CaseName}.csv') > 1:
|
|
244
244
|
dictSets.load(filename=f'{_path}/oT_Dict_Reservoir_' f'{CaseName}.csv', set='rs' , format='set')
|
|
245
245
|
mTEPES.del_component(mTEPES.rs)
|
|
246
|
-
mTEPES.rs = Set(initialize=dictSets['rs' ], doc='reservoirs'
|
|
246
|
+
mTEPES.rs = Set(initialize=dictSets['rs' ], doc='reservoirs' )
|
|
247
247
|
if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToHydro_' f'{CaseName}.csv') > 1:
|
|
248
248
|
dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToHydro_' f'{CaseName}.csv', set='r2h', format='set')
|
|
249
249
|
mTEPES.del_component(mTEPES.r2h)
|
|
250
|
-
mTEPES.r2h = Set(initialize=dictSets['r2h'], doc='reservoir to hydro'
|
|
250
|
+
mTEPES.r2h = Set(initialize=dictSets['r2h'], doc='reservoir to hydro' )
|
|
251
251
|
if count_lines_in_csv( f'{_path}/oT_Dict_HydroToReservoir_' f'{CaseName}.csv') > 1:
|
|
252
252
|
dictSets.load(filename=f'{_path}/oT_Dict_HydroToReservoir_' f'{CaseName}.csv', set='h2r', format='set')
|
|
253
253
|
mTEPES.del_component(mTEPES.h2r)
|
|
254
|
-
mTEPES.h2r = Set(initialize=dictSets['h2r'], doc='hydro to reservoir'
|
|
254
|
+
mTEPES.h2r = Set(initialize=dictSets['h2r'], doc='hydro to reservoir' )
|
|
255
255
|
if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToReservoir_' f'{CaseName}.csv') > 1:
|
|
256
256
|
dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToReservoir_' f'{CaseName}.csv', set='r2r', format='set')
|
|
257
257
|
mTEPES.del_component(mTEPES.r2r)
|
|
258
|
-
mTEPES.r2r = Set(initialize=dictSets['r2r'], doc='reservoir to reservoir'
|
|
258
|
+
mTEPES.r2r = Set(initialize=dictSets['r2r'], doc='reservoir 1 to reservoir 2')
|
|
259
259
|
if count_lines_in_csv( f'{_path}/oT_Dict_PumpedHydroToReservoir_' f'{CaseName}.csv') > 1:
|
|
260
260
|
dictSets.load(filename=f'{_path}/oT_Dict_PumpedHydroToReservoir_' f'{CaseName}.csv', set='p2r', format='set')
|
|
261
261
|
mTEPES.del_component(mTEPES.p2r)
|
|
262
|
-
mTEPES.p2r = Set(initialize=dictSets['p2r'], doc='pumped-hydro to reservoir')
|
|
262
|
+
mTEPES.p2r = Set(initialize=dictSets['p2r'], doc='pumped-hydro to reservoir' )
|
|
263
263
|
if count_lines_in_csv( f'{_path}/oT_Dict_ReservoirToPumpedHydro_' f'{CaseName}.csv') > 1:
|
|
264
264
|
dictSets.load(filename=f'{_path}/oT_Dict_ReservoirToPumpedHydro_' f'{CaseName}.csv', set='r2p', format='set')
|
|
265
265
|
mTEPES.del_component(mTEPES.r2p)
|
|
266
|
-
mTEPES.r2p = Set(initialize=dictSets['r2p'], doc='reservoir to pumped-hydro')
|
|
266
|
+
mTEPES.r2p = Set(initialize=dictSets['r2p'], doc='reservoir to pumped-hydro' )
|
|
267
267
|
except:
|
|
268
268
|
pass
|
|
269
269
|
|
|
@@ -302,10 +302,10 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
302
302
|
pReserveMargin = dfReserveMargin['ReserveMargin' ] # minimum adequacy reserve margin [p.u.]
|
|
303
303
|
pEmission = dfEmission ['CO2Emission' ] # maximum CO2 emission [MtCO2]
|
|
304
304
|
pRESEnergy = dfRESEnergy ['RESEnergy' ] # minimum RES energy [GWh]
|
|
305
|
-
pDemandElec
|
|
306
|
-
pSystemInertia
|
|
307
|
-
pOperReserveUp
|
|
308
|
-
pOperReserveDw
|
|
305
|
+
pDemandElec = dfDemand.reindex (columns=mTEPES.nd, fill_value=0.0) * 1e-3 # electric demand [GW]
|
|
306
|
+
pSystemInertia = dfInertia.reindex (columns=mTEPES.ar, fill_value=0.0) # inertia [s]
|
|
307
|
+
pOperReserveUp = dfUpOperatingReserve.reindex (columns=mTEPES.ar, fill_value=0.0) * 1e-3 # upward operating reserve [GW]
|
|
308
|
+
pOperReserveDw = dfDwOperatingReserve.reindex (columns=mTEPES.ar, fill_value=0.0) * 1e-3 # downward operating reserve [GW]
|
|
309
309
|
|
|
310
310
|
pVariableMinPowerElec = dfVariableMinPower.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic variable minimum power [GW]
|
|
311
311
|
pVariableMaxPowerElec = dfVariableMaxPower.reindex (columns=mTEPES.gg, fill_value=0.0) * 1e-3 # dynamic variable maximum power [GW]
|
|
@@ -378,6 +378,13 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
378
378
|
for n in range(pTimeStep-2,-1,-1):
|
|
379
379
|
pDuration.iloc[[range(n,len(mTEPES.pp)*len(mTEPES.scc)*len(mTEPES.nn),pTimeStep)]] = 0
|
|
380
380
|
|
|
381
|
+
for psn in pDuration.index:
|
|
382
|
+
p,sc,n = psn
|
|
383
|
+
if pPeriodWeight[p] == 0:
|
|
384
|
+
pDuration.loc[p,sc,n] = 0
|
|
385
|
+
if pScenProb[p,sc] == 0:
|
|
386
|
+
pDuration.loc[p,sc,n] = 0
|
|
387
|
+
|
|
381
388
|
#%% generation parameters
|
|
382
389
|
pGenToNode = dfGeneration ['Node' ] # generator location in node
|
|
383
390
|
pGenToTechnology = dfGeneration ['Technology' ] # generator association to technology
|
|
@@ -578,9 +585,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
578
585
|
if pIndHydroTopology == 1:
|
|
579
586
|
mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[rs for rs in mTEPES.rs if pRsrInvestCost [rs] > 0.0 and pRsrPeriodIni[rs] <= mTEPES.p.last() and pRsrPeriodFin[rs] >= mTEPES.p.first()])
|
|
580
587
|
else:
|
|
581
|
-
mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[]
|
|
588
|
+
mTEPES.rn = Set(doc='candidate reservoirs' , initialize=[])
|
|
582
589
|
if pIndHydrogen == 1:
|
|
583
|
-
mTEPES.pn = Set(doc='all input hydrogen pipes' , initialize=dfNetworkHydrogen.index
|
|
590
|
+
mTEPES.pn = Set(doc='all input hydrogen pipes' , initialize=dfNetworkHydrogen.index)
|
|
584
591
|
if len(mTEPES.pn) != len(dfNetworkHydrogen.index):
|
|
585
592
|
raise ValueError('### Some hydrogen pipes are invalid ', len(mTEPES.pn), len(dfNetworkHydrogen.index))
|
|
586
593
|
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()])
|
|
@@ -876,9 +883,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
876
883
|
pLinearVarCost = pLinearVarCost.reindex (sorted(pLinearVarCost.columns ), axis=1)
|
|
877
884
|
pConstantVarCost = pConstantVarCost.reindex(sorted(pConstantVarCost.columns), axis=1)
|
|
878
885
|
|
|
879
|
-
# variable emission cost
|
|
880
|
-
pVariableEmissionCost = pVariableEmissionCost.replace(0.0, pCO2Cost)
|
|
881
|
-
pEmissionVarCost = pEmissionRate * 1e-3 * pVariableEmissionCost
|
|
886
|
+
# variable emission cost [M€/GWh]
|
|
887
|
+
pVariableEmissionCost = pVariableEmissionCost.replace(0.0, pCO2Cost) #[€/tCO2]
|
|
888
|
+
pEmissionVarCost = pEmissionRate * 1e-3 * pVariableEmissionCost #[M€/GWh] = [tCO2/MWh] * 1e-3 * [€/tCO2]
|
|
882
889
|
pEmissionVarCost = pEmissionVarCost.reindex(sorted(pEmissionVarCost.columns), axis=1)
|
|
883
890
|
|
|
884
891
|
# minimum up- and downtime and maximum shift time converted to an integer number of time steps
|
|
@@ -889,8 +896,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
889
896
|
|
|
890
897
|
# %% definition of the time-steps leap to observe the stored energy at an ESS
|
|
891
898
|
idxCycle = dict()
|
|
892
|
-
idxCycle[0 ] =
|
|
893
|
-
idxCycle[0.0 ] =
|
|
899
|
+
idxCycle[0 ] = 1
|
|
900
|
+
idxCycle[0.0 ] = 1
|
|
894
901
|
idxCycle['Hourly' ] = 1
|
|
895
902
|
idxCycle['Daily' ] = 1
|
|
896
903
|
idxCycle['Weekly' ] = round( 24/pTimeStep)
|
|
@@ -898,8 +905,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
898
905
|
idxCycle['Yearly' ] = round( 672/pTimeStep)
|
|
899
906
|
|
|
900
907
|
idxOutflows = dict()
|
|
901
|
-
idxOutflows[0 ] =
|
|
902
|
-
idxOutflows[0.0 ] =
|
|
908
|
+
idxOutflows[0 ] = 1
|
|
909
|
+
idxOutflows[0.0 ] = 1
|
|
903
910
|
idxOutflows['Hourly' ] = 1
|
|
904
911
|
idxOutflows['Daily' ] = round( 24/pTimeStep)
|
|
905
912
|
idxOutflows['Weekly' ] = round( 168/pTimeStep)
|
|
@@ -907,17 +914,17 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
907
914
|
idxOutflows['Yearly' ] = round(8736/pTimeStep)
|
|
908
915
|
|
|
909
916
|
idxEnergy = dict()
|
|
910
|
-
idxEnergy[0 ] =
|
|
911
|
-
idxEnergy[0.0 ] =
|
|
917
|
+
idxEnergy[0 ] = 1
|
|
918
|
+
idxEnergy[0.0 ] = 1
|
|
912
919
|
idxEnergy['Hourly' ] = 1
|
|
913
920
|
idxEnergy['Daily' ] = round( 24/pTimeStep)
|
|
914
921
|
idxEnergy['Weekly' ] = round( 168/pTimeStep)
|
|
915
922
|
idxEnergy['Monthly'] = round( 672/pTimeStep)
|
|
916
923
|
idxEnergy['Yearly' ] = round(8736/pTimeStep)
|
|
917
924
|
|
|
918
|
-
pStorageTimeStep = pStorageType.map (idxCycle
|
|
919
|
-
pOutflowsTimeStep = pOutflowsType.map(idxOutflows).where(pEnergyOutflows.sum() > 0.0, other =
|
|
920
|
-
pEnergyTimeStep = pEnergyType.map (idxEnergy ).where(pVariableMinEnergy.sum() + pVariableMaxEnergy.sum() > 0.0, other =
|
|
925
|
+
pStorageTimeStep = pStorageType.map (idxCycle ).astype('int')
|
|
926
|
+
pOutflowsTimeStep = pOutflowsType.map(idxOutflows).where(pEnergyOutflows.sum() > 0.0, other = 1).astype('int')
|
|
927
|
+
pEnergyTimeStep = pEnergyType.map (idxEnergy ).where(pVariableMinEnergy.sum() + pVariableMaxEnergy.sum() > 0.0, other = 1).astype('int')
|
|
921
928
|
|
|
922
929
|
pStorageTimeStep = pd.concat([pStorageTimeStep, pOutflowsTimeStep, pEnergyTimeStep], axis=1).min(axis=1)
|
|
923
930
|
# cycle time step can't exceed the stage duration
|
|
@@ -926,8 +933,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
926
933
|
if pIndHydroTopology == 1:
|
|
927
934
|
# %% definition of the time-steps leap to observe the stored energy at a reservoir
|
|
928
935
|
idxCycleRsr = dict()
|
|
929
|
-
idxCycleRsr[0 ] =
|
|
930
|
-
idxCycleRsr[0.0 ] =
|
|
936
|
+
idxCycleRsr[0 ] = 1
|
|
937
|
+
idxCycleRsr[0.0 ] = 1
|
|
931
938
|
idxCycleRsr['Hourly' ] = 1
|
|
932
939
|
idxCycleRsr['Daily' ] = 1
|
|
933
940
|
idxCycleRsr['Weekly' ] = round( 24/pTimeStep)
|
|
@@ -935,8 +942,8 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
935
942
|
idxCycleRsr['Yearly' ] = round( 672/pTimeStep)
|
|
936
943
|
|
|
937
944
|
idxWaterOut = dict()
|
|
938
|
-
idxWaterOut[0 ] =
|
|
939
|
-
idxWaterOut[0.0 ] =
|
|
945
|
+
idxWaterOut[0 ] = 1
|
|
946
|
+
idxWaterOut[0.0 ] = 1
|
|
940
947
|
idxWaterOut['Hourly' ] = 1
|
|
941
948
|
idxWaterOut['Daily' ] = round( 24/pTimeStep)
|
|
942
949
|
idxWaterOut['Weekly' ] = round( 168/pTimeStep)
|
|
@@ -961,28 +968,28 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
961
968
|
pInitialInventory.update(pd.Series([pInitialInventory[ec] if pIndBinStorInvest[ec] == 0 else pRatedMaxStorage[ec] for ec in mTEPES.ec], index=mTEPES.ec, dtype='float64'))
|
|
962
969
|
|
|
963
970
|
# parameter that allows the initial inventory to change with load level
|
|
964
|
-
pIniInventory = pd.DataFrame([pInitialInventory]*len(mTEPES.psn), index=
|
|
971
|
+
pIniInventory = pd.DataFrame([pInitialInventory]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.es)
|
|
965
972
|
if pIndHydroTopology == 1:
|
|
966
|
-
pIniVolume = pd.DataFrame([pInitialVolume ]*len(mTEPES.psn), index=
|
|
973
|
+
pIniVolume = pd.DataFrame([pInitialVolume ]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.rs)
|
|
967
974
|
|
|
968
975
|
# initial inventory must be between minimum and maximum
|
|
969
976
|
for p,sc,n,es in mTEPES.psnes:
|
|
970
977
|
if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == pStorageTimeStep[es]:
|
|
971
978
|
if pIniInventory[es][p,sc,n] < pMinStorage[es][p,sc,n]:
|
|
972
979
|
pIniInventory[es][p,sc,n] = pMinStorage[es][p,sc,n]
|
|
973
|
-
print('### Initial inventory lower than minimum storage ', es)
|
|
980
|
+
print('### Initial inventory lower than minimum storage ', p, sc, st, es)
|
|
974
981
|
if pIniInventory[es][p,sc,n] > pMaxStorage[es][p,sc,n]:
|
|
975
982
|
pIniInventory[es][p,sc,n] = pMaxStorage[es][p,sc,n]
|
|
976
|
-
print('### Initial inventory greater than maximum storage ', es)
|
|
983
|
+
print('### Initial inventory greater than maximum storage ', p, sc, st, es)
|
|
977
984
|
if pIndHydroTopology == 1:
|
|
978
985
|
for p,sc,n,rs in mTEPES.psnrs:
|
|
979
986
|
if (p,sc,st,n) in mTEPES.s2n and mTEPES.n.ord(n) == pReservoirTimeStep[rs]:
|
|
980
987
|
if pIniVolume[rs][p,sc,n] < pMinVolume[rs][p,sc,n]:
|
|
981
988
|
pIniVolume[rs][p,sc,n] = pMinVolume[rs][p,sc,n]
|
|
982
|
-
print('### Initial volume lower than minimum volume ', rs)
|
|
989
|
+
print('### Initial volume lower than minimum volume ', p, sc, st, rs)
|
|
983
990
|
if pIniVolume[rs][p,sc,n] > pMaxVolume[rs][p,sc,n]:
|
|
984
991
|
pIniVolume[rs][p,sc,n] = pMaxVolume[rs][p,sc,n]
|
|
985
|
-
print('### Initial volume greater than maximum volume ', rs)
|
|
992
|
+
print('### Initial volume greater than maximum volume ', p, sc, st, rs)
|
|
986
993
|
|
|
987
994
|
# drop load levels with duration 0
|
|
988
995
|
pDuration = pDuration.loc [mTEPES.psn ]
|
|
@@ -1143,11 +1150,11 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1143
1150
|
# computation of the power to heat ratio of the CHP units
|
|
1144
1151
|
# heat ratio of boiler units is fixed to 1.0
|
|
1145
1152
|
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)
|
|
1146
|
-
pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=
|
|
1147
|
-
pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=
|
|
1148
|
-
pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=
|
|
1149
|
-
pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=
|
|
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=
|
|
1153
|
+
pMinPowerHeat = pd.DataFrame([[pMinPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.ch)
|
|
1154
|
+
pMaxPowerHeat = pd.DataFrame([[pMaxPowerElec [ch][p,sc,n]/pPower2HeatRatio[ch] for ch in mTEPES.ch] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.ch)
|
|
1155
|
+
pMinPowerHeat.update(pd.DataFrame([[pRatedMinPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.bo))
|
|
1156
|
+
pMaxPowerHeat.update(pd.DataFrame([[pRatedMaxPowerHeat[bo] for bo in mTEPES.bo] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.bo))
|
|
1157
|
+
pMaxPowerHeat.update(pd.DataFrame([[pMaxCharge[hp][p,sc,n]/pProductionFunctionHeat[hp] for hp in mTEPES.hp] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.hp))
|
|
1151
1158
|
|
|
1152
1159
|
# drop values not par, p, or ps
|
|
1153
1160
|
pReserveMargin = pReserveMargin.loc [mTEPES.par]
|
|
@@ -1206,7 +1213,7 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1206
1213
|
pIndBinRsrvInvest = pIndBinRsrvInvest.loc [mTEPES.rn]
|
|
1207
1214
|
pRsrInvestCost = pRsrInvestCost.loc [mTEPES.rn]
|
|
1208
1215
|
# maximum outflows depending on the downstream hydropower unit
|
|
1209
|
-
pMaxOutflows = pd.DataFrame([[sum(pMaxPowerElec[h][p,sc,n]/pProductionFunctionHydro[h] for h in mTEPES.h if (rs,h) in mTEPES.r2h) for rs in mTEPES.rs] for p,sc,n in mTEPES.psn], index=
|
|
1216
|
+
pMaxOutflows = pd.DataFrame([[sum(pMaxPowerElec[h][p,sc,n]/pProductionFunctionHydro[h] for h in mTEPES.h if (rs,h) in mTEPES.r2h) for rs in mTEPES.rs] for p,sc,n in mTEPES.psn], index=mTEPES.psn, columns=mTEPES.rs)
|
|
1210
1217
|
|
|
1211
1218
|
if pIndHydrogen == 1:
|
|
1212
1219
|
# drop generators not el
|
|
@@ -1569,9 +1576,9 @@ def InputData(DirName, CaseName, mTEPES, pIndLogConsole):
|
|
|
1569
1576
|
mTEPES.pHeatPipeLength[ni,nf,cc] = 1.1 * 6371 * 2 * math.asin(math.sqrt(math.pow(math.sin((mTEPES.pNodeLat[nf]-mTEPES.pNodeLat[ni])*math.pi/180/2),2) + math.cos(mTEPES.pNodeLat[ni]*math.pi/180)*math.cos(mTEPES.pNodeLat[nf]*math.pi/180)*math.pow(math.sin((mTEPES.pNodeLon[nf]-mTEPES.pNodeLon[ni])*math.pi/180/2),2)))
|
|
1570
1577
|
|
|
1571
1578
|
# initialize generation output, unit commitment and line switching
|
|
1572
|
-
pInitialOutput = pd.DataFrame([[0.0]*len(mTEPES.g )]*len(mTEPES.psn), index=
|
|
1573
|
-
pInitialUC = pd.DataFrame([[0 ]*len(mTEPES.g )]*len(mTEPES.psn), index=
|
|
1574
|
-
pInitialSwitch = pd.DataFrame([[0 ]*len(mTEPES.la)]*len(mTEPES.psn), index=
|
|
1579
|
+
pInitialOutput = pd.DataFrame([[0.0]*len(mTEPES.g )]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.g )
|
|
1580
|
+
pInitialUC = pd.DataFrame([[0 ]*len(mTEPES.g )]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.g )
|
|
1581
|
+
pInitialSwitch = pd.DataFrame([[0 ]*len(mTEPES.la)]*len(mTEPES.psn), index=mTEPES.psn, columns=mTEPES.la)
|
|
1575
1582
|
|
|
1576
1583
|
pInitialOutput = filter_rows(pInitialOutput, mTEPES.psng )
|
|
1577
1584
|
pInitialUC = filter_rows(pInitialUC , mTEPES.psng )
|
|
@@ -1601,14 +1608,15 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1601
1608
|
None: Variables are added directly to the mTEPES object.
|
|
1602
1609
|
'''
|
|
1603
1610
|
#%% variables
|
|
1604
|
-
OptModel.vTotalSCost = Var( within=NonNegativeReals,
|
|
1605
|
-
OptModel.vTotalICost = Var( within=NonNegativeReals,
|
|
1611
|
+
OptModel.vTotalSCost = Var( within=NonNegativeReals, doc='total system cost [MEUR]')
|
|
1612
|
+
OptModel.vTotalICost = Var( within=NonNegativeReals, doc='total system investment cost [MEUR]')
|
|
1606
1613
|
OptModel.vTotalFCost = Var(mTEPES.p, within=NonNegativeReals, doc='total system fixed cost [MEUR]')
|
|
1607
1614
|
OptModel.vTotalGCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable generation operation cost [MEUR]')
|
|
1608
1615
|
OptModel.vTotalCCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total variable consumption operation cost [MEUR]')
|
|
1609
1616
|
OptModel.vTotalECost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system emission cost [MEUR]')
|
|
1610
1617
|
OptModel.vTotalRCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total system reliability cost [MEUR]')
|
|
1611
1618
|
OptModel.vTotalNCost = Var(mTEPES.psn, within=NonNegativeReals, doc='total network loss penalty operation cost [MEUR]')
|
|
1619
|
+
OptModel.vTotalEmissionArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission [MtCO2]')
|
|
1612
1620
|
OptModel.vTotalECostArea = Var(mTEPES.psnar, within=NonNegativeReals, doc='total area emission cost [MEUR]')
|
|
1613
1621
|
OptModel.vTotalRESEnergyArea = Var(mTEPES.psnar, within=NonNegativeReals, doc=' RES energy [GWh]')
|
|
1614
1622
|
|
|
@@ -1640,21 +1648,21 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1640
1648
|
[OptModel.vTotalOutputHeat[p,sc,n,ch].setlb(mTEPES.pMinPowerHeat[p,sc,n,ch]) for p,sc,n,ch in mTEPES.psnch]
|
|
1641
1649
|
# only boilers are forced to produce at their minimum heat power. CHPs are not forced to produce at their minimum heat power, they are committed or not to produce electricity
|
|
1642
1650
|
|
|
1643
|
-
if mTEPES.pIndBinGenInvest()
|
|
1651
|
+
if mTEPES.pIndBinGenInvest() != 1:
|
|
1644
1652
|
OptModel.vGenerationInvest = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
|
|
1645
1653
|
OptModel.vGenerationInvPer = Var(mTEPES.peb, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
|
|
1646
1654
|
else:
|
|
1647
1655
|
OptModel.vGenerationInvest = Var(mTEPES.peb, within=Binary, doc='generation investment decision exists in a year {0,1}')
|
|
1648
1656
|
OptModel.vGenerationInvPer = Var(mTEPES.peb, within=Binary, doc='generation investment decision done in a year {0,1}')
|
|
1649
1657
|
|
|
1650
|
-
if mTEPES.pIndBinGenRetire()
|
|
1658
|
+
if mTEPES.pIndBinGenRetire() != 1:
|
|
1651
1659
|
OptModel.vGenerationRetire = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
|
|
1652
1660
|
OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=UnitInterval, doc='generation retirement decision exists in a year [0,1]')
|
|
1653
1661
|
else:
|
|
1654
1662
|
OptModel.vGenerationRetire = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
|
|
1655
1663
|
OptModel.vGenerationRetPer = Var(mTEPES.pgd, within=Binary, doc='generation retirement decision exists in a year {0,1}')
|
|
1656
1664
|
|
|
1657
|
-
if mTEPES.pIndBinNetElecInvest()
|
|
1665
|
+
if mTEPES.pIndBinNetElecInvest() != 1:
|
|
1658
1666
|
OptModel.vNetworkInvest = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
|
|
1659
1667
|
OptModel.vNetworkInvPer = Var(mTEPES.plc, within=UnitInterval, doc='electric network investment decision exists in a year [0,1]')
|
|
1660
1668
|
else:
|
|
@@ -1662,7 +1670,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1662
1670
|
OptModel.vNetworkInvPer = Var(mTEPES.plc, within=Binary, doc='electric network investment decision exists in a year {0,1}')
|
|
1663
1671
|
|
|
1664
1672
|
if mTEPES.pIndHydroTopology == 1:
|
|
1665
|
-
if mTEPES.pIndBinRsrInvest()
|
|
1673
|
+
if mTEPES.pIndBinRsrInvest() != 1:
|
|
1666
1674
|
OptModel.vReservoirInvest = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
|
|
1667
1675
|
OptModel.vReservoirInvPer = Var(mTEPES.prc, within=UnitInterval, doc='reservoir investment decision exists in a year [0,1]')
|
|
1668
1676
|
else:
|
|
@@ -1670,7 +1678,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1670
1678
|
OptModel.vReservoirInvPer = Var(mTEPES.prc, within=Binary, doc='reservoir investment decision exists in a year {0,1}')
|
|
1671
1679
|
|
|
1672
1680
|
if mTEPES.pIndHydrogen == 1:
|
|
1673
|
-
if mTEPES.pIndBinNetH2Invest()
|
|
1681
|
+
if mTEPES.pIndBinNetH2Invest() != 1:
|
|
1674
1682
|
OptModel.vH2PipeInvest = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
|
|
1675
1683
|
OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=UnitInterval, doc='hydrogen network investment decision exists in a year [0,1]')
|
|
1676
1684
|
else:
|
|
@@ -1678,13 +1686,13 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1678
1686
|
OptModel.vH2PipeInvPer = Var(mTEPES.ppc, within=Binary, doc='hydrogen network investment decision exists in a year {0,1}')
|
|
1679
1687
|
|
|
1680
1688
|
if mTEPES.pIndHeat == 1:
|
|
1681
|
-
if mTEPES.pIndBinGenInvest()
|
|
1689
|
+
if mTEPES.pIndBinGenInvest() != 1:
|
|
1682
1690
|
OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision exists in a year [0,1]')
|
|
1683
1691
|
OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=UnitInterval, doc='generation investment decision done in a year [0,1]')
|
|
1684
1692
|
else:
|
|
1685
1693
|
OptModel.vGenerationInvestHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision exists in a year {0,1}')
|
|
1686
1694
|
OptModel.vGenerationInvPerHeat = Var(mTEPES.pbc, within=Binary, doc='generation investment decision done in a year {0,1}')
|
|
1687
|
-
if mTEPES.pIndBinNetHeatInvest()
|
|
1695
|
+
if mTEPES.pIndBinNetHeatInvest() != 1:
|
|
1688
1696
|
OptModel.vHeatPipeInvest = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
|
|
1689
1697
|
OptModel.vHeatPipeInvPer = Var(mTEPES.phc, within=UnitInterval, doc='heat network investment decision exists in a year [0,1]' )
|
|
1690
1698
|
else:
|
|
@@ -1696,7 +1704,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1696
1704
|
OptModel.vStartUp = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='startup of the unit [0,1]')
|
|
1697
1705
|
OptModel.vShutDown = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='shutdown of the unit [0,1]')
|
|
1698
1706
|
OptModel.vMaxCommitment = Var(mTEPES.psnr , within=UnitInterval, initialize=0.0, doc='maximum commitment of the unit [0,1]')
|
|
1699
|
-
OptModel.vStableState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='stable
|
|
1707
|
+
OptModel.vStableState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='stable state of the unit [0,1]')
|
|
1700
1708
|
OptModel.vRampUpState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp up state of the unit [0,1]')
|
|
1701
1709
|
OptModel.vRampDwState = Var(mTEPES.psnnr, within=UnitInterval, initialize=0.0, doc='ramp down state of the unit [0,1]')
|
|
1702
1710
|
else:
|
|
@@ -1704,7 +1712,7 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1704
1712
|
OptModel.vStartUp = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='startup of the unit {0,1}')
|
|
1705
1713
|
OptModel.vShutDown = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='shutdown of the unit {0,1}')
|
|
1706
1714
|
OptModel.vMaxCommitment = Var(mTEPES.psnr , within=Binary, initialize=0 , doc='maximum commitment of the unit {0,1}')
|
|
1707
|
-
OptModel.vStableState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='stable
|
|
1715
|
+
OptModel.vStableState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='stable state of the unit {0,1}')
|
|
1708
1716
|
OptModel.vRampUpState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp up state of the unit {0,1}')
|
|
1709
1717
|
OptModel.vRampDwState = Var(mTEPES.psnnr, within=Binary, initialize=0 , doc='ramp down state of the unit {0,1}')
|
|
1710
1718
|
|
|
@@ -1951,7 +1959,10 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1951
1959
|
nFixedVariables += 2
|
|
1952
1960
|
|
|
1953
1961
|
# total energy inflows per storage
|
|
1954
|
-
|
|
1962
|
+
pTotalEnergyInflows = pd.Series([sum(mTEPES.pEnergyInflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,sc,n,es) in mTEPES.psnes) for es in mTEPES.es], index=mTEPES.es)
|
|
1963
|
+
pTotalMaxCharge = pd.Series([sum(mTEPES.pMaxCharge[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,sc,n,es) in mTEPES.psnes) for es in mTEPES.es], index=mTEPES.es)
|
|
1964
|
+
mTEPES.pTotalEnergyInflows = Param(mTEPES.es, initialize=pTotalEnergyInflows.to_dict(), within=NonNegativeReals, doc='Total energy outflows')
|
|
1965
|
+
mTEPES.pTotalMaxCharge = Param(mTEPES.es, initialize=pTotalMaxCharge.to_dict(), within=NonNegativeReals, doc='Total maximum charge' )
|
|
1955
1966
|
|
|
1956
1967
|
for p,sc,n,es in mTEPES.psnes:
|
|
1957
1968
|
# ESS with no charge capacity
|
|
@@ -1959,12 +1970,13 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1959
1970
|
OptModel.vESSTotalCharge [p,sc,n,es].fix(0.0)
|
|
1960
1971
|
nFixedVariables += 1
|
|
1961
1972
|
# ESS with no charge capacity and no inflows can't produce
|
|
1962
|
-
if
|
|
1973
|
+
if pTotalMaxCharge[es] == 0.0 and pTotalEnergyInflows[es] == 0.0:
|
|
1963
1974
|
OptModel.vTotalOutput [p,sc,n,es].fix(0.0)
|
|
1964
1975
|
OptModel.vOutput2ndBlock [p,sc,n,es].fix(0.0)
|
|
1965
1976
|
OptModel.vReserveUp [p,sc,n,es].fix(0.0)
|
|
1966
1977
|
OptModel.vReserveDown [p,sc,n,es].fix(0.0)
|
|
1967
1978
|
OptModel.vESSSpillage [p,sc,n,es].fix(0.0)
|
|
1979
|
+
OptModel.vESSInventory [p,sc,n,es].fix(mTEPES.pIniInventory[p,sc,n,es])
|
|
1968
1980
|
nFixedVariables += 5
|
|
1969
1981
|
if mTEPES.pMaxCharge2ndBlock[p,sc,n,es] == 0.0:
|
|
1970
1982
|
OptModel.vCharge2ndBlock [p,sc,n,es].fix(0.0)
|
|
@@ -1992,12 +2004,20 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
1992
2004
|
nFixedVariables += 2
|
|
1993
2005
|
return nFixedVariables
|
|
1994
2006
|
nFixedGeneratorCommits = FixGeneratorsCommitment(mTEPES,mTEPES)
|
|
1995
|
-
nFixedVariables
|
|
1996
|
-
# thermal and RES units ordered by increasing variable operation cost, excluding reactive generating units
|
|
2007
|
+
nFixedVariables += nFixedGeneratorCommits
|
|
2008
|
+
# thermal, ESS, and RES units ordered by increasing variable operation cost, excluding reactive generating units
|
|
1997
2009
|
if len(mTEPES.tq):
|
|
1998
2010
|
mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.sq])
|
|
1999
2011
|
else:
|
|
2000
|
-
|
|
2012
|
+
if mTEPES.pIndHydroTopology == 1:
|
|
2013
|
+
mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__)])
|
|
2014
|
+
else:
|
|
2015
|
+
mTEPES.go = Set(initialize=[g for g in sorted(mTEPES.pRatedLinearVarCost, key=mTEPES.pRatedLinearVarCost.__getitem__) if g not in mTEPES.h])
|
|
2016
|
+
|
|
2017
|
+
g2a = defaultdict(list)
|
|
2018
|
+
for ar,g in mTEPES.ar*mTEPES.g:
|
|
2019
|
+
if (ar,g) in mTEPES.a2g:
|
|
2020
|
+
g2a[ar].append(g)
|
|
2001
2021
|
|
|
2002
2022
|
for p,sc,st in mTEPES.ps*mTEPES.stt:
|
|
2003
2023
|
# activate only period, scenario, and load levels to formulate
|
|
@@ -2007,46 +2027,43 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2007
2027
|
mTEPES.n = Set(doc='load levels', initialize=[nn for nn in mTEPES.nn if (p,sc,st,nn) in mTEPES.s2n ])
|
|
2008
2028
|
|
|
2009
2029
|
if len(mTEPES.n):
|
|
2010
|
-
mTEPES.psn1 = Set(initialize=[(p,sc,n) for p,sc,n in mTEPES.ps*mTEPES.n])
|
|
2011
2030
|
# determine the first load level of each stage
|
|
2012
|
-
n1 =
|
|
2013
|
-
# commit the units and their output at the first load level of each stage
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
mTEPES.
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
if (n1,go) in mTEPES.psng:
|
|
2031
|
+
n1 = (p,sc,mTEPES.n.first())
|
|
2032
|
+
# commit the units of each area and their output at the first load level of each stage
|
|
2033
|
+
for ar in mTEPES.ar:
|
|
2034
|
+
pSystemOutput = 0.0
|
|
2035
|
+
for nr in mTEPES.nr:
|
|
2036
|
+
if nr in g2a[ar] and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[nr] == 1:
|
|
2037
|
+
mTEPES.pInitialOutput[n1,nr] = mTEPES.pMaxPowerElec[n1,nr]
|
|
2038
|
+
mTEPES.pInitialUC [n1,nr] = 1
|
|
2039
|
+
pSystemOutput += mTEPES.pInitialOutput[n1,nr]()
|
|
2040
|
+
|
|
2041
|
+
# determine the initial committed units and their output at the first load level of each period, scenario, and stage
|
|
2042
|
+
for go in mTEPES.go:
|
|
2043
|
+
if go in g2a[ar] and pSystemOutput < sum(mTEPES.pDemandElec[n1,nd] for nd in mTEPES.nd if (nd,ar) in mTEPES.ndar) and mTEPES.pMustRun[go] == 0:
|
|
2026
2044
|
if go in mTEPES.re:
|
|
2027
2045
|
mTEPES.pInitialOutput[n1,go] = mTEPES.pMaxPowerElec[n1,go]
|
|
2028
2046
|
else:
|
|
2029
2047
|
mTEPES.pInitialOutput[n1,go] = mTEPES.pMinPowerElec[n1,go]
|
|
2030
|
-
mTEPES.pInitialUC[n1,go]
|
|
2031
|
-
pSystemOutput
|
|
2048
|
+
mTEPES.pInitialUC[n1,go] = 1
|
|
2049
|
+
pSystemOutput += mTEPES.pInitialOutput[n1,go]()
|
|
2032
2050
|
|
|
2033
2051
|
# determine the initial committed lines
|
|
2034
2052
|
for la in mTEPES.la:
|
|
2035
|
-
if
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
mTEPES.pInitialSwitch[n1,la] = 1
|
|
2053
|
+
if la in mTEPES.lc:
|
|
2054
|
+
mTEPES.pInitialSwitch[n1,la] = 0
|
|
2055
|
+
else:
|
|
2056
|
+
mTEPES.pInitialSwitch[n1,la] = 1
|
|
2040
2057
|
|
|
2041
2058
|
# fixing the ESS inventory at the last load level of the stage for every period and scenario if between storage limits
|
|
2042
2059
|
for es in mTEPES.es:
|
|
2043
|
-
if es not in mTEPES.ec
|
|
2060
|
+
if es not in mTEPES.ec:
|
|
2044
2061
|
OptModel.vESSInventory[p,sc,mTEPES.n.last(),es].fix(mTEPES.pIniInventory[p,sc,mTEPES.n.last(),es])
|
|
2045
2062
|
|
|
2046
2063
|
if mTEPES.pIndHydroTopology == 1:
|
|
2047
2064
|
# fixing the reservoir volume at the last load level of the stage for every period and scenario if between storage limits
|
|
2048
2065
|
for rs in mTEPES.rs:
|
|
2049
|
-
if rs not in mTEPES.rn
|
|
2066
|
+
if rs not in mTEPES.rn:
|
|
2050
2067
|
OptModel.vReservoirVolume[p,sc,mTEPES.n.last(),rs].fix(mTEPES.pIniVolume[p,sc,mTEPES.n.last(),rs]())
|
|
2051
2068
|
|
|
2052
2069
|
# activate all the periods, scenarios, and load levels again
|
|
@@ -2406,18 +2423,25 @@ def SettingUpVariables(OptModel, mTEPES):
|
|
|
2406
2423
|
Returns:
|
|
2407
2424
|
None: Raises a ValueError when an infeasibility is found.
|
|
2408
2425
|
'''
|
|
2426
|
+
|
|
2427
|
+
if len(mTEPES.p ) == 0:
|
|
2428
|
+
raise ValueError('### No active periods in the case study' )
|
|
2429
|
+
if len(mTEPES.sc) == 0:
|
|
2430
|
+
raise ValueError('### No active scenarios in the case study')
|
|
2431
|
+
if len(mTEPES.st) == 0:
|
|
2432
|
+
raise ValueError('### No active stages in the case study' )
|
|
2433
|
+
|
|
2409
2434
|
# detecting infeasibility: sum of scenario probabilities must be 1 in each period
|
|
2410
2435
|
# for p in mTEPES.p:
|
|
2411
2436
|
# if abs(sum(mTEPES.pScenProb[p,sc] for sc in mTEPES.sc)-1.0) > 1e-6:
|
|
2412
2437
|
# raise ValueError('### Sum of scenario probabilities different from 1 in period ', p)
|
|
2413
2438
|
|
|
2414
|
-
|
|
2415
2439
|
for es in mTEPES.es:
|
|
2416
2440
|
# detecting infeasibility: total min ESS output greater than total inflows, total max ESS charge lower than total outflows
|
|
2417
2441
|
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:
|
|
2418
|
-
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))
|
|
2442
|
+
raise ValueError('### Total minimum output greater than total inflows for ESS unit ', es, ' by ', sum(mTEPES.pMinPowerElec[p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyInflows [p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
|
|
2419
2443
|
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:
|
|
2420
|
-
raise ValueError('### Total maximum charge lower than total outflows for ESS unit ',
|
|
2444
|
+
raise ValueError('### Total maximum charge lower than total outflows for ESS unit ', es, ' by ', sum(mTEPES.pMaxCharge [p,sc,n,es] for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes) - sum(mTEPES.pEnergyOutflows[p,sc,n,es]() for p,sc,n in mTEPES.psn if (p,es) in mTEPES.pes), ' GWh')
|
|
2421
2445
|
|
|
2422
2446
|
# detect inventory infeasibility
|
|
2423
2447
|
for p,sc,n,es in mTEPES.ps*mTEPES.nesc:
|
openTEPES/openTEPES_Main.py
CHANGED
|
@@ -660,12 +660,12 @@
|
|
|
660
660
|
# For more information on this, and how to apply and follow the GNU AGPL, see
|
|
661
661
|
# <https://www.gnu.org/licenses/>.
|
|
662
662
|
|
|
663
|
-
# Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) -
|
|
663
|
+
# Open Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - March 18, 2025
|
|
664
664
|
# simplicity and transparency in power systems planning
|
|
665
665
|
|
|
666
666
|
# Developed by
|
|
667
667
|
|
|
668
|
-
# Andres Ramos, Erik Alvarez, Sara Lumbreras
|
|
668
|
+
# Andres Ramos, Erik Alvarez, Francisco Labora, Sara Lumbreras
|
|
669
669
|
# Instituto de Investigacion Tecnologica
|
|
670
670
|
# Escuela Tecnica Superior de Ingenieria - ICAI
|
|
671
671
|
# UNIVERSIDAD PONTIFICIA COMILLAS
|
|
@@ -673,6 +673,7 @@
|
|
|
673
673
|
# 28015 Madrid, Spain
|
|
674
674
|
# Andres.Ramos@comillas.edu
|
|
675
675
|
# Erik.Alvarez@comillas.edu
|
|
676
|
+
# Francisco.Labora@comillas.edu
|
|
676
677
|
# Sara.Lumbreras@comillas.edu
|
|
677
678
|
# https://pascua.iit.comillas.edu/aramos/Ramos_CV.htm
|
|
678
679
|
|
|
@@ -685,7 +686,7 @@ import time
|
|
|
685
686
|
# import pkg_resources
|
|
686
687
|
from .openTEPES import openTEPES_run
|
|
687
688
|
|
|
688
|
-
print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.
|
|
689
|
+
print('\033[1;32mOpen Generation, Storage, and Transmission Operation and Expansion Planning Model with RES and ESS (openTEPES) - Version 4.18.3 - March 18, 2025\033[0m')
|
|
689
690
|
print('\033[34m#### Academic research license - for non-commercial use only ####\033[0m \n')
|
|
690
691
|
|
|
691
692
|
parser = argparse.ArgumentParser(description='Introducing main parameters...')
|