From 4523f324cd50bd3e24b81ed74bb6d2975b22efe0 Mon Sep 17 00:00:00 2001 From: martavp Date: Sat, 18 Jul 2020 11:22:30 +0200 Subject: [PATCH] Group the generators existing before base year into categories and add them to the network as e.g. solar-2005, solar-2010, solar-2015 --- config.default.yaml | 5 +- scripts/add_brownfield.py | 82 ++++----- scripts/add_existing_baseyear.py | 267 ++++++++++++++++-------------- scripts/prepare_sector_network.py | 42 +++-- 4 files changed, 217 insertions(+), 179 deletions(-) diff --git a/config.default.yaml b/config.default.yaml index 485526da..6d070675 100755 --- a/config.default.yaml +++ b/config.default.yaml @@ -46,7 +46,10 @@ biomass: year: 2030 scenario: "Med" - +existing_capacities: + grouping_years: ['1980', '1985', '1990', '1995', '2000','2005','2010','2015', '2019'] + threshold_capacity: 10 + sector: 'central' : True 'central_fraction' : 0.6 diff --git a/scripts/add_brownfield.py b/scripts/add_brownfield.py index f7ca624c..55195592 100755 --- a/scripts/add_brownfield.py +++ b/scripts/add_brownfield.py @@ -20,10 +20,6 @@ import pytz from vresutils.costdata import annuity -from add_existing_baseyear import add_power_capacities_installed_before_baseyear - -from add_existing_baseyear import add_heating_capacities_installed_before_baseyear - from prepare_sector_network import prepare_costs #First tell PyPSA that links can have multiple outputs by @@ -39,7 +35,12 @@ override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per u override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"] override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"] - +override_component_attrs["Link"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Link"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] +override_component_attrs["Generator"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Generator"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] +override_component_attrs["Store"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Store"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] def add_brownfield(n, n_p, year): print("adding brownfield") @@ -66,19 +67,21 @@ def add_brownfield(n, n_p, year): previous_timestep=snakemake.config['scenario']['planning_horizons'][snakemake.config['scenario']['planning_horizons'].index(year)-1] previous_timesteps=snakemake.config['scenario']['planning_horizons'][0:snakemake.config['scenario']['planning_horizons'].index(year)] + grouping_years=snakemake.config['existing_capacities']['grouping_years'] - # generators installed before baseyear are removed, - # they are added again by add_power_capacities_installed_before_baseyear() - # with updated capacities (some of them have been decomissioned) - n_p.mremove("Generator", [index for index in n_p.generators.index.to_list() if '<'+snakemake.config['scenario']['planning_horizons'][0] in index]) - + + ### GENERATORS ### # generators whose build_year + lifetime < year are removed n_p.mremove("Generator", [index for index in n_p.generators.index.to_list() - if (n_p.generators.loc[index, 'build_year'] in previous_timesteps) - and (n_p.generators.loc[index, 'build_year']+n_p.generators.loc[index, 'lifetime'] < int(year))]) + if (n_p.generators.loc[index, 'build_year']+n_p.generators.loc[index, 'lifetime'] < int(year))]) - # generators whose capacity was optimized in the previous year are renamed - n_p.generators.index=np.where(n_p.generators.index.str[-4:].isin(previous_timesteps)==False, + # remove generators if their optimized nominal capacity is lower than a threshold + n_p.mremove("Generator", [index for index in n_p.generators.index.to_list() + if (n_p.generators.loc[index, 'p_nom_opt'] < snakemake.config['existing_capacities']['threshold_capacity'])]) + + + # generators whose capacity was optimized in the previous year are renamed and build year is added + n_p.generators.index=np.where(n_p.generators.index.str[-4:].isin(previous_timesteps+grouping_years)==False, n_p.generators.index + '-' + previous_timestep, n_p.generators.index) n_p.generators.loc[[index for index in n_p.generators.index.to_list() @@ -97,19 +100,22 @@ def add_brownfield(n, n_p, year): build_year=n_p.generators.build_year, lifetime=n_p.generators.lifetime) - #add stores from previous steps - + ### STORES ### # stores whose installationYear + lifetime < year are removed n_p.mremove("Store", [index for index in n_p.stores.index.to_list() - if (n_p.stores.loc[index, 'build_year'] in previous_timesteps) - and (n_p.stores.loc[index, 'build_year']+n_p.stores.loc[index, 'lifetime'] < int(year))]) + if (n_p.stores.loc[index, 'build_year']+n_p.stores.loc[index, 'lifetime'] < int(year))]) - # stores whose capacity was optimized in the previous year are renamed - n_p.stores.index=np.where(n_p.stores.index.str[-4:].isin(previous_timesteps)==False, + # remove stores if their optimized nominal capacity is lower than a threshold + n_p.mremove("Store", [index for index in n_p.stores.index.to_list() + if (n_p.stores.loc[index, 'e_nom_opt'] < snakemake.config['existing_capacities']['threshold_capacity'])]) + + # stores whose capacity was optimized in the previous year are renamed and the build year is added + n_p.stores.index=np.where(n_p.stores.index.str[-4:].isin(previous_timesteps+grouping_years)==False, n_p.stores.index + '-' + previous_timestep, n_p.stores.index) n_p.stores.loc[[index for index in n_p.stores.index.to_list() if previous_timestep in index], 'build_year']=int(previous_timestep) + #add stores from previous steps n.madd("Store", n_p.stores.index, bus=n_p.stores.bus, @@ -120,22 +126,25 @@ def add_brownfield(n, n_p, year): build_year=n_p.stores.build_year, lifetime=n_p.stores.lifetime) - ## add links from previous steps - # TODO: add_chp_constraint() in solve_network needs to be adjusted - n_p.mremove("Link", [index for index in n_p.links.index.to_list() if '<'+snakemake.config['scenario']['planning_horizons'][0] in index]) + ### LINKS ### + # TODO: add_chp_constraint() in solve_network needs to be adjusted n_p.mremove("Link", [index for index in n_p.links.index.to_list() if 'CHP' in index]) - # stores whose installationYear + lifetime < year are removed - n_p.mremove("Link", [index for index in n_p.links.index.to_list() - if (n_p.links.loc[index, 'build_year'] in previous_timesteps) - and (n_p.links.loc[index, 'build_year']+n_p.links.loc[index, 'lifetime'] < int(year))]) - # links whose installationYear + lifetime < year are removed - n_p.links.index=np.where(n_p.links.index.str[-4:].isin(previous_timesteps)==False, + n_p.mremove("Link", [index for index in n_p.links.index.to_list() + if (n_p.links.loc[index, 'build_year']+n_p.links.loc[index, 'lifetime'] < int(year))]) + + # delete links if their optimized nominal capacity is lower than a threshold + n_p.mremove("Link", [index for index in n_p.links.index.to_list() + if (n_p.links.loc[index, 'p_nom_opt'] < snakemake.config['existing_capacities']['threshold_capacity'])]) + + # links whose capacity was optimized in the previous year are renamed and the build year is added + n_p.links.index=np.where(n_p.links.index.str[-4:].isin(previous_timesteps+grouping_years)==False, n_p.links.index + '-' + previous_timestep, n_p.links.index) n_p.links.loc[[index for index in n_p.links.index.to_list() if previous_timestep in index], 'build_year']=int(previous_timestep) + #add links from previous steps n.madd("Link", n_p.links.index, bus0=n_p.links.bus0, @@ -159,9 +168,9 @@ if __name__ == "__main__": wildcards=dict(network='elec', simpl='', clusters='37', lv='1.0', sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1', co2_budget_name='go', - planning_horizons='2050'), + planning_horizons='2030'), input=dict(network='pypsa-eur-sec/results/test/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_{planning_horizons}.nc', - network_p='pypsa-eur-sec/results/test/postnetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_2040.nc', + network_p='pypsa-eur-sec/results/test/postnetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_2020.nc', costs='pypsa-eur-sec/data/costs/costs_{planning_horizons}.csv', cop_air_total="pypsa-eur-sec/resources/cop_air_total_{network}_s{simpl}_{clusters}.nc", cop_soil_total="pypsa-eur-sec/resources/cop_soil_total_{network}_s{simpl}_{clusters}.nc"), @@ -195,17 +204,8 @@ if __name__ == "__main__": Nyears) baseyear = snakemake.config['scenario']["planning_horizons"][0] - - add_power_capacities_installed_before_baseyear(n, year, baseyear, costs) # only the capacities with YearDecomissioning > year are added + - if "H" in opts: - time_dep_hp_cop = options["time_dep_hp_cop"] - ashp_cop = xr.open_dataarray(snakemake.input.cop_air_total).T.to_pandas().reindex(index=n.snapshots) - gshp_cop = xr.open_dataarray(snakemake.input.cop_soil_total).T.to_pandas().reindex(index=n.snapshots) - default_lifetime = snakemake.config['costs']['lifetime'] - add_heating_capacities_installed_before_baseyear(n, year, baseyear, ashp_cop, gshp_cop, time_dep_hp_cop, costs, - default_lifetime) # only the capacities with YearDecomissioning > year are added - n.export_to_netcdf(snakemake.output[0]) diff --git a/scripts/add_existing_baseyear.py b/scripts/add_existing_baseyear.py index 1c07d5ad..172a2fc3 100755 --- a/scripts/add_existing_baseyear.py +++ b/scripts/add_existing_baseyear.py @@ -35,18 +35,22 @@ override_component_attrs["Link"].loc["efficiency3"] = ["static or series","per u override_component_attrs["Link"].loc["p2"] = ["series","MW",0.,"2nd bus output","Output"] override_component_attrs["Link"].loc["p3"] = ["series","MW",0.,"3rd bus output","Output"] +override_component_attrs["Link"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Link"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] +override_component_attrs["Generator"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Generator"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] +override_component_attrs["Store"].loc["build_year"] = ["integer","year",np.nan,"build year","Input (optional)"] +override_component_attrs["Store"].loc["lifetime"] = ["float","years",np.nan,"build year","Input (optional)"] -def add_power_capacities_installed_before_baseyear(n, year, baseyear, costs): +def add_power_capacities_installed_before_baseyear(n, grouping_years, costs): """ Parameters ---------- n : network - year : capacity that fulfills YearDecomissioning > year is added + + grouping_years : intervals to group existing capacities - baseyear : capacity name will be e.g. "solar 2020 + nodes=set([node[0:2] for node in n.buses.index[n.buses.carrier == "AC"]]) + + + #TODO: Check if we want to change YearCommisioned into YearRetrofited + for i,grouping_year in enumerate(grouping_years): + if i==0: + index = df_agg.YearCommissioned < int(grouping_year) + else: + index = (int(grouping_years[i-1]) < df_agg.YearCommissioned) & (df_agg.YearCommissioned < int(grouping_year)) - index=df_agg.YearDecommissioning > int(year) df = df_agg[index].pivot_table(index='Country', columns='Fueltype', - values='Capacity', aggfunc='sum') - - nodes=set([node[0:2] for node in n.buses.index[n.buses.carrier == "AC"]]) - - for carrier in ['coal', 'oil', 'uranium']: - n.add("Bus", - "EU " + carrier, - carrier=carrier) - - for node in nodes: - #if a country has more than one node, selects the first one - bus_selected=[bus for bus in n.buses.index[n.buses.carrier == "AC"] if bus[0:2]==node][0] - for generator,carrier in [("OCGT","gas"), - ("CCGT", "gas"), - ("coal", "coal"), - ("oil","oil"), - ("nuclear","uranium")]: - if node in df.index and not np.isnan(df.loc[node, generator]): - n.add("Link", - bus_selected + " " + generator +" <" + baseyear, - bus0="EU " + carrier, - bus1=bus_selected, - bus2="co2 atmosphere", - marginal_cost=costs.at[generator,'efficiency']*costs.at[generator,'VOM'], #NB: VOM is per MWel - capital_cost=costs.at[generator,'efficiency']*costs.at[generator,'fixed'], #NB: fixed cost is per MWel - p_nom=df.loc[node, generator], - efficiency=costs.at[generator,'efficiency'], - efficiency2=costs.at[carrier,'CO2 intensity']) + values='Capacity', aggfunc='sum') - for generator in ['solar', 'onwind', 'offwind']: - try: - if not np.isnan(df.loc[node, generator]): - if generator =='offwind': - p_max_pu=n.generators_t.p_max_pu[bus_selected + ' offwind-ac'] - else: - p_max_pu=n.generators_t.p_max_pu[bus_selected + ' ' + generator] - - n.add("Generator", - bus_selected + ' ' + generator +" <"+ baseyear, - bus=bus_selected, - carrier=generator, - p_nom=df.loc[node, generator], - marginal_cost=costs.at[generator,'VOM'], - capital_cost=costs.at[generator,'fixed'], - efficiency=costs.at[generator, 'efficiency'], - p_max_pu=p_max_pu) - except: - print("No capacity installed before base year of " + generator + " is alive in node " + node) - - # delete generators if their lifetime is over and p_nom=0 - n.mremove("Generator", [index for index in n.generators.index.to_list() if '<'+baseyear in index and n.generators.p_nom[index]==0]) - n.mremove("Link", [index for index in n.links.index.to_list() if '<'+baseyear in index and n.links.p_nom[index]==0]) -def add_heating_capacities_installed_before_baseyear(n, year, baseyear, ashp_cop, gshp_cop, time_dep_hp_cop, costs, default_lifetime): + for node in nodes: + #if a country has more than one node, selects the first one + bus_selected=[bus for bus in n.buses.index[n.buses.carrier == "AC"] if bus[0:2]==node][0] + for generator,carrier in [("OCGT","gas"), + ("CCGT", "gas"), + ("coal", "coal"), + ("oil","oil"), + ("nuclear","uranium")]: + try: + if node in df.index and not np.isnan(df.loc[node, generator]): + n.add("Link", + bus_selected + " " + generator +"-" + grouping_year, + bus0="EU " + carrier, + bus1=bus_selected, + bus2="co2 atmosphere", + marginal_cost=costs.at[generator,'efficiency']*costs.at[generator,'VOM'], #NB: VOM is per MWel + capital_cost=costs.at[generator,'efficiency']*costs.at[generator,'fixed'], #NB: fixed cost is per MWel + p_nom=df.loc[node, generator], + efficiency=costs.at[generator,'efficiency'], + efficiency2=costs.at[carrier,'CO2 intensity'], + build_year=int(grouping_year), + lifetime=costs.at[generator,'lifetime']) + except: + print("No capacity installed around " + grouping_year + " of " + generator + " in node " + node) + + for generator in ['solar', 'onwind', 'offwind']: + try: + if not np.isnan(df.loc[node, generator]): + if generator =='offwind': + p_max_pu=n.generators_t.p_max_pu[bus_selected + ' offwind-ac'] + else: + p_max_pu=n.generators_t.p_max_pu[bus_selected + ' ' + generator] + + n.add("Generator", + bus_selected + ' ' + generator +"-"+ grouping_year, + bus=bus_selected, + carrier=generator, + p_nom=df.loc[node, generator], + marginal_cost=costs.at[generator,'VOM'], + capital_cost=costs.at[generator,'fixed'], + efficiency=costs.at[generator, 'efficiency'], + p_max_pu=p_max_pu, + build_year=int(grouping_year), + lifetime=costs.at[generator,'lifetime']) + except: + print("No capacity installed around " + grouping_year + " of " + generator + " in node " + node) + + # delete generators if their lifetime is over and p_nom=0 + n.mremove("Generator", [index for index in n.generators.index.to_list() if grouping_year in index and n.generators.p_nom[index] < snakemake.config['existing_capacities']['threshold_capacity']]) + n.mremove("Link", [index for index in n.links.index.to_list() if grouping_year in index and n.links.p_nom[index] < snakemake.config['existing_capacities']['threshold_capacity']]) + +def add_heating_capacities_installed_before_baseyear(n, baseyear, grouping_years, ashp_cop, gshp_cop, time_dep_hp_cop, costs, default_lifetime): """ Parameters ---------- n : network - year : capacity that fulfills YearDecomissioning > year is added - baseyear : capacity name will be e.g. "gas boiler