Workflow for a transformation pathway of a single node energy system with perfect foresight¶
In this application of the ETHOS.FINE framework, a transformation pathway of a energy system is modeled and optimized.
All classes which are available to the user are utilized and examples of the selection of different parameters within these classes are given.
The workflow is structures as follows:
- Required packages are imported and the input data path is set
- An energy system model instance is created
- Commodity sources are added to the energy system model
- Commodity conversion components are added to the energy system model
- Commodity storages are added to the energy system model
- Commodity sinks are added to the energy system model
- The energy system model is optimized
- Selected optimization results are presented
1. Import required packages and set input data path¶
The ETHOS.FINE framework is imported which provides the required classes and functions for modeling the energy system.
import fine as fn
from getData import getData
from pathlib import Path
cwd = Path.cwd()
data = getData()
2. Create an energy system model instance¶
The structure of the energy system model is given by the considered locations, commodities, the number of time steps as well as the hours per time step.
The commodities are specified by a unit (i.e. 'GW_electric', 'GW_H2lowerHeatingValue', 'Mio. t CO2/h') which can be given as an energy or mass unit per hour. Furthermore, the cost unit and length unit are specified.
locations = {"GermanyRegion"}
commodityUnitDict = {"electricity": r"GW$_{el}$", "hydrogen": r"GW$_{H_{2},LHV}$"}
commodities = {"electricity", "hydrogen"}
numberOfTimeSteps = 8760
hoursPerTimeStep = 1
2.1 define Transformation Pathway parameters¶
Transformation Pathway Analyses can be run by setting a number of investment periods larger than 1, which is the default value and results in a single year optimization.
numberOfInvestmentPeriods = 3
startYear = 2020
interval = 5
esM = fn.EnergySystemModel(
locations=locations,
commodities=commodities,
numberOfInvestmentPeriods=numberOfInvestmentPeriods,
startYear=startYear,
investmentPeriodInterval=interval,
numberOfTimeSteps=8760,
commodityUnitsDict=commodityUnitDict,
hoursPerTimeStep=1,
costUnit="1e9 Euro",
lengthUnit="km",
verboseLogLevel=0,
)
3. Add commodity sources to the energy system model¶
3.1. Electricity sources¶
Wind onshore¶
change weather conditions for the different investment periods
operationRateMax = {}
operationRateMax[2020] = 1.2 * data["Wind (onshore), operationRateMax"]
operationRateMax[2025] = 0.7 * data["Wind (onshore), operationRateMax"]
operationRateMax[2030] = 1 * data["Wind (onshore), operationRateMax"]
define existing stock for wind onshore turbines
stockWindCommissioning = {
2010: 5,
2015: 10,
}
define invest and opex per capacity for wind onshore turbines
investPerCapacityWind = {2010: 1.5, 2015: 1.25, 2020: 1.1, 2025: 1, 2030: 0.95}
opexPerCapacityWind = {
2010: 1.5 * 0.02,
2015: 1.25 * 0.02,
2020: 1.1 * 0.02,
2025: 1 * 0.02,
2030: 0.95 * 0.02,
}
add wind onshore source to esM
esM.add(
fn.Source(
esM=esM,
name="Wind (onshore)",
commodity="electricity",
hasCapacityVariable=True,
operationRateMax=data["Wind (onshore), operationRateMax"],
capacityMax=data["Wind (onshore), capacityMax"],
investPerCapacity=investPerCapacityWind,
opexPerCapacity=opexPerCapacityWind,
interestRate=0.08,
economicLifetime=20,
stockCommissioning=stockWindCommissioning,
)
)
Full load hours:
data["Wind (onshore), operationRateMax"].sum()
np.float64(2300.4069071646272)
4. Add conversion components to the energy system model¶
New combined cycly gas turbines for hydrogen¶
esM.add(
fn.Conversion(
esM=esM,
name="New CCGT plants (hydrogen)",
physicalUnit=r"GW$_{el}$",
commodityConversionFactors={"electricity": 1, "hydrogen": -1 / 0.6},
hasCapacityVariable=True,
investPerCapacity=0.7,
opexPerCapacity={2020: 0.021, 2025: 0.018, 2030: 0.025},
interestRate=0.08,
economicLifetime=30,
)
)
Electrolyzers¶
add component with constant invest and opex per capacity
esM.add(
fn.Conversion(
esM=esM,
name="Electroylzers",
physicalUnit=r"GW$_{el}$",
commodityConversionFactors={"electricity": -1, "hydrogen": 0.7},
hasCapacityVariable=True,
investPerCapacity=0.5,
opexPerCapacity=0.5 * 0.025,
interestRate=0.08,
economicLifetime=10,
)
)
5. Add commodity storages to the energy system model¶
5.1. Electricity storage¶
Lithium ion batteries¶
The self discharge of a lithium ion battery is here described as 3% per month. The self discharge per hours is obtained using the equation (1-$\text{selfDischarge}_\text{hour})^{30*24\text{h}} = 1-\text{selfDischarge}_\text{month}$.
esM.add(
fn.Storage(
esM=esM,
name="Li-ion batteries",
commodity="electricity",
hasCapacityVariable=True,
chargeEfficiency=0.95,
cyclicLifetime=10000,
dischargeEfficiency=0.95,
selfDischarge=1 - (1 - 0.03) ** (1 / (30 * 24)),
chargeRate=1,
dischargeRate=1,
doPreciseTsaModeling=False,
investPerCapacity=0.151,
opexPerCapacity=0.002,
interestRate=0.08,
economicLifetime=20,
)
)
5.2. Hydrogen storage¶
Hydrogen filled salt caverns¶
The maximum capacity is here obtained by: dividing the given capacity (which is given for methane) by the lower heating value of methane and then multiplying it with the lower heating value of hydrogen.
esM.add(
fn.Storage(
esM=esM,
name="Salt caverns (hydrogen)",
commodity="hydrogen",
hasCapacityVariable=True,
capacityVariableDomain="continuous",
capacityPerPlantUnit=133,
chargeRate=1 / 470.37,
dischargeRate=1 / 470.37,
sharedPotentialID="Existing salt caverns",
stateOfChargeMin=0.33,
stateOfChargeMax=1,
capacityMax=data["Salt caverns (hydrogen), capacityMax"],
investPerCapacity={2020: 0.00011, 2025: 0.00009, 2030: 0.00009},
opexPerCapacity=0.00057,
interestRate=0.08,
economicLifetime=30,
)
)
7. Add commodity sinks to the energy system model¶
7.1. Electricity sinks¶
Electricity demand¶
vary the demand with the years - increasing demand by 30% per year
electricityDemand = {}
electricityDemand[2020] = (1 + 0 * 0.3) * data["Electricity demand, operationRateFix"]
electricityDemand[2025] = (1 + 1 * 0.3) * data["Electricity demand, operationRateFix"]
electricityDemand[2030] = (1 + 2 * 0.3) * data["Electricity demand, operationRateFix"]
esM.add(
fn.Sink(
esM=esM,
name="Electricity demand",
commodity="electricity",
hasCapacityVariable=False,
operationRateFix=electricityDemand,
)
)
7.2. Hydrogen sinks¶
Fuel cell electric vehicle (FCEV) demand¶
FCEV_penetration = 0.5
# vary the demand with the years - increasing demand by 25% per year
hydrogendDemand = {}
hydrogendDemand[2020] = (
(1 + 0 * 0.25) * data["Hydrogen demand, operationRateFix"] * FCEV_penetration
)
hydrogendDemand[2025] = (
(1 + 0 * 0.25) * data["Hydrogen demand, operationRateFix"] * FCEV_penetration
)
hydrogendDemand[2030] = (
(1 + 0 * 0.25) * data["Hydrogen demand, operationRateFix"] * FCEV_penetration
)
esM.add(
fn.Sink(
esM=esM,
name="Hydrogen demand",
commodity="hydrogen",
hasCapacityVariable=False,
operationRateFix=hydrogendDemand,
)
)
8. Optimize energy system model¶
All components are now added to the model and the model can be optimized. If the computational complexity of the optimization should be reduced, the time series data of the specified components can be clustered before the optimization and the parameter timeSeriesAggregation is set to True in the optimize call.
esM.aggregateTemporally(numberOfTypicalPeriods=20)
Clustering time series data with 20 typical periods and 24 time steps per period further clustered to 12 segments per period... (8.1008 sec)
esM.optimize(
timeSeriesAggregation=True, solver=fn.utils.ImplementedSolvers.STANDARD_SOLVER.value
)
Time series aggregation specifications:
Number of typical periods:20, number of time steps per period:24, number of segments per period:12
Declaring sets, variables and constraints for SourceSinkModel
declaring sets...
declaring variables...
declaring constraints...
(0.3366 sec)
Declaring sets, variables and constraints for ConversionModel
declaring sets...
declaring variables...
declaring constraints...
(0.1259 sec)
Declaring sets, variables and constraints for StorageModel
declaring sets...
declaring variables...
declaring constraints...
(0.7133 sec)
Declaring shared potential constraint...
(0.0000 sec)
Declaring linked component quantity constraint...
(0.0000 sec)
Declaring commodity balances...
(0.1789 sec)
(0.0000 sec)
Declaring objective function...
(0.5640 sec)
GLPSOL--GLPK LP/MIP Solver 5.0
Parameter(s) specified in the command line:
--write C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmpz30r9rzb.glpk.raw --wglp
C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmpb98o6n66.glpk.glp --cpxlp C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmp81zy3twc.pyomo.lp
Reading problem data from 'C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmp81zy3twc.pyomo.lp'...
19619 rows, 10538 columns, 54796 non-zeros
124217 lines were read
Writing problem data to 'C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmpb98o6n66.glpk.glp'...
100529 lines were written
GLPK Simplex Optimizer 5.0
19619 rows, 10538 columns, 54796 non-zeros
Preprocessing...
19341 rows, 8933 columns, 52675 non-zeros
Scaling...
A: min|aij| = 6.230e-04 max|aij| = 5.000e+02 ratio = 8.026e+05
GM: min|aij| = 3.162e-01 max|aij| = 3.162e+00 ratio = 1.000e+01
EQ: min|aij| = 1.000e-01 max|aij| = 1.000e+00 ratio = 1.000e+01
Constructing initial basis...
Size of triangular part is 16425
0: obj = 1.284448734e+01 inf = 3.721e+03 (1361)
Perturbing LP to avoid stalling [1783]...
4027: obj = 1.297388170e+02 inf = 1.000e-09 (0) 34
Removing LP perturbation [6561]...
* 6561: obj = 9.990712843e+01 inf = 2.627e-12 (0) 21
OPTIMAL LP SOLUTION FOUND
Time used: 3.0 secs
Memory used: 23.4 Mb (24522007 bytes)
Writing basic solution to 'C:\Users\T58C8~1.GRO\AppData\Local\Temp\tmpz30r9rzb.glpk.raw'...
30166 lines were written
Status: ok
Termination condition: optimal
Statistics:
Branch and bound:
Number of bounded subproblems: 0
Number of created subproblems: 0
Error rc: 0
Time: 3.4059207439422607
Name: unknown
Lower bound: 99.9071284328827
Upper bound: 99.9071284328827
Number of objectives: 1
Number of constraints: 19619
Number of variables: 10538
Number of nonzeros: 54796
Sense: minimize
Solve time: 4.345061540603638 sec.
Processing optimization output...
for SourceSinkModel ...(1.2897sec)
for ConversionModel ...(0.7717sec)
C:\Users\t.gross\Documents\Programming\Jugit\fine\fine\storage.py:1984: UserWarning: Charge and discharge at the same time for component Salt caverns (hydrogen) warnings.warn( C:\Users\t.gross\Documents\Programming\Jugit\fine\fine\storage.py:1984: UserWarning: Charge and discharge at the same time for component Salt caverns (hydrogen) warnings.warn( C:\Users\t.gross\Documents\Programming\Jugit\fine\fine\storage.py:1984: UserWarning: Charge and discharge at the same time for component Salt caverns (hydrogen) warnings.warn(
for StorageModel ... (3.1805sec) (5.2778 sec)
9. Selected results output¶
Sources and Sink¶
Show optimization summary
for year in [2020, 2025, 2030]:
print(f"\n Results of SourceSinkModel for year {year}")
print(esM.getOptimizationSummary("SourceSinkModel", outputLevel=2, ip=year))
Results of SourceSinkModel for year 2020
GermanyRegion
Component Property Unit
Electricity demand operation [GW$_{el}$*h/a] 30957.888055
[GW$_{el}$*h] 30957.888055
Hydrogen demand operation [GW$_{H_{2},LHV}$*h/a] 4765.074877
[GW$_{H_{2},LHV}$*h] 4765.074877
Wind (onshore) NPVcontribution [1e9 Euro] 26.640227
TAC [1e9 Euro/a] 6.177979
capacity [GW$_{el}$] 42.909625
capexCap [1e9 Euro/a] 5.163967
commissioning [GW$_{el}$] 27.909625
invest [1e9 Euro] 30.700587
operation [GW$_{el}$*h/a] 43849.916337
[GW$_{el}$*h] 43849.916337
opexCap [1e9 Euro/a] 1.014012
Results of SourceSinkModel for year 2025
GermanyRegion
Component Property Unit
Electricity demand operation [GW$_{el}$*h/a] 40245.254471
[GW$_{el}$*h] 40245.254471
Hydrogen demand operation [GW$_{H_{2},LHV}$*h/a] 4765.074877
[GW$_{H_{2},LHV}$*h] 4765.074877
Wind (onshore) NPVcontribution [1e9 Euro] 23.587859
TAC [1e9 Euro/a] 8.037404
capacity [GW$_{el}$] 58.1693
capexCap [1e9 Euro/a] 6.718198
commissioning [GW$_{el}$] 15.259675
invest [1e9 Euro] 15.259675
operation [GW$_{el}$*h/a] 53538.163728
[GW$_{el}$*h] 53538.163728
opexCap [1e9 Euro/a] 1.319205
Results of SourceSinkModel for year 2030
GermanyRegion
Component Property Unit
Electricity demand operation [GW$_{el}$*h/a] 49532.620887
[GW$_{el}$*h] 49532.620887
Hydrogen demand operation [GW$_{H_{2},LHV}$*h/a] 4765.074877
[GW$_{H_{2},LHV}$*h] 4765.074877
Wind (onshore) NPVcontribution [1e9 Euro] 15.384202
TAC [1e9 Euro/a] 7.70231
capacity [GW$_{el}$] 58.1693
capexCap [1e9 Euro/a] 6.438105
commissioning [GW$_{el}$] 5.0
decommissioning [GW$_{el}$] 5.0
invest [1e9 Euro] 4.75
operation [GW$_{el}$*h/a] 62658.965089
[GW$_{el}$*h] 62658.965089
opexCap [1e9 Euro/a] 1.264205
Plot operation time series (either one or two dimensional) for different years
Electricity demand operation for Investment Period 2020
fig, ax = fn.plotOperation(esM, "Electricity demand", "GermanyRegion", ip=2020)
Electricity demand operation for Investment Period 2030
fig, ax = fn.plotOperation(esM, "Electricity demand", "GermanyRegion", ip=2030)
Operation color map for Electricity demand in Investment Period 2020
fig, ax = fn.plotOperationColorMap(esM, "Electricity demand", "GermanyRegion", ip=2020)
Operation color map for Electricity demand in Investment Period 2030
fig, ax = fn.plotOperationColorMap(esM, "Electricity demand", "GermanyRegion", ip=2030)
Conversion¶
Show optimization summary
for year in [2020, 2025, 2030]:
print(f"\n Results of ConversionMpdel for year {year}")
esM.getOptimizationSummary("ConversionModel", outputLevel=2, ip=year)
Results of ConversionMpdel for year 2020 Results of ConversionMpdel for year 2025 Results of ConversionMpdel for year 2030
Operation color map for New CCGT plants (hydrogen) in Investment Period 2020
fig, ax = fn.plotOperationColorMap(
esM, "New CCGT plants (hydrogen)", "GermanyRegion", ip=2020
)
Operation color map for New CCGT plants (hydrogen) in Investment Period 2030
fig, ax = fn.plotOperationColorMap(
esM, "New CCGT plants (hydrogen)", "GermanyRegion", ip=2030
)
Storage¶
Show optimization summary
for year in [2020, 2025, 2030]:
print(f"\n Results of StorageModel for year {year}")
print(esM.getOptimizationSummary("StorageModel", outputLevel=2, ip=year))
Results of StorageModel for year 2020
GermanyRegion
Component Property Unit
Li-ion batteries NPVcontribution [1e9 Euro] 3.656265
TAC [1e9 Euro/a] 0.847903
capacity [GW$_{el}$*h] 48.787021
capexCap [1e9 Euro/a] 0.750329
commissioning [GW$_{el}$*h] 48.787021
invest [1e9 Euro] 7.36684
operationCharge [GW$_{el}$*h/a] 5920.124728
[GW$_{el}$*h] 5920.124728
operationDischarge [GW$_{el}$*h/a] 5333.377587
[GW$_{el}$*h] 5333.377587
opexCap [1e9 Euro/a] 0.097574
Salt caverns (hydrogen) NPVcontribution [1e9 Euro] 3.088097
TAC [1e9 Euro/a] 0.716143
capacity [GW$_{H_{2},LHV}$*h] 1235.216115
capexCap [1e9 Euro/a] 0.012069
commissioning [GW$_{H_{2},LHV}$*h] 1235.216115
invest [1e9 Euro] 0.135874
operationCharge [GW$_{H_{2},LHV}$*h/a] 9615.522414
[GW$_{H_{2},LHV}$*h] 9615.522414
operationDischarge [GW$_{H_{2},LHV}$*h/a] 9615.522414
[GW$_{H_{2},LHV}$*h] 9615.522414
opexCap [1e9 Euro/a] 0.704073
Results of StorageModel for year 2025
GermanyRegion
Component Property Unit
Li-ion batteries NPVcontribution [1e9 Euro] 4.155994
TAC [1e9 Euro/a] 1.416127
capacity [GW$_{el}$*h] 81.481742
capexCap [1e9 Euro/a] 1.253163
commissioning [GW$_{el}$*h] 32.694722
invest [1e9 Euro] 4.936903
operationCharge [GW$_{el}$*h/a] 7674.71308
[GW$_{el}$*h] 7674.71308
operationDischarge [GW$_{el}$*h/a] 6911.809083
[GW$_{el}$*h] 6911.809083
opexCap [1e9 Euro/a] 0.162963
Salt caverns (hydrogen) NPVcontribution [1e9 Euro] 2.101707
TAC [1e9 Euro/a] 0.716143
capacity [GW$_{H_{2},LHV}$*h] 1235.216115
capexCap [1e9 Euro/a] 0.012069
operationCharge [GW$_{H_{2},LHV}$*h/a] 9489.131984
[GW$_{H_{2},LHV}$*h] 9489.131984
operationDischarge [GW$_{H_{2},LHV}$*h/a] 9489.131984
[GW$_{H_{2},LHV}$*h] 9489.131984
opexCap [1e9 Euro/a] 0.704073
Results of StorageModel for year 2030
GermanyRegion
Component Property Unit
Li-ion batteries NPVcontribution [1e9 Euro] 14.774916
TAC [1e9 Euro/a] 7.397263
capacity [GW$_{el}$*h] 425.627003
capexCap [1e9 Euro/a] 6.546009
commissioning [GW$_{el}$*h] 344.145261
invest [1e9 Euro] 51.965934
operationCharge [GW$_{el}$*h/a] 10798.672566
[GW$_{el}$*h] 10798.672566
operationDischarge [GW$_{el}$*h/a] 9680.156781
[GW$_{el}$*h] 9680.156781
opexCap [1e9 Euro/a] 0.851254
Salt caverns (hydrogen) NPVcontribution [1e9 Euro] 1.430387
TAC [1e9 Euro/a] 0.716143
capacity [GW$_{H_{2},LHV}$*h] 1235.216115
capexCap [1e9 Euro/a] 0.012069
operationCharge [GW$_{H_{2},LHV}$*h/a] 10716.522664
[GW$_{H_{2},LHV}$*h] 10716.522664
operationDischarge [GW$_{H_{2},LHV}$*h/a] 10716.522664
[GW$_{H_{2},LHV}$*h] 10716.522664
opexCap [1e9 Euro/a] 0.704073
Operation color map for Li-ion batteries in Investment Period 2020
fig, ax = fn.plotOperationColorMap(
esM,
"Li-ion batteries",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2020,
)
Operation color map for Li-ion batteries in Investment Period 2025
fig, ax = fn.plotOperationColorMap(
esM,
"Li-ion batteries",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2025,
)
Operation color map for Li-ion batteries in Investment Period 2030
fig, ax = fn.plotOperationColorMap(
esM,
"Li-ion batteries",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2030,
)
Operation color map for Salt caverns (hydrogen) in Investment Period 2020
fig, ax = fn.plotOperationColorMap(
esM,
"Salt caverns (hydrogen)",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2020,
)
Operation color map for Salt caverns (hydrogen) in Investment Period 2025
fig, ax = fn.plotOperationColorMap(
esM,
"Salt caverns (hydrogen)",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2025,
)
Operation color map for Salt caverns (hydrogen) in Investment Period 2030
fig, ax = fn.plotOperationColorMap(
esM,
"Salt caverns (hydrogen)",
"GermanyRegion",
variableName="stateOfChargeOperationVariablesOptimum",
ip=2030,
)