def test_init_full_load_hours(minimal_test_esM): import FINE as fn import pandas as pd # load minimal test system esM = minimal_test_esM # add a component with yearly minimal load hours esM.add( fn.Conversion( esM=esM, name="Electrolyzers_minFLH", physicalUnit=r"kW$_{el}$", commodityConversionFactors={"electricity": -1, "hydrogen": 0.7}, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, yearlyFullLoadHoursMin=5000, ) ) full_load_hours_min = esM.getComponent( "Electrolyzers_minFLH" ).yearlyFullLoadHoursMin full_load_hours_max = esM.getComponent( "Electrolyzers_minFLH" ).yearlyFullLoadHoursMax assert isinstance(full_load_hours_min, pd.Series) assert full_load_hours_max is None
def test_conversion_factors_as_series(): """ Input as pandas.Series for one location. """ esM = create_core_esm() esM.add( fn.Conversion( esM=esM, name="Electrolyzers_VarConvFac", physicalUnit=r"kW$_{el}$", commodityConversionFactors=pd.Series( [0.7, -1], index=["hydrogen", "electricity"] ), # Here we add a Series of time invariant conversion factors. hasCapacityVariable=True, investPerCapacity=1000, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, capacityMax=1000, economicLifetime=10, locationalEligibility=pd.Series([1], ["ElectrolyzerLocation"]), ) ) # optimize esM.optimize(timeSeriesAggregation=False, solver="glpk")
def test_QPinvest(): numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel(locations={'location1'}, commodities={'electricity', 'hydrogen'}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={'electricity': r'kW$_{el}$', 'hydrogen': r'kW$_{H_{2},LHV}$'}, hoursPerTimeStep=hoursPerTimeStep, costUnit='1 Euro', lengthUnit='km', verboseLogLevel=2) # time step length [h] timeStepLength = numberOfTimeSteps * hoursPerTimeStep ### Buy electricity at the electricity market costs = pd.Series([0.5,0.4,0.2,0.5], index=[0,1,2,3]) esM.add(fn.Source(esM=esM, name='Electricity market', commodity='electricity', hasCapacityVariable=False, commodityCostTimeSeries = costs, )) # euro/kWh ### Electrolyzers esM.add(fn.Conversion(esM=esM, name='Electrolyzer', physicalUnit=r'kW$_{el}$', commodityConversionFactors={'electricity':-1, 'hydrogen':0.7}, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500*0.025, interestRate=0.08, economicLifetime=10, QPcostScale=0.1, capacityMin=0, capacityMax=10)) ### Industry site demand = pd.Series([10000., 10000., 10000., 10000.], index = [0,1,2,3]) esM.add(fn.Sink(esM=esM, name='Industry site', commodity='hydrogen', hasCapacityVariable=False, operationRateFix = demand, )) ### Optimize (just executed if gurobi is installed) flag=True try: esM.optimize(timeSeriesAggregation=False, solver = 'gurobi') except: flag=False if flag: invest = round(esM.getOptimizationSummary('ConversionModel').loc['Electrolyzer']. \ loc['invest']['location1'].values.astype(float)[0],3) assert invest == 3148.179
def test_linkedQuantityID(minimal_test_esM): """ """ esM = minimal_test_esM # get components electrolyzer = esM.getComponent("Electrolyzers") market = esM.getComponent("Electricity market") # create dummy component esM.add( fn.Conversion( esM=esM, name="Dummy", physicalUnit=r"kW$_{el}$", commodityConversionFactors={"electricity": -1, "electricity": 1}, hasCapacityVariable=True, capacityPerPlantUnit=1.0, opexPerCapacity=1.0, linkedQuantityID="test", ) ) # make electrolyzer sizing discrete electrolyzer.capacityPerPlantUnit = 1 electrolyzer.linkedQuantityID = "test" electrolyzer.opexPerCapacity = pd.Series(1, index=esM.locations) # optimize esM.optimize(timeSeriesAggregation=False, solver="glpk") assert ( esM.getOptimizationSummary("ConversionModel") .loc["Electrolyzers"] .loc["opexCap"]["ElectrolyzerLocation"] .values.astype(int)[0] == esM.getOptimizationSummary("ConversionModel") .loc["Dummy"] .loc["opexCap"]["ElectrolyzerLocation"] .values.astype(int)[0] )
def test_conversionPartLoad(): # Set up energy system model instance locations = {'GlassProductionSite'} commodities = {'electricity', 'heat', 'hydrogen', 'O2', 'CO2','rawMaterial'} commodityUnitDict = {'electricity': r'kW$_{el}$', 'heat':r'kW$_{heat}$}', 'hydrogen':r'kW$_{H2}$', 'O2': r'kg$_{O_{2}}$/h', 'CO2': r'kg$_{CO_{2}}$/h', 'rawMaterial': r'kg$_{R}}$/h'} numberOfTimeSteps=80 hoursPerTimeStep=0.25 esM = fn.EnergySystemModel(locations=locations, commodities=commodities, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=hoursPerTimeStep, costUnit='1 Euro', lengthUnit='km', verboseLogLevel=0) ### Sources ### # Electricity from grid esM.add(fn.Source(esM=esM, name='ElectricityGrid', commodity='electricity', hasCapacityVariable=False, commodityCost=0.070)) # Oxygen source from trailer esM.add(fn.Source(esM=esM, name='oxygenSource', commodity='O2', hasCapacityVariable=False, commodityCost=0.070)) # Raw material for glass furnace esM.add(fn.Source(esM=esM, name='rawMaterialSource', commodity='rawMaterial', hasCapacityVariable=False, commodityCost=0.20)) ### Conversion ### # PEM Electrolyzer # Get partLoadData from EC Campus Mainz Utilization = [0.0208023774145616,0.0222882615156017,0.0222882615156017,0.0222882615156017,0.025260029717682, 0.025260029717682,0.0267459138187221,0.0282317979197622,0.0297176820208024,0.0312035661218424, 0.0326894502228826,0.0341753343239227,0.0356612184249628,0.0371471025260029,0.038632986627043, 0.0416047548291233,0.0430906389301634,0.0445765230312035,0.0475482912332838,0.0430906389301634, 0.0490341753343239,0.050520059435364,0.0520059435364041,0.0549777117384844,0.0579494799405646, 0.0609212481426448,0.0638930163447251,0.0683506686478455,0.0713224368499256,0.075780089153046, 0.0787518573551263,0.0832095096582466,0.087667161961367,0.0921248142644873,0.0980683506686478, 0.104011887072808,0.108469539375928,0.114413075780089,0.120356612184249,0.12778603268945, 0.13521545319465,0.142644873699851,0.151560178306092,0.157503714710252,0.166419019316493, 0.173848439821693,0.181277860326894,0.190193164933135,0.196136701337295,0.202080237741456, 0.209509658246656,0.216939078751857,0.224368499257057,0.233283803863298,0.240713224368499, 0.246656760772659,0.25408618127786,0.26151560178306,0.270430906389301,0.276374442793462, 0.283803863298662,0.292719167904903,0.298662704309063,0.306092124814264,0.313521545319465, 0.319465081723625,0.328380386329866,0.335809806835066,0.343239227340267,0.349182763744427, 0.356612184249628,0.364041604754829,0.371471025260029,0.38038632986627,0.389301634472511, 0.398216939078751,0.407132243684992,0.416047548291233,0.423476968796433,0.430906389301634, 0.439821693907875,0.448736998514115,0.457652303120356,0.465081723625557,0.472511144130757, 0.481426448736998,0.490341753343239,0.497771173848439,0.50668647845468,0.514115898959881, 0.523031203566121,0.531946508172362,0.540861812778603,0.551263001485884,0.560178306092124, 0.567607726597325,0.576523031203566,0.585438335809806,0.595839524517087,0.604754829123328, 0.613670133729569,0.619613670133729,0.62852897473997,0.638930163447251,0.646359583952451, 0.656760772659732,0.664190193164933,0.673105497771173,0.684992570579494,0.692421991084695, 0.699851411589896,0.710252600297176,0.717682020802377,0.726597325408618,0.735512630014858, 0.744427934621099,0.75334323922734,0.762258543833581,0.769687964338781,0.780089153046062, 0.786032689450223,0.793462109955423,0.802377414561664,0.811292719167904,0.820208023774145, 0.829123328380386,0.838038632986627,0.846953937592867,0.858841010401188,0.867756315007429, 0.87667161961367,0.88707280832095,0.895988112927191,0.904903417533432,0.913818722139673, 0.924219910846954,0.933135215453194,0.942050520059435,0.947994056463595,0.958395245170876, 0.965824665676077,0.974739970282318,0.985141158989598,0.994056463595839] Efficiency = [0.03449362655834178,0.05655553999159559,0.04920156884717763,0.07616612971004356,0.09332539571368476, 0.115387309146939,0.13254657515058135,0.1497058411542229,0.1693164308726712,0.1864756968763127, 0.2036349628799551,0.2256968763132085,0.2404048186020449,0.25511276089088053,0.27472335060932884, 0.2918826166129703,0.30904188261661275,0.33110379604986695,0.34581173833870255,0.3212985011906424, 0.3629710043423449,0.38013027034598645,0.3923868889200161,0.4095461549236585,0.42425409721249496, 0.4365107157865246,0.44876733436055427,0.45857262921977887,0.4683779240790026,0.4830858663678382, 0.4953424849418686,0.5075991035158983,0.517404398375122,0.5247583695195407,0.5321123406639585, 0.5419176355231823,0.5517229303824059,0.5639795489564365,0.5713335201008543,0.5811388149600779, 0.5884927861044958,0.5933954335341085,0.5982980809637204,0.6007494046785262,0.6007494046785262, 0.6007494046785262,0.5982980809637204,0.5982980809637204,0.5958467572489144,0.5958467572489144, 0.5958467572489144,0.5933954335341085,0.5933954335341085,0.5933954335341085,0.5909441098193018, 0.5909441098193018,0.5909441098193018,0.5884927861044958,0.5884927861044958,0.5884927861044958, 0.5884927861044958,0.5884927861044958,0.5884927861044958,0.5860414623896899,0.5860414623896899, 0.5860414623896899,0.583590138674884,0.583590138674884,0.583590138674884,0.5811388149600779, 0.5811388149600779,0.5786874912452721,0.5762361675304661,0.5762361675304661,0.5737848438156602, 0.5737848438156602,0.5713335201008543,0.5713335201008543,0.5688821963860483,0.5688821963860483, 0.5664308726712425,0.5664308726712425,0.5639795489564365,0.5639795489564365,0.5615282252416306, 0.5590769015268245,0.5590769015268245,0.5590769015268245,0.5566255778120178,0.5566255778120178, 0.5541742540972119,0.5541742540972119,0.5517229303824059,0.5517229303824059,0.5517229303824059, 0.5492716066676,0.5492716066676,0.5492716066676,0.5492716066676,0.5468202829527942, 0.5443689592379882,0.5443689592379882,0.5443689592379882,0.5443689592379882,0.5419176355231823, 0.5394663118083762,0.5394663118083762,0.5370149880935703,0.5370149880935703,0.5345636643787645, 0.5345636643787645,0.5321123406639585,0.5321123406639585,0.5296610169491526,0.5296610169491526, 0.5272096932343466,0.5272096932343466,0.5272096932343466,0.5247583695195399,0.5247583695195399, 0.522307045804734,0.522307045804734,0.522307045804734,0.522307045804734,0.519855722089928, 0.519855722089928,0.519855722089928,0.517404398375122,0.5149530746603161,0.5125017509455102, 0.5125017509455102,0.5100504272307043,0.5100504272307043,0.5075991035158983,0.5051477798010924, 0.5051477798010924,0.5051477798010924,0.5026964560862865,0.5026964560862865,0.5026964560862865, 0.5026964560862865,0.5002451323714806,0.5002451323714806,0.49779380865667455] d = {'x':Utilization, 'y':Efficiency} partLoadData = pd.DataFrame(d) partLoadData nSegments = 4 esM.add(fn.ConversionPartLoad(esM=esM, name='PEMEC', physicalUnit=r'kW$_{el}$', commodityConversionFactors={'electricity':-1, 'hydrogen':1}, commodityConversionFactorsPartLoad={'electricity':-1, 'hydrogen': partLoadData}, nSegments=nSegments, hasCapacityVariable=True, bigM=99999, investPerCapacity=900, opexPerCapacity=900*0.01, interestRate=0.08, economicLifetime=10)) # Glass production - Hydrogen gas furnace capacityFix = 4985 annualLoss = 0.03 #After one year a glass melting furnace needs 3% more energy to maintain process quality due to increasing thermal losses operationRateFix = pd.DataFrame(np.linspace(capacityFix*(1-annualLoss/2),capacityFix*(1+annualLoss/2),num=numberOfTimeSteps),columns=['GlassProductionSite']) operationRateFix.mean() operationRateFix = operationRateFix/operationRateFix.mean() esM.add(fn.Conversion(esM=esM, name='hydrogenGasFurnace', physicalUnit= r'kW$_{H2}$', commodityConversionFactors={'hydrogen':-1, 'electricity':-0.020,'O2': -0.137, # stochiometric combustion: -0.238; lambda: -0.274 'rawMaterial': -0.209, 'heat':0.070, 'CO2':0.020}, # 'CO2':0.040 for H2 with german grid; 'CO2':0.021 for 100% renewable electricity hasCapacityVariable=True, capacityFix = capacityFix, operationRateFix=operationRateFix, investPerCapacity=1103.5, opexPerCapacity=652, interestRate=0.08, economicLifetime=10)) ### Sinks ### # Heat output esM.add(fn.Sink(esM=esM, name='Heat output', commodity='heat', hasCapacityVariable=False)) # CO2 output esM.add(fn.Sink(esM=esM, name='CO2 output', commodity='CO2', hasCapacityVariable=False)) # O2 output # We include this sink to enable feasibility of the optimization problem in case the electrolyzer produces slightly more oxygen than required in the hydrogen furnace (round-off error < 1%) - we only that for stochiometric combustion -> for lambda > 1 that shouldn't be necessary esM.add(fn.Sink(esM=esM, name='O2 output', commodity='O2', hasCapacityVariable=False)) ### Optimization ### # Input parameters timeSeriesAggregation=False solver='glpk' # Code esM.optimize(timeSeriesAggregation=timeSeriesAggregation, solver=solver) ### Test ### # Overall TAC srcSnkSummary = esM.getOptimizationSummary("SourceSinkModel", outputLevel=1) convSummary = esM.getOptimizationSummary("ConversionModel", outputLevel=1) convPartloadSummary = esM.getOptimizationSummary("ConversionPartLoadModel", outputLevel=1) TAC=(srcSnkSummary.loc[('ElectricityGrid', 'TAC', '[1 Euro/a]'),'GlassProductionSite']+ srcSnkSummary.loc[('oxygenSource', 'TAC', '[1 Euro/a]'),'GlassProductionSite']+ srcSnkSummary.loc[('rawMaterialSource', 'TAC', '[1 Euro/a]'),'GlassProductionSite']+ convSummary.loc[('hydrogenGasFurnace', 'TAC', '[1 Euro/a]'),'GlassProductionSite']+ convPartloadSummary.loc[('PEMEC', 'TAC', '[1 Euro/a]'),'GlassProductionSite']) np.testing.assert_allclose(TAC, 14016197.2088, rtol = 0.005) # relative toerlance < 0.5% # Electricity TAC np.testing.assert_allclose(srcSnkSummary.loc[('ElectricityGrid', 'TAC', '[1 Euro/a]'),'GlassProductionSite'], 6.23855e+06, rtol = 0.01) # relative toerlance < 1% # PEMEC summary np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'TAC', '[1 Euro/a]'),'GlassProductionSite'], 1.46349e+06, rtol = 0.002) # relative toerlance < 0.2% np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'capacity', '[kW$_{el}$]'),'GlassProductionSite'], 10225.1729065, rtol = 0.002) # relative toerlance < 0.2% np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'capexCap', '[1 Euro/a]'),'GlassProductionSite'], 1371467.06108539, rtol = 0.002) # relative toerlance < 0.2% np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'invest', '[1 Euro]'),'GlassProductionSite'], 9202655.61585, rtol = 0.002) # relative toerlance < 0.2% np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'opexCap', '[1 Euro/a]'),'GlassProductionSite'], 92026.5561585, rtol = 0.002) # relative toerlance < 0.2% np.testing.assert_allclose(convPartloadSummary.loc[('PEMEC', 'operation', '[kW$_{el}$*h/a]'),'GlassProductionSite'], 8.82488e+07, rtol = 0.01) # relative toerlance < 1% # PEM operation results opVarOptPartLoad = esM.componentModelingDict["ConversionPartLoadModel"].operationVariablesOptimum.loc[('PEMEC', 'GlassProductionSite')] opVarOptConstLoad = [2480.73776181,2481.6941601,2482.65055839,2483.60695668,2484.56335497,2485.51975325,2486.47615154, 2487.43254983,2488.38894812,2489.34534641,2490.3017447,2491.25814299,2492.21454128,2493.17093957,2494.12733785, 2495.08373614,2496.04013443,2496.99653272,2497.95293101,2498.9093293,2499.86572759,2500.82212588, 2501.77852417,2502.73492245,2503.69132074,2504.64771903,2505.60411732,2506.56051561,2507.5169139, 2508.47331219,2509.42971048,2510.38610877,2511.34250706,2512.29890534,2513.25530363,2514.21170192, 2515.16810021,2516.1244985,2517.08089679,2518.03729508,2518.99369337,2519.95009166,2520.90648994, 2521.86288823,2522.81928652,2523.77568481,2524.7320831,2525.68848139,2526.64487968,2527.60127797, 2528.55767626,2529.51407455,2530.47047283,2531.42687112,2532.38326941,2533.3396677,2534.29606599, 2535.25246428,2536.20886257,2537.16526086,2538.12165915,2539.07805743,2540.03445572,2540.99085401, 2541.9472523,2542.90365059,2543.86004888,2544.81644717,2545.77284546,2546.72924375,2547.68564204, 2548.64204032,2549.59843861,2550.5548369,2551.51123519,2552.46763348,2553.42403177,2554.38043006, 2555.33682835,2556.29322664] np.testing.assert_allclose(opVarOptPartLoad, opVarOptConstLoad,rtol=0.01)
def test_watersupply(): # read in original results results = pd.read_csv(os.path.join( os.path.dirname(__file__), '_testInputFiles', 'waterSupplySystem_totalTransmission.csv'), index_col=[0, 1, 2], header=None, squeeze=True) # get parameters locations = [ 'House 1', 'House 2', 'House 3', 'House 4', 'House 5', 'House 6', 'Node 1', 'Node 2', 'Node 3', 'Node 4', 'Water treatment', 'Water tank' ] commodityUnitDict = {'clean water': 'U', 'river water': 'U'} commodities = {'clean water', 'river water'} esM = fn.EnergySystemModel(locations=set(locations), commodities=commodities, numberOfTimeSteps=8760, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit='1e3 Euro', lengthUnit='m') starttime = time.time() # Source riverFlow = pd.DataFrame(np.zeros((8760, 12)), columns=locations) np.random.seed(42) riverFlow.loc[:, 'Water treatment'] = np.random.uniform( 0, 4, (8760)) + 8 * np.sin(np.pi * np.arange(8760) / 8760) esM.add( fn.Source(esM=esM, name='River', commodity='river water', hasCapacityVariable=False, operationRateMax=riverFlow, opexPerOperation=0.05)) # Conversion eligibility = pd.Series([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], index=locations) esM.add( fn.Conversion(esM=esM, name='Water treatment plant', physicalUnit='U', commodityConversionFactors={ 'river water': -1, 'clean water': 1 }, hasCapacityVariable=True, locationalEligibility=eligibility, investPerCapacity=7, opexPerCapacity=0.02 * 7, interestRate=0.08, economicLifetime=20)) # Storage eligibility = pd.Series([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], index=locations) esM.add( fn.Storage(esM=esM, name='Water tank', commodity='clean water', hasCapacityVariable=True, chargeRate=1 / 24, dischargeRate=1 / 24, locationalEligibility=eligibility, investPerCapacity=0.10, opexPerCapacity=0.02 * 0.1, interestRate=0.08, economicLifetime=20)) # Transmission distances = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0], [0, 0, 0, 0, 38, 40, 0, 105, 0, 0, 0, 0], [0, 0, 38, 40, 0, 0, 105, 0, 100, 0, 0, 0], [38, 40, 0, 0, 0, 0, 0, 100, 0, 30, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 20, 50], [0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0]]) distances = pd.DataFrame(distances, index=locations, columns=locations) # Old water pipes capacityFix = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 2, 0, 4, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 4, 4], [0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0]]) capacityFix = pd.DataFrame(capacityFix, index=locations, columns=locations) isBuiltFix = capacityFix.copy() isBuiltFix[isBuiltFix > 0] = 1 esM.add( fn.Transmission(esM=esM, name='Old water pipes', commodity='clean water', losses=0.1e-2, distances=distances, hasCapacityVariable=True, hasIsBuiltBinaryVariable=True, bigM=100, capacityFix=capacityFix, isBuiltFix=isBuiltFix)) # New water pipes incidence = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0], [0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]]) eligibility = pd.DataFrame(incidence, index=locations, columns=locations) esM.add( fn.Transmission(esM=esM, name='New water pipes', commodity='clean water', losses=0.05e-2, distances=distances, hasCapacityVariable=True, hasIsBuiltBinaryVariable=True, bigM=100, locationalEligibility=eligibility, investPerCapacity=0.1, investIfBuilt=0.5, interestRate=0.08, economicLifetime=50)) # Sink winterHours = np.append(range(8520, 8760), range(1920)) springHours, summerHours, autumnHours = np.arange(1920, 4128), np.arange( 4128, 6384), np.arange(6384, 8520) demand = pd.DataFrame(np.zeros((8760, 12)), columns=list(locations)) np.random.seed(42) demand[locations[0:6]] = np.random.uniform(0, 1, (8760, 6)) demand.loc[winterHours[(winterHours % 24 < 5) | (winterHours % 24 >= 23)]] = 0 demand.loc[springHours[(springHours % 24 < 4)]] = 0 demand.loc[summerHours[(summerHours % 24 < 5) | (summerHours % 24 >= 23)]] = 0 demand.loc[autumnHours[(autumnHours % 24 < 6) | (autumnHours % 24 >= 23)]] = 0 esM.add( fn.Sink(esM=esM, name='Water demand', commodity='clean water', hasCapacityVariable=False, operationRateFix=demand)) # # Optimize the system esM.cluster(numberOfTypicalPeriods=7) esM.optimize( timeSeriesAggregation=True, solver='glpk', optimizationSpecs='LogToConsole=1 OptimalityTol=1e-6 crossover=1') # # Selected results output esM.getOptimizationSummary("SourceSinkModel", outputLevel=2) # ### Storage esM.getOptimizationSummary("StorageModel", outputLevel=2) # ### Transmission esM.getOptimizationSummary("TransmissionModel", outputLevel=2) esM.componentModelingDict[ "TransmissionModel"].operationVariablesOptimum.sum(axis=1) # testresults = esM.componentModelingDict[ "TransmissionModel"].operationVariablesOptimum.sum(axis=1) print('Optimization took ' + str(time.time() - starttime)) # test if here solved fits with original results np.testing.assert_array_almost_equal(testresults.values, results.values, decimal=2)
# # 4. Add conversion components to the energy system model # %% [markdown] # ### Boiler # %% esM.add( fn.Conversion( esM=esM, name="Boiler", physicalUnit="kW_th", commodityConversionFactors={ "methane": -1.1, "heat": 1 }, hasIsBuiltBinaryVariable=True, hasCapacityVariable=True, interestRate=0.04, economicLifetime=20, investIfBuilt=2800, investPerCapacity=100, opexIfBuilt=24, bigM=200, )) # %% [markdown] # # 5. Add commodity storages to the energy system model # %% [markdown] # ### Thermal Storage
# # These are the components which can transfer one commodity into another one. # %% [markdown] # ## 4.1 Biomas to biogas # %% [markdown] # ### Bioslurry to Biogas # %% esM.add( fn.Conversion( esM=esM, name="bioslurry-biogas", physicalUnit=r"GW$_{CH4}$", commodityConversionFactors={ "bioslurry": -1, "biogas": 1 }, hasCapacityVariable=False, )) # %% [markdown] # ### Biowaste to Biogas # %% esM.add( fn.Conversion( esM=esM, name="biowaste-biogas", physicalUnit=r"GW$_{CH4}$", commodityConversionFactors={
def test_CO2Limit(): # 0) Preprocess energy system model locations = {"Region1", "Region2"} commodityUnitDict = { "electricity": r"MW$_{el}$", "methane": r"MW$_{CH_{4},LHV}$", "CO2": r"Mio. kg$_{CO_2}$/h", } commodities = {"electricity", "methane", "CO2"} ndays = 30 nhours = 24 * ndays ## Define Electricity Demand dailyProfileSimple = [ 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9, 0.8, ] demand = pd.DataFrame( [[(u + 0.1 * np.random.rand()) * 40, (u + 0.1 * np.random.rand()) * 60] for day in range(ndays) for u in dailyProfileSimple], index=range(nhours), columns=["Region1", "Region2"], ).round(2) # 1) Define CO2-Limit with balanceLimitConstraint (sink are defined negative) CO2_limit = pd.Series(index=["CO2 limit"]) CO2_limit.loc["CO2 limit"] = -1 * demand.sum().sum( ) * 0.6 * 201 * 1e-6 / 0.6 # 2) Initialize EnergySystemModel with two Regions esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=nhours, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit="1e6 Euro", lengthUnit="km", verboseLogLevel=2, balanceLimit=CO2_limit, lowerBound=True, ) # 3) Components are added: 'Electricity demand', 'Methane purchase', 'cctg', 'CO2 to environment', # 'Fuel Cell', 'Batteries' # Define Electricity demand and added to Energysystem esM.add( fn.Sink( esM=esM, name="Electricity demand", commodity="electricity", hasCapacityVariable=False, operationRateFix=demand, )) # Define Methane purchase and added to Energysystem esM.add( fn.Source( esM=esM, name="Methane purchase", commodity="methane", hasCapacityVariable=False, )) # Define ccgt and added to Energysystem esM.add( fn.Conversion( esM=esM, name="ccgt", physicalUnit=r"MW$_{el}$", commodityConversionFactors={ "electricity": 1, "methane": -1 / 0.6, "CO2": 201 * 1e-6 / 0.6, }, hasCapacityVariable=True, investPerCapacity=0.65, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33, )) # Define CO2 to environment and added to Energysystem esM.add( fn.Sink( esM=esM, name="CO2 to environment", commodity="CO2", hasCapacityVariable=False, balanceLimitID="CO2 limit", )) ## Wind turbines # Define Fuel Cell and added to Energysystem opexPerOperation = 150 / 1e6 interestRate, economicLifetime = 0.08, 20 esM.add( fn.Source( esM=esM, name="Fuel Cell", commodity="electricity", hasCapacityVariable=False, opexPerOperation=opexPerOperation, interestRate=interestRate, economicLifetime=economicLifetime, )) # Define Batteries and added to Energysystem chargeEfficiency, dischargeEfficiency, selfDischarge = ( 0.95, 0.95, 1 - (1 - 0.03)**(1 / (30 * 24)), ) chargeRate, dischargeRate = 1, 1 investPerCapacity, opexPerCapacity = 150, 150 * 0.01 interestRate, economicLifetime, cyclicLifetime = 0.08, 22, 10000 esM.add( fn.Storage( esM=esM, name="Batteries", commodity="electricity", hasCapacityVariable=True, chargeEfficiency=chargeEfficiency, cyclicLifetime=cyclicLifetime, dischargeEfficiency=dischargeEfficiency, selfDischarge=selfDischarge, chargeRate=chargeRate, dischargeRate=dischargeRate, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # 4) Optimize model esM.optimize(timeSeriesAggregation=False, solver="glpk") co2_to_environment = 0 # 5) The CO2_limit is compared to the outcome of the model # (sinks are defined negative) for i, loc in enumerate(esM.locations): # Get operation of Renewables for loc co2_to_environment += ( esM.componentModelingDict["SourceSinkModel"]. operationVariablesOptimum.loc["CO2 to environment", loc].sum()) tolerance = 0.001 ## Compare modeled co2 emissions to limit set in constraint. assert co2_to_environment * (1 - tolerance) < -1 * CO2_limit.loc["CO2 limit"] assert co2_to_environment * (1 + tolerance) > -1 * CO2_limit.loc["CO2 limit"]
def test_balanceLimitConstraint(): # 0) Preprocess energy system model locations = {"Region1", "Region2"} commodityUnitDict = {"electricity": r"MW$_{el}$", "heat": r"MW$_{th}$"} commodities = {"electricity", "heat"} ndays = 30 nhours = 24 * ndays # Electricity Demand dailyProfileSimple = [ 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9, 0.8, ] demand = pd.DataFrame( [[(u + 0.1 * np.random.rand()) * 40, (u + 0.1 * np.random.rand()) * 60] for day in range(ndays) for u in dailyProfileSimple], index=range(nhours), columns=["Region1", "Region2"], ).round(2) heat_demand = pd.DataFrame( [[(u + 0.1 * np.random.rand()) * 10, (u + 0.1 * np.random.rand()) * 20] for day in range(ndays) for u in dailyProfileSimple], index=range(nhours), columns=["Region1", "Region2"], ).round(2) # 1) Define balanceLimit constraint in relation to demand in regions balanceLimit = pd.DataFrame(columns=["Region1", "Region2"], index=["el", "heat"]) perNetAutarky = 0.75 perNetAutarky_h = 1 balanceLimit.loc["el"] = (1 - perNetAutarky) * demand.sum() balanceLimit.loc["heat"] = (1 - perNetAutarky_h) * heat_demand.sum() # 2) Initialize esM with two regions esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=nhours, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit="1e6 Euro", lengthUnit="km", verboseLogLevel=2, balanceLimit=balanceLimit, ) # 3) Components are added: 'Electricity demand', 'Heat demand', 'Electricity purchase', 'Heat purchase', # 'Heat pump', 'Wind turbines', 'PV', 'Batteries', 'AC cables', 'Heat pipes' # Define Electricity demand and added to Energysystem esM.add( fn.Sink( esM=esM, name="Electricity demand", commodity="electricity", hasCapacityVariable=False, operationRateFix=demand, )) # Define Heat demand and added to Energysystem esM.add( fn.Sink( esM=esM, name="Heat demand", commodity="heat", hasCapacityVariable=False, operationRateFix=heat_demand, )) # Define Cheap purchase 'Electricity purchase' and 'Heat purchase', which incentives to purchase, # but is limited because of balanceLimit # added to Energysystem esM.add( fn.Source( esM=esM, name="Electricity purchase", commodity="electricity", hasCapacityVariable=False, commodityCost=0.001, balanceLimitID="el", )) esM.add( fn.Source( esM=esM, name="Heat purchase", commodity="heat", hasCapacityVariable=False, commodityCost=0.001, balanceLimitID="heat", )) # Define heatpump and added to Energysystem esM.add( fn.Conversion( esM=esM, name="heatpump", physicalUnit=r"MW$_{el}$", commodityConversionFactors={ "electricity": -1, "heat": 2.5, }, hasCapacityVariable=True, capacityMax=1e6, investPerCapacity=0.95, opexPerCapacity=0.95 * 0.01, interestRate=0.08, economicLifetime=33, )) # Define Wind turbines and added to Energysystem operationRateMax = pd.DataFrame( [[np.random.beta(a=2, b=7.5), np.random.beta(a=2, b=9)] for t in range(nhours)], index=range(nhours), columns=["Region1", "Region2"], ).round(6) capacityMax = pd.Series([400, 200], index=["Region1", "Region2"]) investPerCapacity, opexPerCapacity = 1200, 1200 * 0.02 interestRate, economicLifetime = 0.08, 20 esM.add( fn.Source( esM=esM, name="Wind turbines", commodity="electricity", hasCapacityVariable=True, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Define PV and added to Energysystem operationRateMax = pd.DataFrame( [[u, u] for day in range(ndays) for u in dailyProfileSimple], index=range(nhours), columns=["Region1", "Region2"], ) capacityMax = pd.Series([100, 100], index=["Region1", "Region2"]) investPerCapacity, opexPerCapacity = 800, 800 * 0.02 interestRate, economicLifetime = 0.08, 25 esM.add( fn.Source( esM=esM, name="PV", commodity="electricity", hasCapacityVariable=True, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Define Batteries and added to Energysystem chargeEfficiency, dischargeEfficiency, selfDischarge = ( 0.95, 0.95, 1 - (1 - 0.03)**(1 / (30 * 24)), ) chargeRate, dischargeRate = 1, 1 investPerCapacity, opexPerCapacity = 150, 150 * 0.01 interestRate, economicLifetime, cyclicLifetime = 0.08, 22, 10000 esM.add( fn.Storage( esM=esM, name="Batteries", commodity="electricity", hasCapacityVariable=True, chargeEfficiency=chargeEfficiency, cyclicLifetime=cyclicLifetime, dischargeEfficiency=dischargeEfficiency, selfDischarge=selfDischarge, chargeRate=chargeRate, dischargeRate=dischargeRate, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Transmission Components # Define AC Cables and added to Energysystem capacityFix = pd.DataFrame([[0, 30], [30, 0]], columns=["Region1", "Region2"], index=["Region1", "Region2"]) distances = pd.DataFrame( [[0, 400], [400, 0]], columns=["Region1", "Region2"], index=["Region1", "Region2"], ) losses = 0.0001 esM.add( fn.Transmission( esM=esM, name="AC cables", commodity="electricity", hasCapacityVariable=True, capacityFix=capacityFix, distances=distances, losses=losses, balanceLimitID="el", )) # Define Heat pipes and added to Energysystem capacityFix = pd.DataFrame([[0, 30], [30, 0]], columns=["Region1", "Region2"], index=["Region1", "Region2"]) distances = pd.DataFrame( [[0, 400], [400, 0]], columns=["Region1", "Region2"], index=["Region1", "Region2"], ) losses = 0.0001 esM.add( fn.Transmission( esM=esM, name="Heat pipes", commodity="heat", hasCapacityVariable=True, capacityFix=capacityFix, distances=distances, losses=losses, balanceLimitID="heat", )) # 4) Optimize model esM.optimize(timeSeriesAggregation=False, solver="glpk") # 5) The balanceLimit is compared to the outcome of the model # purchase + exchange_in - exchange_out <= balanceLimit for i, loc in enumerate(esM.locations): # Get Electricity Purchase for location el_purchase = (esM.componentModelingDict["SourceSinkModel"]. operationVariablesOptimum.loc["Electricity purchase", loc].sum()) heat_purchase = (esM.componentModelingDict["SourceSinkModel"]. operationVariablesOptimum.loc["Heat purchase", loc].sum()) # Get Exchange going into region and out of region cables = esM.componentModelingDict[ "TransmissionModel"].operationVariablesOptimum.loc["AC cables"] pipes = esM.componentModelingDict[ "TransmissionModel"].operationVariablesOptimum.loc["Heat pipes"] for j, loc_ in enumerate(esM.locations): if loc != loc_: exch_in = (cables.loc[loc_, loc] * (1 - losses * distances.loc[loc_, loc])).T.sum() exch_in_h = (pipes.loc[loc_, loc] * (1 - losses * distances.loc[loc_, loc])).T.sum() exch_out = cables.loc[loc, loc_].T.sum() exch_out_h = pipes.loc[loc, loc_].T.sum() tolerance = 0.001 ## Compare modelled autarky to limit set in constraint. assert (el_purchase + exch_in - exch_out - tolerance < balanceLimit.loc["el", loc]) assert (heat_purchase + exch_in_h - exch_out_h - tolerance < balanceLimit.loc["heat", loc])
def getModel(): # 1. Create an energy system model instance locations = { "cluster_0", "cluster_1", "cluster_2", "cluster_3", "cluster_4", "cluster_5", "cluster_6", "cluster_7", } commodityUnitDict = { "electricity": "GW$_{el}$", "methane": "GW$_{CH_{4},LHV}$", "biogas": "GW$_{biogas,LHV}$", "CO2": "Mio. t$_{CO_2}$/h", "hydrogen": "GW$_{H_{2},LHV}$", } commodities = {"electricity", "hydrogen", "methane", "biogas", "CO2"} esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=8760, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit="1e9 Euro", lengthUnit="km", verboseLogLevel=0, ) CO2_reductionTarget = 1 # 2. Add commodity sources to the energy system model ### Wind onshore esM.add( fn.Source( esM=esM, name="Wind (onshore)", commodity="electricity", hasCapacityVariable=True, operationRateMax=data["Wind (onshore), operationRateMax"], capacityMax=data["Wind (onshore), capacityMax"], investPerCapacity=1.1, opexPerCapacity=1.1 * 0.02, interestRate=0.08, economicLifetime=20, )) ### PV esM.add( fn.Source( esM=esM, name="PV", commodity="electricity", hasCapacityVariable=True, operationRateMax=data["PV, operationRateMax"], capacityMax=data["PV, capacityMax"], investPerCapacity=0.65, opexPerCapacity=0.65 * 0.02, interestRate=0.08, economicLifetime=25, )) # 3. 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=0.021, interestRate=0.08, economicLifetime=33, )) ### Electrolyzers 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, )) # 4. Add commodity storages to the energy system model ### Lithium ion batteries 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=22, )) ### Hydrogen filled salt caverns 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=0.00011, opexPerCapacity=0.00057, interestRate=0.08, economicLifetime=30, )) # 5. Add commodity transmission components to the energy system model ### AC cables esM.add( fn.LinearOptimalPowerFlow( esM=esM, name="AC cables", commodity="electricity", hasCapacityVariable=True, capacityFix=data["AC cables, capacityFix"], reactances=data["AC cables, reactances"], )) ### DC cables esM.add( fn.Transmission( esM=esM, name="DC cables", commodity="electricity", losses=data["DC cables, losses"], distances=data["DC cables, distances"], hasCapacityVariable=True, capacityFix=data["DC cables, capacityFix"], )) ### Hydrogen pipelines esM.add( fn.Transmission( esM=esM, name="Pipelines (hydrogen)", commodity="hydrogen", distances=data["Pipelines, distances"], hasCapacityVariable=True, hasIsBuiltBinaryVariable=False, bigM=300, locationalEligibility=data["Pipelines, eligibility"], capacityMax=data["Pipelines, eligibility"] * 15, sharedPotentialID="pipelines", investPerCapacity=0.000177, investIfBuilt=0.00033, interestRate=0.08, economicLifetime=40, )) # 6. Add commodity sinks to the energy system model ### Electricity demand esM.add( fn.Sink( esM=esM, name="Electricity demand", commodity="electricity", hasCapacityVariable=False, operationRateFix=data["Electricity demand, operationRateFix"], )) ## 7.2. Hydrogen sinks FCEV_penetration = 0.5 esM.add( fn.Sink( esM=esM, name="Hydrogen demand", commodity="hydrogen", hasCapacityVariable=False, operationRateFix=data["Hydrogen demand, operationRateFix"] * FCEV_penetration, )) return esM
def create_simple_esm(): """ To observe the effects of variable conversion factors, we create a simple test esm. It consists of a source, a conversion and a sink. The sink has a fixed demand. The conversion rate of the electrolyzer changes in every period. We use a pandas.DataFrame for the electricity conversion factors and a pandas.Series for the hydrogen conversion factors to test the different inputs. """ numberOfTimeSteps = 4 hoursPerTimeStep = 2190 locs = ["ElectrolyzerLocation"] # Create an energy system model instance esM = fn.EnergySystemModel( locations={"ElectrolyzerLocation"}, commodities={"electricity", "hydrogen"}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ "electricity": r"kW$_{el}$", "hydrogen": r"kW$_{H_{2},LHV}$", }, hoursPerTimeStep=hoursPerTimeStep, costUnit="1 Euro", lengthUnit="km", verboseLogLevel=2, ) # Source esM.add( fn.Source( esM=esM, name="Electricity market", commodity="electricity", hasCapacityVariable=False, )) # Sink demand = pd.Series(np.array([1.0, 1.0, 1.0, 1.0])) * hoursPerTimeStep esM.add( fn.Sink( esM=esM, name="Industry site", commodity="hydrogen", hasCapacityVariable=False, operationRateFix=demand, )) cfs = {} # Use Dataframe for conversion rate timeseries cfs["electricity"] = pd.DataFrame([np.array([-0.1, -1, -10, -100])], index=locs).T # Use Series for conversion rate timeseries cfs["hydrogen"] = pd.Series(np.array([0.7, 0.7, 0.7, 0.7])) esM.add( fn.Conversion( esM=esM, name="Electrolyzers_VarConvFac", physicalUnit=r"kW$_{el}$", commodityConversionFactors={ "electricity": cfs["electricity"], "hydrogen": cfs["hydrogen"], }, hasCapacityVariable=True, investPerCapacity=1000, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, capacityMax=1000, economicLifetime=10, locationalEligibility=pd.Series([1], ["ElectrolyzerLocation"]), )) return esM
def test_variable_conversion_factor_with_tsa(minimal_test_esM): """ Same as `test_variable_conversion_factor_no_tsa` but with time series aggregation using 3 typical periods. Now the optimal solution is composed of only three different periods. """ # Get the minimal test system from conftest esM = copy.deepcopy(minimal_test_esM) # Create time-variable conversion rates for the two locations as pandas.DataFrame. locs = ["ElectrolyzerLocation", "IndustryLocation"] cfs = {} cfs["electricity"] = pd.DataFrame( [np.array([-0.1, -1, -1, -10]), np.array([-0.1, -1, -1, -10])], index=locs).T cfs["hydrogen"] = pd.DataFrame( [np.array([0.7, 0.7, 0.7, 0.7]), np.array([0.7, 0.7, 0.7, 0.7])], index=locs).T # Add a new component with variable conversion rate to the EnergySystemModel. esM.add( fn.Conversion( esM=esM, name="Electrolyzers_VarConvFac", physicalUnit=r"kW$_{el}$", commodityConversionFactors={ "electricity": cfs["electricity"], "hydrogen": cfs["hydrogen"], }, hasCapacityVariable=True, investPerCapacity=1000, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, )) esM.aggregateTemporally(numberOfTypicalPeriods=3, numberOfTimeStepsPerPeriod=1) esM.optimize(timeSeriesAggregation=True, solver="glpk") # Get optimal electrolyzer operations op_test_const = [] op_test_var = [] for t in range(0, 4): op_test_const.append(esM.componentModelingDict["ConversionModel"]. operationVariablesOptimum.xs("Electrolyzers").loc[ "ElectrolyzerLocation", t]) op_test_var.append( esM.componentModelingDict["ConversionModel"]. operationVariablesOptimum.xs("Electrolyzers_VarConvFac").loc[ "ElectrolyzerLocation", t]) # Assert the optimal operation # We are asserting up to a precision of one decimal to account for precision gaps # of the solver. assertion_values_const = [0.0, 9385714.2, 0.0, 9385714.2] assertion_values_var = [18771428.5, 18771428.5, 18771428.5, 0.0] for t in range(0, 4): np.testing.assert_almost_equal(op_test_const[t], assertion_values_const[t], decimal=1) np.testing.assert_almost_equal(op_test_var[t], assertion_values_var[t], decimal=1)
def test_variable_conversion_factor_no_tsa(minimal_test_esM): """ We add an additional electrolyzer component with variable conversion rates. It has a very high efficiency in time-step 1, where it is now choosen to operate in favour of the electrolyzer with constant efficiency. Efficiency in the last time-step is very low for the new electolyzer, therefore it is not operated in this time-step. """ # Get the minimal test system from conftest esM = copy.deepcopy(minimal_test_esM) # Create time-variable conversion rates for the two locations as pandas.DataFrame. locs = ["ElectrolyzerLocation", "IndustryLocation"] cfs = {} cfs["electricity"] = pd.DataFrame( [np.array([-0.1, -1, -1, -10]), np.array([-0.1, -1, -1, -10])], index=locs).T cfs["hydrogen"] = pd.DataFrame( [np.array([0.7, 0.7, 0.7, 0.7]), np.array([0.7, 0.7, 0.7, 0.7])], index=locs).T # Add a new component with variable conversion rate to the EnergySystemModel. esM.add( fn.Conversion( esM=esM, name="Electrolyzers_VarConvFac", physicalUnit=r"kW$_{el}$", commodityConversionFactors={ "electricity": cfs["electricity"], "hydrogen": cfs["hydrogen"], }, hasCapacityVariable=True, investPerCapacity=1000, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, )) # Optimize the esM without TSA. esM.optimize(timeSeriesAggregation=False, solver="glpk") # Get optimal electrolyzer operations op_test_const = [] op_test_var = [] for t in range(0, 4): op_test_const.append(esM.componentModelingDict["ConversionModel"]. operationVariablesOptimum.xs("Electrolyzers").loc[ "ElectrolyzerLocation", t]) op_test_var.append( esM.componentModelingDict["ConversionModel"]. operationVariablesOptimum.xs("Electrolyzers_VarConvFac").loc[ "ElectrolyzerLocation", t]) # Assert the optimal operation # We are asserting up to a precision of one decimal to account for precision gaps # of the solver. assertion_values_const = [0.0, 18771428.5, 0.0, 18771428.5] assertion_values_var = [18771428.5, 18771428.5, 0.0, 0.0] for t in range(0, 4): np.testing.assert_almost_equal(op_test_const[t], assertion_values_const[t], decimal=1) np.testing.assert_almost_equal(op_test_var[t], assertion_values_var[t], decimal=1)
# %% [markdown] # # 4. Add conversion components to the energy system model # %% [markdown] # ### 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=0.021, interestRate=0.08, economicLifetime=33, )) # %% [markdown] # ### Electrolyzers # %% esM.add( fn.Conversion( esM=esM, name="Electroylzers",
def test_exceededLifetime(): # load a minimal test system """Returns minimal instance of esM""" numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel(locations={'OneLocation'}, commodities={'electricity', 'hydrogen'}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ 'electricity': r'kW$_{el}$', 'hydrogen': r'kW$_{H_{2},LHV}$' }, hoursPerTimeStep=hoursPerTimeStep, costUnit='1 Euro', lengthUnit='km', verboseLogLevel=2) ### Buy electricity at the electricity market costs = pd.DataFrame([np.array([ 0.05, 0., 0.1, 0.051, ])], index=['OneLocation']).T revenues = pd.DataFrame([np.array([ 0., 0.01, 0., 0., ])], index=['OneLocation']).T maxpurchase = pd.DataFrame([np.array([ 1e6, 1e6, 1e6, 1e6, ])], index=['OneLocation']).T * hoursPerTimeStep esM.add( fn.Source( esM=esM, name='Electricity market', commodity='electricity', hasCapacityVariable=False, operationRateMax=maxpurchase, commodityCostTimeSeries=costs, commodityRevenueTimeSeries=revenues, )) # eur/kWh ### Electrolyzers esM.add( fn.Conversion( esM=esM, name='Electrolyzers', physicalUnit=r'kW$_{el}$', commodityConversionFactors={ 'electricity': -1, 'hydrogen': 0.7 }, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10)) ### Hydrogen filled somewhere esM.add( fn.Storage( esM=esM, name='Pressure tank', commodity='hydrogen', hasCapacityVariable=True, capacityVariableDomain='continuous', stateOfChargeMin=0.33, investPerCapacity=0.5, # eur/kWh interestRate=0.08, economicLifetime=30)) ### Industry site demand = pd.DataFrame([np.array([ 6e3, 6e3, 6e3, 6e3, ])], index=['OneLocation']).T * hoursPerTimeStep esM.add( fn.Sink( esM=esM, name='Industry site', commodity='hydrogen', hasCapacityVariable=False, operationRateFix=demand, )) # Set the technical lifetime of the electrolyzers to 7 years. setattr( esM.componentModelingDict['ConversionModel']. componentsDict['Electrolyzers'], 'technicalLifetime', pd.Series([7], index=['OneLocation'])) results = fn.optimizeSimpleMyopic(esM, startYear=2020, endYear=2030, nbOfRepresentedYears=5, timeSeriesAggregation=False, solver='glpk', saveResults=False, trackESMs=True) # Check if electrolyzers which are installed in 2020 are not included in the system of 2030 due to the exceeded lifetime assert 'Electrolyzers_stock_2020' not in results[ 'ESM_2030'].componentNames.keys()
def test_CO2ReductionTargets(): locations = {'regionN', 'regionS'} commodityUnitDict = { 'electricity': r'GW$_{el}$', 'naturalGas': r'GW$_{CH_{4},LHV}$', 'CO2': r'Mio. t$_{CO_2}$/h' } commodities = {'electricity', 'naturalGas', 'CO2'} numberOfTimeSteps, hoursPerTimeStep = 8760, 1 costUnit, lengthUnit = '1e6 Euro', 'km' CO2_reductionTarget = 0.8 esM = fn.EnergySystemModel(locations=locations, commodities=commodities, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=hoursPerTimeStep, costUnit=costUnit, lengthUnit=lengthUnit, verboseLogLevel=0) # Add Source Components ## Wind turbines name, commodity = 'Wind turbines', 'electricity' hasCapacityVariable = True operationRateMax = pd.DataFrame( [[np.random.beta(a=2, b=7.5), np.random.beta(a=2, b=9)] for t in range(8760)], index=range(8760), columns=['regionN', 'regionS']).round(6) capacityMax = pd.Series([400, 200], index=['regionN', 'regionS']) investPerCapacity, opexPerCapacity = 1200, 1200 * 0.02 interestRate, economicLifetime = 0.08, 20 esM.add( fn.Source(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime)) ## PV name, commodity = 'PV', 'electricity' hasCapacityVariable = True dailyProfileSimple = [ 0, 0, 0, 0, 0, 0, 0, 0.05, 0.15, 0.2, 0.4, 0.8, 0.7, 0.4, 0.2, 0.15, 0.05, 0, 0, 0, 0, 0, 0, 0 ] operationRateMax = pd.DataFrame([[u, u] for day in range(365) for u in dailyProfileSimple], index=range(8760), columns=['regionN', 'regionS']) capacityMax = pd.Series([100, 100], index=['regionN', 'regionS']) investPerCapacity, opexPerCapacity = 800, 800 * 0.02 interestRate, economicLifetime = 0.08, 25 esM.add( fn.Source(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime)) # Natural Gas name, commodity = 'Natural gas import', 'naturalGas' hasCapacityVariable = False commodityCost = 0.03 esM.add( fn.Source(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, commodityCost=commodityCost)) # Add Conversion components # Gas power plants name, physicalUnit = 'Gas power plants', r'GW$_{el}$' commodityConversionFactors = { 'electricity': 1, 'naturalGas': -1 / 0.63, 'CO2': 201 * 1e-6 / 0.63 } hasCapacityVariable = True investPerCapacity, opexPerCapacity = 650, 650 * 0.03 interestRate, economicLifetime = 0.08, 30 esM.add( fn.Conversion(esM=esM, name=name, physicalUnit=physicalUnit, commodityConversionFactors=commodityConversionFactors, hasCapacityVariable=hasCapacityVariable, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime)) # Storage Components ## Batteries name, commodity = 'Batteries', 'electricity' hasCapacityVariable = True chargeEfficiency, dischargeEfficiency, selfDischarge = 0.95, 0.95, 1 - ( 1 - 0.03)**(1 / (30 * 24)) chargeRate, dischargeRate = 1, 1 investPerCapacity, opexPerCapacity = 150, 150 * 0.01 interestRate, economicLifetime, cyclicLifetime = 0.08, 22, 10000 esM.add( fn.Storage(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, chargeEfficiency=chargeEfficiency, cyclicLifetime=cyclicLifetime, dischargeEfficiency=dischargeEfficiency, selfDischarge=selfDischarge, chargeRate=chargeRate, dischargeRate=dischargeRate, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime)) # Transmission Components ## AC cables name, commodity = 'AC cables', 'electricity' hasCapacityVariable = True capacityFix = pd.DataFrame([[0, 30], [30, 0]], columns=['regionN', 'regionS'], index=['regionN', 'regionS']) distances = pd.DataFrame([[0, 400], [400, 0]], columns=['regionN', 'regionS'], index=['regionN', 'regionS']) losses = 0.0001 esM.add( fn.Transmission(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, capacityFix=capacityFix, distances=distances, losses=losses)) # Sink Components ## Electricity Demand name, commodity = 'Electricity demand', 'electricity', hasCapacityVariable = False dailyProfileSimple = [ 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9, 0.8 ] operationRateFix = pd.DataFrame([[(u + 0.1 * np.random.rand()) * 25, (u + 0.1 * np.random.rand()) * 40] for day in range(365) for u in dailyProfileSimple], index=range(8760), columns=['regionN', 'regionS']).round(2) esM.add( fn.Sink(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateFix=operationRateFix)) # CO2 to environment name, commodity = 'CO2 to environment', 'CO2', hasCapacityVariable = False commodityLimitID, yearlyLimit = 'CO2 limit', 366 * (1 - CO2_reductionTarget) if yearlyLimit > 0: esM.add( fn.Sink(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, commodityLimitID=commodityLimitID, yearlyLimit=yearlyLimit)) # Optimize the system with simple myopic approach results = fn.optimizeSimpleMyopic(esM, startYear=2020, nbOfSteps=2, nbOfRepresentedYears=5, CO2Reference=366, CO2ReductionTargets=[25, 50, 100], saveResults=False, trackESMs=True, numberOfTypicalPeriods=3, solver='glpk') assert results['ESM_2025'].getOptimizationSummary('SourceSinkModel').loc[ 'CO2 to environment'].loc['operation'].values.sum() < 183 assert results['ESM_2030'].getOptimizationSummary('SourceSinkModel').loc[ 'CO2 to environment'].loc['operation'].values.sum() == 0
# %% [markdown] # # 4. Add conversion components to the energy system model # %% [markdown] # ### Combined cycle gas turbine plants # %% esM.add( fn.Conversion( esM=esM, name="CCGT plants (methane)", physicalUnit=r"GW$_{el}$", commodityConversionFactors={ "electricity": 1, "methane": -1 / 0.6, "CO2": 201 * 1e-6 / 0.6, }, hasCapacityVariable=True, investPerCapacity=0.65, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33, )) # %% [markdown] # ### New combined cycle gas turbine plants for biogas # %% esM.add( fn.Conversion( esM=esM,
def test_industrialSite(): # # Model an energy system # Input parameters locations = {'industry_0'} commodityUnitDict = { 'electricity': r'MW$_{el}$', 'hydrogen': r'MW$_{H_{2},LHV}$', 'heat': r'MW$_{heat}$}' } commodities = {'electricity', 'hydrogen', 'heat'} numberOfTimeSteps, hoursPerTimeStep = 24 * 6, 1 / 6 #8760, 1 # 52560, 1/6 costUnit, lengthUnit = '1e3 Euro', 'km' # Code esM = fn.EnergySystemModel(locations=locations, commodities=commodities, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=hoursPerTimeStep, costUnit=costUnit, lengthUnit=lengthUnit, verboseLogLevel=0) # ## Add source component data = pd.read_excel( os.path.join(os.path.dirname(__file__), '_testInputFiles', 'generationTimeSeries_e825103.xlsx')) data = data.iloc[0:numberOfTimeSteps] operationRateFix = pd.DataFrame( data['e825103_2017_2.3MW_faults9'], index=range(numberOfTimeSteps)) # Dataset with least missing data operationRateFix.columns = ['industry_0'] # Input parameters name, commodity = 'Wind turbines', 'electricity' hasCapacityVariable = True capacityMax = pd.Series([10], index=['industry_0']) # 10 MW_el = 0.01 GW_el investPerCapacity, opexPerCapacity = 0, 30 # 30 €/kW = 30 1e6€/GW = 30 1e3€/MW interestRate, economicLifetime = 0.08, 20 esM.add( fn.Source(esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateFix=operationRateFix, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime)) # ## Add conversion components esM.add( fn.Conversion( esM=esM, name='PEMEC', physicalUnit=r'MW$_{el}$', commodityConversionFactors={ 'electricity': -1, 'hydrogen': 0.67 }, hasCapacityVariable=True, investPerCapacity=2300, opexPerCapacity=12.5, interestRate=0.08, # for 2018 CAPEX economicLifetime=5)) func = lambda x: 0.5 * (x - 2)**3 + (x - 2)**2 + 0.0001 esM.add( fn.ConversionPartLoad( esM=esM, name='AEC', physicalUnit=r'MW$_{el}$', commodityConversionFactors={ 'electricity': -1, 'hydrogen': 0.64 }, commodityConversionFactorsPartLoad={ 'electricity': -1, 'hydrogen': func }, # commodityConversionFactorsPartLoad=pwl, nSegments=2, hasCapacityVariable=True, bigM=99, investPerCapacity=1300, opexPerCapacity=18, interestRate=0.08, # for 2018 CAPEX economicLifetime=9)) # ## Add storage components esM.add( fn.Storage(esM=esM, name='Hydrogen tank (gaseous)', commodity='hydrogen', hasCapacityVariable=True, capacityVariableDomain='continuous', capacityPerPlantUnit=1, chargeRate=1, dischargeRate=1, sharedPotentialID=None, stateOfChargeMin=0.06, stateOfChargeMax=1, investPerCapacity=0.004, opexPerCapacity=0.004 * 0.02, interestRate=0.08, economicLifetime=20)) ### Industrial hydrogen demand operationRateFix = pd.DataFrame( 2 * np.ones(numberOfTimeSteps) * (hoursPerTimeStep), columns=['industry_0']) # constant hydrogen demand of 2 MW_GH2: esM.add( fn.Sink(esM=esM, name='Hydrogen demand', commodity='hydrogen', hasCapacityVariable=False, operationRateFix=operationRateFix)) # Heat output esM.add( fn.Sink(esM=esM, name='Heat output', commodity='heat', hasCapacityVariable=False)) # Input parameters timeSeriesAggregation = False solver = 'glpk' # Optimize esM.optimize(timeSeriesAggregation=timeSeriesAggregation, solver=solver)
def test_QPinvest(): numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel( locations={"location1"}, commodities={"electricity", "hydrogen"}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ "electricity": r"kW$_{el}$", "hydrogen": r"kW$_{H_{2},LHV}$", }, hoursPerTimeStep=hoursPerTimeStep, costUnit="1 Euro", lengthUnit="km", verboseLogLevel=2, ) # time step length [h] timeStepLength = numberOfTimeSteps * hoursPerTimeStep ### Buy electricity at the electricity market costs = pd.Series([0.5, 0.4, 0.2, 0.5], index=[0, 1, 2, 3]) esM.add( fn.Source( esM=esM, name="Electricity market", commodity="electricity", hasCapacityVariable=False, commodityCostTimeSeries=costs, ) ) # euro/kWh ### Electrolyzers esM.add( fn.Conversion( esM=esM, name="Electrolyzer", physicalUnit=r"kW$_{el}$", commodityConversionFactors={"electricity": -1, "hydrogen": 0.7}, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, QPcostScale=0.1, capacityMin=0, capacityMax=10, ) ) ### Industry site demand = pd.Series([10000.0, 10000.0, 10000.0, 10000.0], index=[0, 1, 2, 3]) esM.add( fn.Sink( esM=esM, name="Industry site", commodity="hydrogen", hasCapacityVariable=False, operationRateFix=demand, ) ) ### Optimize (just executed if gurobi is installed) flag = True try: esM.optimize(timeSeriesAggregation=False, solver="gurobi") except: flag = False if flag: invest = round( esM.getOptimizationSummary("ConversionModel") .loc["Electrolyzer"] .loc["invest"]["location1"] .values.astype(float)[0], 3, ) assert invest == 3148.179
def test_hydrogenSinkDriver(): # 0) Preprocess energy system model locations = {"Region1"} commodityUnitDict = { "electricity": r"MW$_{el}$", "hydrogen": r"MW$_{LHV_H2}$" } commodities = {"electricity", "hydrogen"} ndays = 20 nhours = 24 * ndays # Wind turbines dailyProfile = [ 0.21773151164616183, 0.034941022753796035, 0.06456056257136962, 0.18781756363388397, 0.07389116186240981, 0.1696331916932108, 0.4464499926416005, 0.1706764273756967, 0.15694190971549513, 0.2882743403035651, 0.32127549679527717, 0.14116461052786136, 0.5461613758054059, 0.21958149674207716, 0.25715084860896853, 0.21525778612323568, 0.15916222011475448, 0.11014921269063864, 0.2532504131880449, 0.3154483508270503, 0.08412368254727028, 0.06337996942065917, 0.12431489082721527, 0.17319120880651773, ] operationRateMax = pd.DataFrame( [u for day in range(ndays) for u in dailyProfile], index=range(nhours), columns=["Region1"], ).round(6) capacityMaxWind = pd.Series([400], index=["Region1"]) # Define min production minProduction = (operationRateMax["Region1"].sum() * capacityMaxWind.loc["Region1"] * 0.6) investPerCapacity, opexPerCapacity = 1200, 1200 * 0.02 interestRate, economicLifetime = 0.08, 20 # 1) specify balanceLimit balanceLimit = pd.DataFrame(index=["hydrogenDriver"], columns=["Region1"]) # Define negative balanceLimit as driver. Because sink is contributing negatively. balanceLimit.loc["hydrogenDriver", "Region1"] = -1 * minProduction # 2) Initialize esM with one region with use of parameters balanceLimit and lowerBound # Use upperBound in a mathematical sense. abs(Sink Operation) >= abs(balanceLimit), both negative because Sink. esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=nhours, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit="1e6 Euro", lengthUnit="km", verboseLogLevel=2, balanceLimit=balanceLimit, lowerBound=False, ) # 3) Components are added: 'Wind turbines', 'Electrolyzer', 'Batteries' and 'Hydrogen Annual Production' # 'Hydrogen Annual Production' is included in the balanceLimit analysis # Define Hydrogen Annual Production and added to Energysystem esM.add( fn.Sink( esM=esM, name="Hydrogen Annual Production", balanceLimitID="hydrogenDriver", commodity="hydrogen", hasCapacityVariable=False, )) # Define Wind turbines and added to Energysystem esM.add( fn.Source( esM=esM, name="Wind turbines", commodity="electricity", hasCapacityVariable=True, operationRateMax=operationRateMax, capacityMax=capacityMaxWind, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Define techno-economic parameters of Batteries and added to Energysystem chargeEfficiency, dischargeEfficiency, selfDischarge = ( 0.95, 0.95, 1 - (1 - 0.03)**(1 / (30 * 24)), ) chargeRate, dischargeRate = 1, 1 investPerCapacity, opexPerCapacity = 150, 150 * 0.01 interestRate, economicLifetime, cyclicLifetime = 0.08, 22, 10000 esM.add( fn.Storage( esM=esM, name="Batteries", commodity="electricity", hasCapacityVariable=True, chargeEfficiency=chargeEfficiency, cyclicLifetime=cyclicLifetime, dischargeEfficiency=dischargeEfficiency, selfDischarge=selfDischarge, chargeRate=chargeRate, dischargeRate=dischargeRate, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Define techno-economic parameters Electrolyzers and added to Energysystem invest_per_capacity_electrolysis = 0.5 # unit 1e9 EUR/GW opex_electrolysis = 0.025 # in %/100 of capex interestRate_electrolysis = 0.08 # in %/100 economicLifetime_electrolysis = 10 # in years esM.add( fn.Conversion( esM=esM, name="Electrolyzers", physicalUnit=r"MW$_{el}$", commodityConversionFactors={ "electricity": -1, "hydrogen": 0.7 }, hasCapacityVariable=True, investPerCapacity=invest_per_capacity_electrolysis, opexPerCapacity=invest_per_capacity_electrolysis * opex_electrolysis, interestRate=interestRate_electrolysis, economicLifetime=economicLifetime_electrolysis, )) # 4) Optimize model esM.optimize(timeSeriesAggregation=False, solver="glpk") # 5) The balanceLimit is compared to the outcome of the model # Hydrogen Annual Production >= hydrogenDriver (as balanceLimitID) for i, loc in enumerate(esM.locations): # Get operation of Renewables for loc operation_hydrogen = ( esM.componentModelingDict["SourceSinkModel"]. operationVariablesOptimum.loc["Hydrogen Annual Production", loc].sum()) tolerance = 0.001 ## Compare modelled lowerLimit to limit set in constraint. test_min_production = (ndays * sum(dailyProfile) * capacityMaxWind.loc["Region1"] * 0.6) # Assert that the min. production requirement was indeed fulfilled (must be). assert operation_hydrogen > test_min_production * (1 - tolerance) # Assert that produced volume does also not exceed the min. requirements (should be # the case for linear models and TAC as cost function but could change in the future). assert operation_hydrogen < test_min_production * (1 + tolerance)
)) # %% [markdown] # # Conversion # %% eligibility = pd.Series([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0], index=locations) esM.add( fn.Conversion( esM=esM, name="Water treatment plant", physicalUnit="U", commodityConversionFactors={ "river water": -1, "clean water": 1 }, hasCapacityVariable=True, locationalEligibility=eligibility, investPerCapacity=7, opexPerCapacity=0.02 * 7, interestRate=0.08, economicLifetime=20, )) # %% [markdown] # # Storage # %% eligibility = pd.Series([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], index=locations) esM.add( fn.Storage(
def test_minimumPartLoad(): # read in original results results = [4.0, 4.0, 0.0, 0.0, 4.0] # 2. Create an energy system model instance locations = {"example_region"} commodityUnitDict = {"electricity": r"GW$_{el}$", "methane": r"GW$_{CH_{4},LHV}$"} commodities = {"electricity", "methane"} esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=5, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit="1e9 Euro", lengthUnit="km", verboseLogLevel=0, ) data_cost = {"example_region": [10, 10, 10, 10, 10]} data_cost_df = pd.DataFrame(data=data_cost) esM.add( fn.Source( esM=esM, name="Natural gas purchase", commodity="methane", hasCapacityVariable=False, commodityCostTimeSeries=data_cost_df, ) ) # 4. Add conversion components to the energy system model ### Combined cycle gas turbine plants esM.add( fn.Conversion( esM=esM, name="unrestricted", physicalUnit=r"GW$_{el}$", commodityConversionFactors={"electricity": 1, "methane": -1 / 0.625}, hasCapacityVariable=True, investPerCapacity=0.65, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33, ) ) data_cap = pd.Series(index=["example_region"], data=10) esM.add( fn.Conversion( esM=esM, name="restricted", physicalUnit=r"GW$_{el}$", commodityConversionFactors={"electricity": 1, "methane": -1 / 0.625}, capacityFix=data_cap, partLoadMin=0.4, bigM=10000, investPerCapacity=0.5, opexPerCapacity=0.015, interestRate=0.08, economicLifetime=33, ) ) data_demand = {"example_region": [5, 5, 1, 1, 4]} data_demand_df = pd.DataFrame(data=data_demand) esM.add( fn.Sink( esM=esM, name="Electricity demand", commodity="electricity", hasCapacityVariable=False, operationRateFix=data_demand_df, ) ) esM.optimize(timeSeriesAggregation=False, solver="glpk") print("restricted dispatch:\n") print( esM.componentModelingDict["ConversionModel"].operationVariablesOptimum.xs( "restricted" ) ) print("unrestricted dispatch:\n") print( esM.componentModelingDict["ConversionModel"].operationVariablesOptimum.xs( "unrestricted" ) ) # test if here solved fits with original results # test if here solved fits with original results testresults = esM.componentModelingDict[ "ConversionModel" ].operationVariablesOptimum.xs("restricted") np.testing.assert_array_almost_equal(testresults.values[0], results, decimal=2)
def test_CO2ReductionTargets(): locations = {"regionN", "regionS"} commodityUnitDict = { "electricity": r"GW$_{el}$", "naturalGas": r"GW$_{CH_{4},LHV}$", "CO2": r"Mio. t$_{CO_2}$/h", } commodities = {"electricity", "naturalGas", "CO2"} numberOfTimeSteps, hoursPerTimeStep = 8760, 1 costUnit, lengthUnit = "1e6 Euro", "km" CO2_reductionTarget = 0.8 esM = fn.EnergySystemModel( locations=locations, commodities=commodities, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=hoursPerTimeStep, costUnit=costUnit, lengthUnit=lengthUnit, verboseLogLevel=0, ) # Add Source Components ## Wind turbines name, commodity = "Wind turbines", "electricity" hasCapacityVariable = True operationRateMax = pd.DataFrame( [[np.random.beta(a=2, b=7.5), np.random.beta(a=2, b=9)] for t in range(8760)], index=range(8760), columns=["regionN", "regionS"], ).round(6) capacityMax = pd.Series([400, 200], index=["regionN", "regionS"]) investPerCapacity, opexPerCapacity = 1200, 1200 * 0.02 interestRate, economicLifetime = 0.08, 20 esM.add( fn.Source( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) ## PV name, commodity = "PV", "electricity" hasCapacityVariable = True dailyProfileSimple = [ 0, 0, 0, 0, 0, 0, 0, 0.05, 0.15, 0.2, 0.4, 0.8, 0.7, 0.4, 0.2, 0.15, 0.05, 0, 0, 0, 0, 0, 0, 0, ] operationRateMax = pd.DataFrame( [[u, u] for day in range(365) for u in dailyProfileSimple], index=range(8760), columns=["regionN", "regionS"], ) capacityMax = pd.Series([100, 100], index=["regionN", "regionS"]) investPerCapacity, opexPerCapacity = 800, 800 * 0.02 interestRate, economicLifetime = 0.08, 25 esM.add( fn.Source( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateMax=operationRateMax, capacityMax=capacityMax, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Natural Gas name, commodity = "Natural gas import", "naturalGas" hasCapacityVariable = False commodityCost = 0.03 esM.add( fn.Source( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, commodityCost=commodityCost, )) # Add Conversion components # Gas power plants name, physicalUnit = "Gas power plants", r"GW$_{el}$" commodityConversionFactors = { "electricity": 1, "naturalGas": -1 / 0.63, "CO2": 201 * 1e-6 / 0.63, } hasCapacityVariable = True investPerCapacity, opexPerCapacity = 650, 650 * 0.03 interestRate, economicLifetime = 0.08, 30 esM.add( fn.Conversion( esM=esM, name=name, physicalUnit=physicalUnit, commodityConversionFactors=commodityConversionFactors, hasCapacityVariable=hasCapacityVariable, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Storage Components ## Batteries name, commodity = "Batteries", "electricity" hasCapacityVariable = True chargeEfficiency, dischargeEfficiency, selfDischarge = ( 0.95, 0.95, 1 - (1 - 0.03)**(1 / (30 * 24)), ) chargeRate, dischargeRate = 1, 1 investPerCapacity, opexPerCapacity = 150, 150 * 0.01 interestRate, economicLifetime, cyclicLifetime = 0.08, 22, 10000 esM.add( fn.Storage( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, chargeEfficiency=chargeEfficiency, cyclicLifetime=cyclicLifetime, dischargeEfficiency=dischargeEfficiency, selfDischarge=selfDischarge, chargeRate=chargeRate, dischargeRate=dischargeRate, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, )) # Transmission Components ## AC cables name, commodity = "AC cables", "electricity" hasCapacityVariable = True capacityFix = pd.DataFrame([[0, 30], [30, 0]], columns=["regionN", "regionS"], index=["regionN", "regionS"]) distances = pd.DataFrame( [[0, 400], [400, 0]], columns=["regionN", "regionS"], index=["regionN", "regionS"], ) losses = 0.0001 esM.add( fn.Transmission( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, capacityFix=capacityFix, distances=distances, losses=losses, )) # Sink Components ## Electricity Demand name, commodity = ( "Electricity demand", "electricity", ) hasCapacityVariable = False dailyProfileSimple = [ 0.6, 0.6, 0.6, 0.6, 0.6, 0.7, 0.9, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9, 0.8, ] operationRateFix = pd.DataFrame( [[(u + 0.1 * np.random.rand()) * 25, (u + 0.1 * np.random.rand()) * 40] for day in range(365) for u in dailyProfileSimple], index=range(8760), columns=["regionN", "regionS"], ).round(2) esM.add( fn.Sink( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, operationRateFix=operationRateFix, )) # CO2 to environment name, commodity = ( "CO2 to environment", "CO2", ) hasCapacityVariable = False commodityLimitID, yearlyLimit = "CO2 limit", 366 * (1 - CO2_reductionTarget) if yearlyLimit > 0: esM.add( fn.Sink( esM=esM, name=name, commodity=commodity, hasCapacityVariable=hasCapacityVariable, commodityLimitID=commodityLimitID, yearlyLimit=yearlyLimit, )) # Optimize the system with simple myopic approach results = fn.optimizeSimpleMyopic( esM, startYear=2020, nbOfSteps=2, nbOfRepresentedYears=5, CO2Reference=366, CO2ReductionTargets=[25, 50, 100], saveResults=False, trackESMs=True, numberOfTypicalPeriods=3, solver="glpk", ) assert (results["ESM_2025"].getOptimizationSummary("SourceSinkModel"). loc["CO2 to environment"].loc["operation", "[Mio. t$_{CO_2}$/h*h/a]"].sum() < 183) assert ( results["ESM_2030"].getOptimizationSummary("SourceSinkModel"). loc["CO2 to environment"].loc["operation", "[Mio. t$_{CO_2}$/h*h/a]"].sum() == 0)
def getModel(): numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel( locations={"ElectrolyzerLocation", "IndustryLocation"}, commodities={"electricity", "hydrogen"}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ "electricity": r"kW$_{el}$", "hydrogen": r"kW$_{H_{2},LHV}$", }, hoursPerTimeStep=hoursPerTimeStep, costUnit="1 Euro", lengthUnit="km", verboseLogLevel=1, balanceLimit=None, lowerBound=False, ) # time step length [h] timeStepLength = numberOfTimeSteps * hoursPerTimeStep ### Buy electricity at the electricity market costs = pd.DataFrame( [ np.array([ 0.05, 0.0, 0.1, 0.051, ]), np.array([ 0.0, 0.0, 0.0, 0.0, ]), ], index=["ElectrolyzerLocation", "IndustryLocation"], ).T revenues = pd.DataFrame( [ np.array([ 0.0, 0.01, 0.0, 0.0, ]), np.array([ 0.0, 0.0, 0.0, 0.0, ]), ], index=["ElectrolyzerLocation", "IndustryLocation"], ).T maxpurchase = (pd.DataFrame( [ np.array([ 1e6, 1e6, 1e6, 1e6, ]), np.array([ 0.0, 0.0, 0.0, 0.0, ]), ], index=["ElectrolyzerLocation", "IndustryLocation"], ).T * hoursPerTimeStep) esM.add( fn.Source( esM=esM, name="Electricity market", commodity="electricity", hasCapacityVariable=False, operationRateMax=maxpurchase, commodityCostTimeSeries=costs, commodityRevenueTimeSeries=revenues, )) # eur/kWh ### Electrolyzers esM.add( fn.Conversion( esM=esM, name="Electrolyzers", physicalUnit=r"kW$_{el}$", commodityConversionFactors={ "electricity": -1, "hydrogen": 0.7 }, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, )) ### Hydrogen filled somewhere esM.add( fn.Storage( esM=esM, name="Pressure tank", commodity="hydrogen", hasCapacityVariable=True, capacityVariableDomain="continuous", stateOfChargeMin=0.33, investPerCapacity=0.5, # eur/kWh interestRate=0.08, economicLifetime=30, )) ### Hydrogen pipelines esM.add( fn.Transmission( esM=esM, name="Pipelines", commodity="hydrogen", hasCapacityVariable=True, investPerCapacity=0.177, interestRate=0.08, economicLifetime=40, )) ### Industry site demand = (pd.DataFrame( [ np.array([ 0.0, 0.0, 0.0, 0.0, ]), np.array([ 6e3, 6e3, 6e3, 6e3, ]), ], index=["ElectrolyzerLocation", "IndustryLocation"], ).T * hoursPerTimeStep) esM.add( fn.Sink( esM=esM, name="Industry site", commodity="hydrogen", hasCapacityVariable=False, operationRateFix=demand, )) return esM
def test_exceededLifetime(): # load a minimal test system """Returns minimal instance of esM""" numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel( locations={"OneLocation"}, commodities={"electricity", "hydrogen"}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ "electricity": r"kW$_{el}$", "hydrogen": r"kW$_{H_{2},LHV}$", }, hoursPerTimeStep=hoursPerTimeStep, costUnit="1 Euro", lengthUnit="km", verboseLogLevel=2, ) ### Buy electricity at the electricity market costs = pd.DataFrame( [np.array([ 0.05, 0.0, 0.1, 0.051, ])], index=["OneLocation"], ).T revenues = pd.DataFrame( [np.array([ 0.0, 0.01, 0.0, 0.0, ])], index=["OneLocation"], ).T maxpurchase = (pd.DataFrame( [np.array([ 1e6, 1e6, 1e6, 1e6, ])], index=["OneLocation"], ).T * hoursPerTimeStep) esM.add( fn.Source( esM=esM, name="Electricity market", commodity="electricity", hasCapacityVariable=False, operationRateMax=maxpurchase, commodityCostTimeSeries=costs, commodityRevenueTimeSeries=revenues, )) # eur/kWh ### Electrolyzers esM.add( fn.Conversion( esM=esM, name="Electrolyzers", physicalUnit=r"kW$_{el}$", commodityConversionFactors={ "electricity": -1, "hydrogen": 0.7 }, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10, )) ### Hydrogen filled somewhere esM.add( fn.Storage( esM=esM, name="Pressure tank", commodity="hydrogen", hasCapacityVariable=True, capacityVariableDomain="continuous", stateOfChargeMin=0.33, investPerCapacity=0.5, # eur/kWh interestRate=0.08, economicLifetime=30, )) ### Industry site demand = (pd.DataFrame( [np.array([ 6e3, 6e3, 6e3, 6e3, ])], index=["OneLocation"], ).T * hoursPerTimeStep) esM.add( fn.Sink( esM=esM, name="Industry site", commodity="hydrogen", hasCapacityVariable=False, operationRateFix=demand, )) # Set the technical lifetime of the electrolyzers to 7 years. setattr( esM.componentModelingDict["ConversionModel"]. componentsDict["Electrolyzers"], "technicalLifetime", pd.Series([7], index=["OneLocation"]), ) results = fn.optimizeSimpleMyopic( esM, startYear=2020, endYear=2030, nbOfRepresentedYears=5, timeSeriesAggregation=False, solver="glpk", saveResults=False, trackESMs=True, ) # Check if electrolyzers which are installed in 2020 are not included in the system of 2030 due to the exceeded lifetime assert "Electrolyzers_stock_2020" not in results[ "ESM_2030"].componentNames.keys()
def test_commodityCostTimeSeries(): cwd = os.getcwd() data = getData() # read in original results results = pd.Series.from_csv( os.path.join(os.path.dirname(__file__), '..', 'examples', 'Multi-regional Energy System Workflow', 'totalBiogasPurchase.csv')) # 2. Create an energy system model instance locations = { 'cluster_0', 'cluster_1', 'cluster_2', 'cluster_3', 'cluster_4', 'cluster_5', 'cluster_6', 'cluster_7' } commodityUnitDict = { 'electricity': r'GW$_{el}$', 'methane': r'GW$_{CH_{4},LHV}$', 'biogas': r'GW$_{biogas,LHV}$', 'CO2': r'Mio. t$_{CO_2}$/h', 'hydrogen': r'GW$_{H_{2},LHV}$' } commodities = {'electricity', 'hydrogen', 'methane', 'biogas', 'CO2'} numberOfTimeSteps = 8760 hoursPerTimeStep = 1 esM = fn.EnergySystemModel(locations=locations, commodities=commodities, numberOfTimeSteps=8760, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit='1e9 Euro', lengthUnit='km', verboseLogLevel=0) CO2_reductionTarget = 1 # 3. Add commodity sources to the energy system model ## 3.1. Electricity sources ### Wind onshore esM.add( fn.Source(esM=esM, name='Wind (onshore)', commodity='electricity', hasCapacityVariable=True, operationRateMax=data['Wind (onshore), operationRateMax'], capacityMax=data['Wind (onshore), capacityMax'], investPerCapacity=1.1, opexPerCapacity=1.1 * 0.02, interestRate=0.08, economicLifetime=20)) data['Wind (onshore), operationRateMax'].sum() ### Wind offshore esM.add( fn.Source(esM=esM, name='Wind (offshore)', commodity='electricity', hasCapacityVariable=True, operationRateMax=data['Wind (offshore), operationRateMax'], capacityMax=data['Wind (offshore), capacityMax'], investPerCapacity=2.3, opexPerCapacity=2.3 * 0.02, interestRate=0.08, economicLifetime=20)) data['Wind (offshore), operationRateMax'].sum() ### PV esM.add( fn.Source(esM=esM, name='PV', commodity='electricity', hasCapacityVariable=True, operationRateMax=data['PV, operationRateMax'], capacityMax=data['PV, capacityMax'], investPerCapacity=0.65, opexPerCapacity=0.65 * 0.02, interestRate=0.08, economicLifetime=25)) data['PV, operationRateMax'].sum() ### Exisisting run-of-river hydroelectricity plants esM.add( fn.Source( esM=esM, name='Existing run-of-river plants', commodity='electricity', hasCapacityVariable=True, operationRateFix=data[ 'Existing run-of-river plants, operationRateFix'], tsaWeight=0.01, capacityFix=data['Existing run-of-river plants, capacityFix'], investPerCapacity=0, opexPerCapacity=0.208)) ## 3.2. Methane (natural gas and biogas) ### Natural gas esM.add( fn.Source(esM=esM, name='Natural gas purchase', commodity='methane', hasCapacityVariable=False, commodityCostTimeSeries=data[ 'Natural Gas, commodityCostTimeSeries'])) ### Biogas esM.add( fn.Source( esM=esM, name='Biogas purchase', commodity='biogas', operationRateMax=data['Biogas, operationRateMax'], hasCapacityVariable=False, commodityCostTimeSeries=data['Biogas, commodityCostTimeSeries'])) ## 3.3 CO2 ### CO2 esM.add( fn.Source(esM=esM, name='CO2 from enviroment', commodity='CO2', hasCapacityVariable=False, commodityLimitID='CO2 limit', yearlyLimit=366 * (1 - CO2_reductionTarget))) # 4. Add conversion components to the energy system model ### Combined cycle gas turbine plants esM.add( fn.Conversion(esM=esM, name='CCGT plants (methane)', physicalUnit=r'GW$_{el}$', commodityConversionFactors={ 'electricity': 1, 'methane': -1 / 0.625, 'CO2': 201 * 1e-6 / 0.625 }, hasCapacityVariable=True, investPerCapacity=0.65, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33)) ### New combined cycle gas turbine plants for biogas esM.add( fn.Conversion(esM=esM, name='New CCGT plants (biogas)', physicalUnit=r'GW$_{el}$', commodityConversionFactors={ 'electricity': 1, 'biogas': -1 / 0.635 }, hasCapacityVariable=True, investPerCapacity=0.7, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33)) ### 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=0.021, interestRate=0.08, economicLifetime=33)) ### Electrolyzers 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)) ### rSOC capexRSOC = 1.5 esM.add( fn.Conversion(esM=esM, name='rSOEC', physicalUnit=r'GW$_{el}$', linkedConversionCapacityID='rSOC', commodityConversionFactors={ 'electricity': -1, 'hydrogen': 0.6 }, hasCapacityVariable=True, investPerCapacity=capexRSOC / 2, opexPerCapacity=capexRSOC * 0.02 / 2, interestRate=0.08, economicLifetime=10)) esM.add( fn.Conversion(esM=esM, name='rSOFC', physicalUnit=r'GW$_{el}$', linkedConversionCapacityID='rSOC', commodityConversionFactors={ 'electricity': 1, 'hydrogen': -1 / 0.6 }, hasCapacityVariable=True, investPerCapacity=capexRSOC / 2, opexPerCapacity=capexRSOC * 0.02 / 2, interestRate=0.08, economicLifetime=10)) # 5. Add commodity storages to the energy system model ## 5.1. Electricity storage ### Lithium ion batteries 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=22)) ## 5.2. Hydrogen storage ### Hydrogen filled salt caverns 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=0.00011, opexPerCapacity=0.00057, interestRate=0.08, economicLifetime=30)) ## 5.3. Methane storage ### Methane filled salt caverns esM.add( fn.Storage(esM=esM, name='Salt caverns (biogas)', commodity='biogas', hasCapacityVariable=True, capacityVariableDomain='continuous', capacityPerPlantUnit=443, chargeRate=1 / 470.37, dischargeRate=1 / 470.37, sharedPotentialID='Existing salt caverns', stateOfChargeMin=0.33, stateOfChargeMax=1, capacityMax=data['Salt caverns (methane), capacityMax'], investPerCapacity=0.00004, opexPerCapacity=0.00001, interestRate=0.08, economicLifetime=30)) ## 5.4 Pumped hydro storage ### Pumped hydro storage esM.add( fn.Storage(esM=esM, name='Pumped hydro storage', commodity='electricity', chargeEfficiency=0.88, dischargeEfficiency=0.88, hasCapacityVariable=True, selfDischarge=1 - (1 - 0.00375)**(1 / (30 * 24)), chargeRate=0.16, dischargeRate=0.12, capacityFix=data['Pumped hydro storage, capacityFix'], investPerCapacity=0, opexPerCapacity=0.000153)) # 6. Add commodity transmission components to the energy system model ## 6.1. Electricity transmission ### AC cables esM.add( fn.LinearOptimalPowerFlow(esM=esM, name='AC cables', commodity='electricity', hasCapacityVariable=True, capacityFix=data['AC cables, capacityFix'], reactances=data['AC cables, reactances'])) ### DC cables esM.add( fn.Transmission(esM=esM, name='DC cables', commodity='electricity', losses=data['DC cables, losses'], distances=data['DC cables, distances'], hasCapacityVariable=True, capacityFix=data['DC cables, capacityFix'])) ## 6.2 Methane transmission ### Methane pipeline esM.add( fn.Transmission(esM=esM, name='Pipelines (biogas)', commodity='biogas', distances=data['Pipelines, distances'], hasCapacityVariable=True, hasIsBuiltBinaryVariable=False, bigM=300, locationalEligibility=data['Pipelines, eligibility'], capacityMax=data['Pipelines, eligibility'] * 15, sharedPotentialID='pipelines', investPerCapacity=0.000037, investIfBuilt=0.000314, interestRate=0.08, economicLifetime=40)) ## 6.3 Hydrogen transmission ### Hydrogen pipelines esM.add( fn.Transmission(esM=esM, name='Pipelines (hydrogen)', commodity='hydrogen', distances=data['Pipelines, distances'], hasCapacityVariable=True, hasIsBuiltBinaryVariable=False, bigM=300, locationalEligibility=data['Pipelines, eligibility'], capacityMax=data['Pipelines, eligibility'] * 15, sharedPotentialID='pipelines', investPerCapacity=0.000177, investIfBuilt=0.00033, interestRate=0.08, economicLifetime=40)) # 7. Add commodity sinks to the energy system model ## 7.1. Electricity sinks ### Electricity demand esM.add( fn.Sink(esM=esM, name='Electricity demand', commodity='electricity', hasCapacityVariable=False, operationRateFix=data['Electricity demand, operationRateFix'])) ## 7.2. Hydrogen sinks ### Fuel cell electric vehicle (FCEV) demand FCEV_penetration = 0.5 esM.add( fn.Sink(esM=esM, name='Hydrogen demand', commodity='hydrogen', hasCapacityVariable=False, operationRateFix=data['Hydrogen demand, operationRateFix'] * FCEV_penetration)) ## 7.3. CO2 sinks ### CO2 exiting the system's boundary esM.add( fn.Sink(esM=esM, name='CO2 to enviroment', commodity='CO2', hasCapacityVariable=False, commodityLimitID='CO2 limit', yearlyLimit=366 * (1 - CO2_reductionTarget))) # 8. Optimize energy system model esM.cluster(numberOfTypicalPeriods=3) esM.optimize(timeSeriesAggregation=True, solver='glpk') # test if here solved fits with original results testresults = esM.componentModelingDict[ "SourceSinkModel"].operationVariablesOptimum.xs('Biogas purchase').sum( axis=1) np.testing.assert_array_almost_equal(testresults.values, results.values, decimal=2)
"electricity": 1, "naturalGas": -1 / 0.63, "CO2": 201 * 1e-6 / 0.63, } hasCapacityVariable = True investPerCapacity, opexPerCapacity = 650, 650 * 0.03 interestRate, economicLifetime = 0.08, 30 # Code esM.add( fn.Conversion( esM=esM, name=name, physicalUnit=physicalUnit, commodityConversionFactors=commodityConversionFactors, hasCapacityVariable=hasCapacityVariable, investPerCapacity=investPerCapacity, opexPerCapacity=opexPerCapacity, interestRate=interestRate, economicLifetime=economicLifetime, ) ) # %% [markdown] # ## Add storage components # # Storage components can store commodities across time steps. # # The self discharge of a storage technology is described in FINE in percent per hour. If the literature value is given in percent per month, e.g. 3%/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}$. # %%
def minimal_test_esM(): """Returns minimal instance of esM""" numberOfTimeSteps = 4 hoursPerTimeStep = 2190 # Create an energy system model instance esM = fn.EnergySystemModel( locations={'ElectrolyzerLocation', 'IndustryLocation'}, commodities={'electricity', 'hydrogen'}, numberOfTimeSteps=numberOfTimeSteps, commodityUnitsDict={ 'electricity': r'kW$_{el}$', 'hydrogen': r'kW$_{H_{2},LHV}$' }, hoursPerTimeStep=hoursPerTimeStep, costUnit='1 Euro', lengthUnit='km', verboseLogLevel=2) # time step length [h] timeStepLength = numberOfTimeSteps * hoursPerTimeStep ### Buy electricity at the electricity market costs = pd.DataFrame( [np.array([ 0.05, 0., 0.1, 0.051, ]), np.array([ 0., 0., 0., 0., ])], index=['ElectrolyzerLocation', 'IndustryLocation']).T revenues = pd.DataFrame( [np.array([ 0., 0.01, 0., 0., ]), np.array([ 0., 0., 0., 0., ])], index=['ElectrolyzerLocation', 'IndustryLocation']).T maxpurchase = pd.DataFrame( [np.array([ 1e6, 1e6, 1e6, 1e6, ]), np.array([ 0., 0., 0., 0., ])], index=['ElectrolyzerLocation', 'IndustryLocation' ]).T * hoursPerTimeStep esM.add( fn.Source( esM=esM, name='Electricity market', commodity='electricity', hasCapacityVariable=False, operationRateMax=maxpurchase, commodityCostTimeSeries=costs, commodityRevenueTimeSeries=revenues, )) # eur/kWh ### Electrolyzers esM.add( fn.Conversion( esM=esM, name='Electrolyzers', physicalUnit=r'kW$_{el}$', commodityConversionFactors={ 'electricity': -1, 'hydrogen': 0.7 }, hasCapacityVariable=True, investPerCapacity=500, # euro/kW opexPerCapacity=500 * 0.025, interestRate=0.08, economicLifetime=10)) ### Hydrogen filled somewhere esM.add( fn.Storage( esM=esM, name='Pressure tank', commodity='hydrogen', hasCapacityVariable=True, capacityVariableDomain='continuous', stateOfChargeMin=0.33, investPerCapacity=0.5, # eur/kWh interestRate=0.08, economicLifetime=30)) ### Hydrogen pipelines esM.add( fn.Transmission(esM=esM, name='Pipelines', commodity='hydrogen', hasCapacityVariable=True, investPerCapacity=0.177, interestRate=0.08, economicLifetime=40)) ### Industry site demand = pd.DataFrame([ np.array([ 0., 0., 0., 0., ]), np.array([ 6e3, 6e3, 6e3, 6e3, ]), ], index=['ElectrolyzerLocation', 'IndustryLocation' ]).T * hoursPerTimeStep esM.add( fn.Sink( esM=esM, name='Industry site', commodity='hydrogen', hasCapacityVariable=False, operationRateFix=demand, )) return esM
def test_minimumPartLoad(): # read in original results results = [4., 4., 0., 0., 4.] # 2. Create an energy system model instance locations = {'example_region'} commodityUnitDict = { 'electricity': r'GW$_{el}$', 'methane': r'GW$_{CH_{4},LHV}$' } commodities = {'electricity', 'methane'} esM = fn.EnergySystemModel(locations=locations, commodities=commodities, numberOfTimeSteps=5, commodityUnitsDict=commodityUnitDict, hoursPerTimeStep=1, costUnit='1e9 Euro', lengthUnit='km', verboseLogLevel=0) data_cost = {'example_region': [10, 10, 10, 10, 10]} data_cost_df = pd.DataFrame(data=data_cost) esM.add( fn.Source(esM=esM, name='Natural gas purchase', commodity='methane', hasCapacityVariable=False, commodityCostTimeSeries=data_cost_df)) # 4. Add conversion components to the energy system model ### Combined cycle gas turbine plants esM.add( fn.Conversion(esM=esM, name='unrestricted', physicalUnit=r'GW$_{el}$', commodityConversionFactors={ 'electricity': 1, 'methane': -1 / 0.625 }, hasCapacityVariable=True, investPerCapacity=0.65, opexPerCapacity=0.021, interestRate=0.08, economicLifetime=33)) data_cap = pd.Series(index=['example_region'], data=10) esM.add( fn.Conversion(esM=esM, name='restricted', physicalUnit=r'GW$_{el}$', commodityConversionFactors={ 'electricity': 1, 'methane': -1 / 0.625 }, capacityFix=data_cap, partLoadMin=0.4, bigM=10000, investPerCapacity=0.5, opexPerCapacity=0.015, interestRate=0.08, economicLifetime=33)) data_demand = {'example_region': [5, 5, 1, 1, 4]} data_demand_df = pd.DataFrame(data=data_demand) esM.add( fn.Sink(esM=esM, name='Electricity demand', commodity='electricity', hasCapacityVariable=False, operationRateFix=data_demand_df)) esM.optimize(timeSeriesAggregation=False, solver='glpk') print('restricted dispatch:\n') print(esM.componentModelingDict['ConversionModel']. operationVariablesOptimum.xs('restricted')) print('unrestricted dispatch:\n') print(esM.componentModelingDict['ConversionModel']. operationVariablesOptimum.xs('unrestricted')) # test if here solved fits with original results # test if here solved fits with original results testresults = esM.componentModelingDict[ "ConversionModel"].operationVariablesOptimum.xs('restricted') np.testing.assert_array_almost_equal(testresults.values[0], results, decimal=2)