From 2b9ac511b57a4274d091e37872a51f9dddeda8f4 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 09:34:45 +0200 Subject: [PATCH 01/27] update mocksnakemake for testing --- scripts/prepare_sector_network.py | 57 ++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 6e79e897..e068fd02 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1736,7 +1736,7 @@ def remove_h2_network(n): capital_cost=h2_capital_cost) - +#%% if __name__ == "__main__": # Detect running outside of snakemake and mock snakemake for testing if 'snakemake' not in globals(): @@ -1746,25 +1746,42 @@ def remove_h2_network(n): opts='', planning_horizons='2020', co2_budget_name='go', sector_opts='Co2L0-168H-T-H-B-I-solar3-dist1'), - input=dict(network='pypsa-eur/networks/{network}_s{simpl}_{clusters}_ec_lv{lv}_{opts}.nc', - timezone_mappings='pypsa-eur-sec/data/timezone_mappings.csv', - co2_budget='pypsa-eur-sec/data/co2_budget.csv', - clustered_pop_layout='pypsa-eur-sec/resources/pop_layout_{network}_s{simpl}_{clusters}.csv', - costs='technology-data/outputs/costs_{planning_horizons}.csv', - profile_offwind_ac='pypsa-eur/resources/profile_offwind-ac.nc', - profile_offwind_dc='pypsa-eur/resources/profile_offwind-dc.nc', - clustermaps="pypsa-eur/resources/clustermaps_{network}_s{simpl}_{clusters}.h5", - 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', - solar_thermal_total='pypsa-eur-sec/resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc', - energy_totals_name='pypsa-eur-sec/data/energy_totals.csv', - heat_demand_total='pypsa-eur-sec/resources/heat_demand_total_{network}_s{simpl}_{clusters}.nc', - heat_profile='pypsa-eur-sec/data/heat_load_profile_BDEW.csv', - transport_name='pypsa-eur-sec/data/transport_data.csv', - temp_air_total='pypsa-eur-sec/resources/temp_air_total_{network}_s{simpl}_{clusters}.nc', - co2_totals_name='pypsa-eur-sec/data/co2_totals.csv', - biomass_potentials='pypsa-eur-sec/data/biomass_potentials.csv', - industrial_demand='pypsa-eur-sec/resources/industrial_demand_{network}_s{simpl}_{clusters}.csv',), + input=dict( + network='../pypsa-eur/networks/{network}_s{simpl}_{clusters}_ec_lv{lv}_{opts}.nc', + energy_totals_name='resources/energy_totals.csv', + co2_totals_name='resources/co2_totals.csv', + transport_name='resources/transport_data.csv', + biomass_potentials='resources/biomass_potentials.csv', + timezone_mappings='data/timezone_mappings.csv', + heat_profile="data/heat_load_profile_BDEW.csv", + costs="../technology-data/outputs/costs_{planning_horizons}.csv", + h2_cavern = "data/hydrogen_salt_cavern_potentials.csv", + co2_budget="data/co2_budget.csv", + profile_offwind_ac="../pypsa-eur/resources/profile_offwind-ac.nc", + profile_offwind_dc="../pypsa-eur/resources/profile_offwind-dc.nc", + clustermaps='../pypsa-eur/resources/clustermaps_{network}_s{simpl}_{clusters}.h5', + clustered_pop_layout="resources/pop_layout_{network}_s{simpl}_{clusters}.csv", + simplified_pop_layout="resources/pop_layout_{network}_s{simpl}.csv", + industrial_demand="resources/industrial_energy_demand_{network}_s{simpl}_{clusters}.csv", + heat_demand_urban="resources/heat_demand_urban_{network}_s{simpl}_{clusters}.nc", + heat_demand_rural="resources/heat_demand_rural_{network}_s{simpl}_{clusters}.nc", + heat_demand_total="resources/heat_demand_total_{network}_s{simpl}_{clusters}.nc", + temp_soil_total="resources/temp_soil_total_{network}_s{simpl}_{clusters}.nc", + temp_soil_rural="resources/temp_soil_rural_{network}_s{simpl}_{clusters}.nc", + temp_soil_urban="resources/temp_soil_urban_{network}_s{simpl}_{clusters}.nc", + temp_air_total="resources/temp_air_total_{network}_s{simpl}_{clusters}.nc", + temp_air_rural="resources/temp_air_rural_{network}_s{simpl}_{clusters}.nc", + temp_air_urban="resources/temp_air_urban_{network}_s{simpl}_{clusters}.nc", + cop_soil_total="resources/cop_soil_total_{network}_s{simpl}_{clusters}.nc", + cop_soil_rural="resources/cop_soil_rural_{network}_s{simpl}_{clusters}.nc", + cop_soil_urban="resources/cop_soil_urban_{network}_s{simpl}_{clusters}.nc", + cop_air_total="resources/cop_air_total_{network}_s{simpl}_{clusters}.nc", + cop_air_rural="resources/cop_air_rural_{network}_s{simpl}_{clusters}.nc", + cop_air_urban="resources/cop_air_urban_{network}_s{simpl}_{clusters}.nc", + solar_thermal_total="resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc", + solar_thermal_urban="resources/solar_thermal_urban_{network}_s{simpl}_{clusters}.nc", + solar_thermal_rural="resources/solar_thermal_rural_{network}_s{simpl}_{clusters}.nc", + ), output=['pypsa-eur-sec/results/test/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_{planning_horizons}.nc'] ) import yaml From b4f1c570d62de5e60fe0689cdaa5a51113cb33aa Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 10:13:02 +0200 Subject: [PATCH 02/27] move input data path for emobility/transport data to snakemake input --- scripts/prepare_sector_network.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index e068fd02..31feaa21 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -449,8 +449,8 @@ def prepare_data(network): ## Get overall demand curve for all vehicles - dir_name = "data/emobility/" - traffic = pd.read_csv(os.path.join(dir_name,"KFZ__count"),skiprows=2)["count"] + traffic = pd.read_csv(snakemake.input.traffic_data + "KFZ__count", + skiprows=2)["count"] #Generate profiles transport_shape = generate_periodic_profiles(dt_index=network.snapshots.tz_localize("UTC"), @@ -504,7 +504,8 @@ def prepare_data(network): ## derive plugged-in availability for PKW's (cars) - traffic = pd.read_csv(os.path.join(dir_name,"Pkw__count"),skiprows=2)["count"] + traffic = pd.read_csv(snakemake.input.traffic_data + "Pkw__count", + skiprows=2)["count"] avail_max = 0.95 @@ -1780,6 +1781,7 @@ def remove_h2_network(n): cop_air_urban="resources/cop_air_urban_{network}_s{simpl}_{clusters}.nc", solar_thermal_total="resources/solar_thermal_total_{network}_s{simpl}_{clusters}.nc", solar_thermal_urban="resources/solar_thermal_urban_{network}_s{simpl}_{clusters}.nc", + traffic_data = "data/emobility/", solar_thermal_rural="resources/solar_thermal_rural_{network}_s{simpl}_{clusters}.nc", ), output=['pypsa-eur-sec/results/test/prenetworks/{network}_s{simpl}_{clusters}_lv{lv}__{sector_opts}_{co2_budget_name}_{planning_horizons}.nc'] From cdaa4587ad8bbf2dea2c2f09bf859d4831c2c5c8 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 12:59:19 +0200 Subject: [PATCH 03/27] Snakefile: add transport input data to rule prepare_sector_network --- Snakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Snakefile b/Snakefile index 57792c24..7e4d6afa 100644 --- a/Snakefile +++ b/Snakefile @@ -305,6 +305,7 @@ rule prepare_sector_network: heat_demand_urban="resources/heat_demand_urban_{network}_s{simpl}_{clusters}.nc", heat_demand_rural="resources/heat_demand_rural_{network}_s{simpl}_{clusters}.nc", heat_demand_total="resources/heat_demand_total_{network}_s{simpl}_{clusters}.nc", + traffic_data = "data/emobility/", temp_soil_total="resources/temp_soil_total_{network}_s{simpl}_{clusters}.nc", temp_soil_rural="resources/temp_soil_rural_{network}_s{simpl}_{clusters}.nc", temp_soil_urban="resources/temp_soil_urban_{network}_s{simpl}_{clusters}.nc", From 50b4e822c49b09f299bad17c52295ab887c26197 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 13:00:41 +0200 Subject: [PATCH 04/27] config: add missing color for industry electricity --- config.default.yaml | 1 + config.myopic.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config.default.yaml b/config.default.yaml index 3498a33b..ee4bb885 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -320,6 +320,7 @@ plotting: "today" : "#D2691E" "shipping" : "#6495ED" "electricity distribution grid" : "#333333" + 'industry electricity': "black" nice_names: # OCGT: "Gas" # OCGT marginal: "Gas (marginal)" diff --git a/config.myopic.yaml b/config.myopic.yaml index 454a8525..8ad7f1b3 100644 --- a/config.myopic.yaml +++ b/config.myopic.yaml @@ -320,6 +320,7 @@ plotting: "today" : "#D2691E" "shipping" : "#6495ED" "electricity distribution grid" : "#333333" + 'industry electricity': "black" nice_names: # OCGT: "Gas" # OCGT marginal: "Gas (marginal)" From 2dd97c39edeb73a054f402e359c0c529bf122a06 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 13:45:34 +0200 Subject: [PATCH 05/27] Snakefile: add path to biomass transport cost in rule prepare_sector_network --- Snakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Snakefile b/Snakefile index 7e4d6afa..0b8cacb0 100644 --- a/Snakefile +++ b/Snakefile @@ -291,6 +291,7 @@ rule prepare_sector_network: co2_totals_name='resources/co2_totals.csv', transport_name='resources/transport_data.csv', biomass_potentials='resources/biomass_potentials.csv', + biomass_transport='data/biomass/biomass_transport_costs.csv', timezone_mappings='data/timezone_mappings.csv', heat_profile="data/heat_load_profile_BDEW.csv", costs=config['costs_dir'] + "costs_{planning_horizons}.csv", From 651a7ff693d864400c860adba041d90f72c3e59d Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 13:46:39 +0200 Subject: [PATCH 06/27] add biomass transport, split potential for solid biomass to nodes, add costs for upgrading biogas to gas --- scripts/prepare_sector_network.py | 97 ++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 16 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 31feaa21..282465f1 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -51,6 +51,33 @@ def add_lifetime_wind_solar(n): n.generators.loc[[index for index in n.generators.index.to_list() if carrier in index], 'lifetime']=costs.at[carrier_name,'lifetime'] + +def create_network_topology(n, prefix): + """ + create a network topology as the electric network, + returns a pandas dataframe with bus0, bus1 and length + """ + + topo = pd.DataFrame(columns=["bus0", "bus1", "length"]) + connector = " -> " + attrs = ["bus0", "bus1", "length"] + + candidates = pd.concat([n.lines[attrs], + n.links.loc[n.links.carrier == "DC", attrs]]) + + positive_order = candidates.bus0 < candidates.bus1 + candidates_p = candidates[positive_order] + candidates_n = (candidates[~ positive_order] + .rename(columns={"bus0": "bus1", "bus1": "bus0"})) + candidates = pd.concat((candidates_p, candidates_n), sort=False) + + topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean() + topo.rename(index=lambda x: prefix + topo.at[x, "bus0"] + + connector + topo.at[x, "bus1"], + inplace=True) + return topo + + def update_wind_solar_costs(n,costs): """ Update costs for wind and solar generators added with pypsa-eur to those @@ -1354,6 +1381,16 @@ def add_biomass(network): biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0) + # costs for biomass transport + transport_costs = pd.read_csv(snakemake.input.biomass_transport, + index_col=0) + + # potential per node distributed within country by population + biomass_pot_node = (biomass_potentials.loc[pop_layout.ct] + .set_index(pop_layout.index) + .mul(pop_layout.fraction, axis="index")) + + network.add("Carrier","biogas") network.add("Carrier","solid biomass") @@ -1363,8 +1400,7 @@ def add_biomass(network): carrier="biogas") network.madd("Bus", - ["EU solid biomass"], - location="EU", + biomass_pot_node.index + " solid biomass", carrier="solid biomass") network.madd("Store", @@ -1376,12 +1412,12 @@ def add_biomass(network): e_initial=biomass_potentials.loc[cts,"biogas"].sum()) network.madd("Store", - ["EU solid biomass"], - bus="EU solid biomass", + biomass_pot_node.index + " solid biomass", + bus=biomass_pot_node.index + " solid biomass", carrier="solid biomass", - e_nom=biomass_potentials.loc[cts,"solid biomass"].sum(), - marginal_cost=costs.at['solid biomass','fuel'], - e_initial=biomass_potentials.loc[cts,"solid biomass"].sum()) + e_nom=biomass_pot_node["solid biomass"].values, + marginal_cost=costs.at['solid biomass', 'fuel'], + e_initial=biomass_pot_node["solid biomass"].values) network.madd("Link", ["biogas to gas"], @@ -1389,9 +1425,38 @@ def add_biomass(network): bus1="EU gas", bus2="co2 atmosphere", carrier="biogas to gas", + capital_cost=costs.loc["biogas upgrading", "fixed"], + marginal_cost=costs.loc["biogas upgrading", "VOM"], efficiency2=-costs.at['gas','CO2 intensity'], p_nom_extendable=True) + # add biomass transport + biomass_transport = create_network_topology(n, "Biomass transport ") + + # make transport in both directions + df = biomass_transport.copy() + df["bus1"] = biomass_transport.bus0 + df["bus0"] = biomass_transport.bus1 + df.rename(index=lambda x: "Biomass transport " + df.at[x, "bus0"] + + " -> " + df.at[x, "bus1"], inplace=True) + biomass_transport = pd.concat([biomass_transport, df]) + + # costs + bus0_costs = biomass_transport.bus0.apply(lambda x: transport_costs.loc[x[:2]]) + bus1_costs = biomass_transport.bus1.apply(lambda x: transport_costs.loc[x[:2]]) + biomass_transport["costs"] = pd.concat([bus0_costs, bus1_costs], + axis=1).mean(axis=1) + + network.madd("Link", + biomass_transport.index, + bus0=biomass_transport.bus0 + " solid biomass", + bus1=biomass_transport.bus1 + " solid biomass", + p_nom_extendable=True, + length=biomass_transport.length.values, + marginal_cost=biomass_transport.costs * biomass_transport.length.values, + capital_cost=1, + carrier="solid biomass transport") + #AC buses with district heating urban_central = network.buses.index[network.buses.carrier == "urban central heat"] @@ -1400,7 +1465,7 @@ def add_biomass(network): network.madd("Link", urban_central + " urban central solid biomass CHP electric", - bus0="EU solid biomass", + bus0=urban_central + " solid biomass", bus1=urban_central, carrier="urban central solid biomass CHP electric", p_nom_extendable=True, @@ -1415,7 +1480,7 @@ def add_biomass(network): network.madd("Link", urban_central + " urban central solid biomass CHP heat", - bus0="EU solid biomass", + bus0=urban_central + " solid biomass", bus1=urban_central + " urban central heat", carrier="urban central solid biomass CHP heat", p_nom_extendable=True, @@ -1425,7 +1490,7 @@ def add_biomass(network): network.madd("Link", urban_central + " urban central solid biomass CHP CCS electric", - bus0="EU solid biomass", + bus0=urban_central + " solid biomass", bus1=urban_central, bus2="co2 atmosphere", bus3="co2 stored", @@ -1443,7 +1508,7 @@ def add_biomass(network): network.madd("Link", urban_central + " urban central solid biomass CHP CCS heat", - bus0="EU solid biomass", + bus0=urban_central + " solid biomass", bus1=urban_central + " urban central heat", bus2="co2 atmosphere", bus3="co2 stored", @@ -1467,7 +1532,6 @@ def add_industry(network): index_col=0) solid_biomass_by_country = industrial_demand["solid biomass"].groupby(pop_layout.ct).sum() - countries = solid_biomass_by_country.index network.madd("Bus", ["solid biomass for industry"], @@ -1481,16 +1545,16 @@ def add_industry(network): p_set=solid_biomass_by_country.sum()/8760.) network.madd("Link", - ["solid biomass for industry"], - bus0="EU solid biomass", + nodes + " solid biomass for industry", + bus0=nodes + " solid biomass", bus1="solid biomass for industry", carrier="solid biomass for industry", p_nom_extendable=True, efficiency=1.) network.madd("Link", - ["solid biomass for industry CCS"], - bus0="EU solid biomass", + nodes + " solid biomass for industry CCS", + bus0=nodes + " solid biomass", bus1="solid biomass for industry", bus2="co2 atmosphere", bus3="co2 stored", @@ -1753,6 +1817,7 @@ def remove_h2_network(n): co2_totals_name='resources/co2_totals.csv', transport_name='resources/transport_data.csv', biomass_potentials='resources/biomass_potentials.csv', + biomass_transport='data/biomass/biomass_transport_costs.csv', timezone_mappings='data/timezone_mappings.csv', heat_profile="data/heat_load_profile_BDEW.csv", costs="../technology-data/outputs/costs_{planning_horizons}.csv", From 3944e19c515d84ab622291a7a41ffb4d97458ae5 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 13:48:05 +0200 Subject: [PATCH 07/27] config.yaml: add color for biomass transport --- config.default.yaml | 1 + config.myopic.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config.default.yaml b/config.default.yaml index ee4bb885..88697285 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -321,6 +321,7 @@ plotting: "shipping" : "#6495ED" "electricity distribution grid" : "#333333" 'industry electricity': "black" + "solid biomass transport": "green" nice_names: # OCGT: "Gas" # OCGT marginal: "Gas (marginal)" diff --git a/config.myopic.yaml b/config.myopic.yaml index 8ad7f1b3..e1a2a15f 100644 --- a/config.myopic.yaml +++ b/config.myopic.yaml @@ -321,6 +321,7 @@ plotting: "shipping" : "#6495ED" "electricity distribution grid" : "#333333" 'industry electricity': "black" + "solid biomass transport": "green" nice_names: # OCGT: "Gas" # OCGT marginal: "Gas (marginal)" From ae9c0d9530b09634579340ad7a10435b29eb1cd9 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 14:08:17 +0200 Subject: [PATCH 08/27] prepare_sector_network: split industry demand for biomass to single nodes --- scripts/prepare_sector_network.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 282465f1..f75ed459 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1531,31 +1531,31 @@ def add_industry(network): industrial_demand = 1e6*pd.read_csv(snakemake.input.industrial_demand, index_col=0) - solid_biomass_by_country = industrial_demand["solid biomass"].groupby(pop_layout.ct).sum() + solid_biomass_by_country = industrial_demand["solid biomass"] network.madd("Bus", - ["solid biomass for industry"], - location="EU", + industrial_demand.index + " solid biomass for industry", carrier="solid biomass for industry") + network.madd("Load", - ["solid biomass for industry"], - bus="solid biomass for industry", + industrial_demand.index + " solid biomass for industry", + bus=industrial_demand.index + " solid biomass for industry", carrier="solid biomass for industry", - p_set=solid_biomass_by_country.sum()/8760.) + p_set=industrial_demand["solid biomass"]/8760.) network.madd("Link", - nodes + " solid biomass for industry", - bus0=nodes + " solid biomass", - bus1="solid biomass for industry", + industrial_demand.index + " solid biomass for industry", + bus0=industrial_demand.index + " solid biomass", + bus1=industrial_demand.index + " solid biomass for industry", carrier="solid biomass for industry", p_nom_extendable=True, efficiency=1.) network.madd("Link", - nodes + " solid biomass for industry CCS", - bus0=nodes + " solid biomass", - bus1="solid biomass for industry", + industrial_demand.index + " solid biomass for industry CCS", + bus0=industrial_demand.index + " solid biomass", + bus1=industrial_demand.index + " solid biomass for industry", bus2="co2 atmosphere", bus3="co2 stored", carrier="solid biomass for industry CCS", From 13cae7d66f17555005c85a87f0bfafeef4fb7e4e Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Tue, 20 Oct 2020 14:19:07 +0200 Subject: [PATCH 09/27] drop nan values to avoid runtime error when removing todays electricity demand --- scripts/prepare_sector_network.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index f75ed459..b33cfcea 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1701,7 +1701,7 @@ def add_industry(network): p_set=industrial_demand.loc[nodes,"low-temperature heat"]/8760.) #remove today's industrial electricity demand by scaling down total electricity demand - for ct in n.buses.country.unique(): + for ct in n.buses.country.dropna().unique(): loads = n.loads.index[(n.loads.index.str[:2] == ct) & (n.loads.carrier == "electricity")] factor = 1 - industrial_demand.loc[loads,"current electricity"].sum()/n.loads_t.p_set[loads].sum().sum() n.loads_t.p_set[loads] *= factor @@ -1800,6 +1800,9 @@ def remove_h2_network(n): carrier="H2 Store", capital_cost=h2_capital_cost) +# def remove_biomass_transport(n): + + #%% if __name__ == "__main__": From b4fbb47d29ab7ea1719b9f96ed1f11d94f458966 Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 21 Oct 2020 07:21:09 +0200 Subject: [PATCH 10/27] fix bug when adding biomass industry demand, add option to remove biomass transport and have only single EU biomass node --- scripts/prepare_sector_network.py | 78 ++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index b33cfcea..ddcdc85b 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1542,7 +1542,8 @@ def add_industry(network): industrial_demand.index + " solid biomass for industry", bus=industrial_demand.index + " solid biomass for industry", carrier="solid biomass for industry", - p_set=industrial_demand["solid biomass"]/8760.) + p_set=(industrial_demand["solid biomass"] + .rename(index=lambda x: x + " solid biomass for industry"))/8760.) network.madd("Link", industrial_demand.index + " solid biomass for industry", @@ -1800,8 +1801,76 @@ def remove_h2_network(n): carrier="H2 Store", capital_cost=h2_capital_cost) -# def remove_biomass_transport(n): +def remove_biomass_transport(n): + + print("no transport of solid biomass considered") + + # remove country specific biomass buses + n.buses = n.buses[~n.buses.carrier.str.contains("biomass")] + # biomass potential + biomass_pot = n.stores[n.stores.carrier=="solid biomass"].e_nom.sum() + # remove biomass store per country + n.stores = n.stores[n.stores.carrier!="solid biomass"] + # remove biomass transport links + n.links = n.links[n.links.carrier!="solid biomass transport"] + # total industry demand for biomass + biomass_demand = n.loads[n.loads.carrier=="solid biomass for industry"].p_set.sum() + # remove industry demand + n.loads = n.loads[n.loads.carrier!="solid biomass for industry"] + # drop transport and industry links + n.links = n.links[~n.links.carrier.isin(['solid biomass transport', + 'solid biomass for industry', + 'solid biomass for industry CCS'])] + + # add back EU bus + load + store + industry links + n.madd("Bus", + ["EU solid biomass"], + location="EU", + carrier="solid biomass") + n.madd("Bus", + ["solid biomass for industry"], + location="EU", + carrier="solid biomass for industry") + + n.madd("Load", + ["solid biomass for industry"], + bus="solid biomass for industry", + carrier="solid biomass for industry", + p_set=biomass_demand) + + n.madd("Store", + ["EU solid biomass"], + bus="EU solid biomass", + carrier="solid biomass", + e_nom=biomass_pot, + marginal_cost=costs.at['solid biomass','fuel'], + e_initial=biomass_pot) + + n.madd("Link", + ["solid biomass for industry"], + bus0="EU solid biomass", + bus1="solid biomass for industry", + carrier="solid biomass for industry", + p_nom_extendable=True, + efficiency=1.) + + n.madd("Link", + ["solid biomass for industry CCS"], + bus0="EU solid biomass", + bus1="solid biomass for industry", + bus2="co2 atmosphere", + bus3="co2 stored", + carrier="solid biomass for industry CCS", + p_nom_extendable=True, + capital_cost=costs.at["industry CCS","fixed"]*costs.at['solid biomass','CO2 intensity']*8760, #8760 converts EUR/(tCO2/a) to EUR/(tCO2/h) + efficiency=0.9, + efficiency2=-costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], + efficiency3=costs.at['solid biomass','CO2 intensity']*options["ccs_fraction"], + lifetime=costs.at['industry CCS','lifetime']) + + # set CHP buses from country to single EU bus + n.links.loc[n.links.carrier.str.contains("biomass CHP"), "bus0"] = "EU solid biomass" #%% @@ -1916,6 +1985,8 @@ def remove_h2_network(n): if o[:4] == "dist": snakemake.config["sector"]['electricity_distribution_grid'] = True snakemake.config["sector"]['electricity_distribution_grid_cost_factor'] = float(o[4:].replace("p",".").replace("m","-")) + if o == "biomasstransport": + options["biomass_transport"] = True nodal_energy_totals, heat_demand, ashp_cop, gshp_cop, solar_thermal, transport, avail_profile, dsm_profile, co2_totals, nodal_transport_data = prepare_data(n) @@ -1943,6 +2014,9 @@ def remove_h2_network(n): if "noH2network" in opts: remove_h2_network(n) + if not options["biomass_transport"]: + remove_biomass_transport(n) + for o in opts: m = re.match(r'^\d+h$', o, re.IGNORECASE) if m is not None: From 4f2135a43a45e0d9d491d05167691e85cbd24efc Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Wed, 21 Oct 2020 07:23:52 +0200 Subject: [PATCH 11/27] add option for solid biomass transport --- config.default.yaml | 1 + config.myopic.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/config.default.yaml b/config.default.yaml index 88697285..6d7c8998 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -104,6 +104,7 @@ sector: 'electricity_grid_connection' : True # only applies to onshore wind and utility PV 'gas_distribution_grid' : True 'gas_distribution_grid_cost_factor' : 1.0 #multiplies cost in data/costs.csv + 'biomass_transport': False # biomass potential per country + transport between countries costs: year: 2030 diff --git a/config.myopic.yaml b/config.myopic.yaml index e1a2a15f..7159607b 100644 --- a/config.myopic.yaml +++ b/config.myopic.yaml @@ -104,6 +104,7 @@ sector: 'electricity_grid_connection' : True # only applies to onshore wind and utility PV 'gas_distribution_grid' : True 'gas_distribution_grid_cost_factor' : 1.0 #multiplies cost in data/costs.csv + 'biomass_transport': False # biomass potential per country + transport between countries costs: year: 2030 From 846f57fde6433770199fae31145575c14e97358a Mon Sep 17 00:00:00 2001 From: lisazeyen Date: Fri, 11 Jun 2021 14:36:56 +0200 Subject: [PATCH 12/27] add script for biomass transport costs. Converts JRC-EU-Times transport cost' --- scripts/convert_biomass_transport_costs.py | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 scripts/convert_biomass_transport_costs.py diff --git a/scripts/convert_biomass_transport_costs.py b/scripts/convert_biomass_transport_costs.py new file mode 100644 index 00000000..57bd455f --- /dev/null +++ b/scripts/convert_biomass_transport_costs.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Thu Mar 12 19:11:20 2020 + +reads biomass transport costs for different countries of the JRC report + + "The JRC-EU-TIMES model. + Bioenergy potentials + for EU and neighbouring countries." + (2015) + +converts them from units 'EUR per km/ton' -> 'EUR/ (km MWh)' + +assuming as an approximation energy content of wood pellets + +@author: bw0928 +""" + +import pandas as pd +from tabula import read_pdf +import numpy as np + +# read pdf file +df_list = read_pdf("biomass potentials in europe_web rev.pdf", + pages="145-147", + multiple_tables=True) +energy_content = 4.8 # unit MWh/tonne (assuming wood pellets) +# %% +columns = ["Komponente", "Größe", "Einheit", 2020, 2025, 2030, 2035, 2040, + 2045, 2050] +countries = df_list[0][0].iloc[6:].rename(index=lambda x: x+1) + +# supply chain 1 +df = df_list[1].copy().rename(index=countries.to_dict()) +df.rename(columns= df.iloc[:6].apply(lambda col: col.str.cat(sep=" "), + axis=0).to_dict(), inplace=True) +df = df.iloc[6:] +df.loc[6]=df.loc[6].str.replace("€", "EUR") + +# supply chain 2 +df2 = df_list[2].copy().rename(index=countries.to_dict()) +df2.rename(columns= df2.iloc[:6].apply(lambda col: col.str.cat(sep=" "), + axis=0).to_dict(), inplace=True) +df2 = df2.iloc[6:] +df2.loc[6]=df2.loc[6].str.replace("€", "EUR") + +#%% +df.to_csv("biomass_transport_costs_supply_chain1.csv") +df2.to_csv("biomass_transport_costs_supply_chain2.csv") + +transport_costs = pd.concat([df['per km/ton'], df2['per km/ton']],axis=1).drop(6) +transport_costs = transport_costs.astype(float, errors="ignore").mean(axis=1) + +# convert unit to EUR/MWh +transport_costs /= energy_content +transport_costs = pd.DataFrame(transport_costs, columns=["cost [EUR/(km MWh)]"]) +# rename +transport_costs.rename({"UK": "GB", "XK": "KO", "EL": "GR"}, inplace=True) +# add missing Norway +transport_costs.loc["NO"] = transport_costs.loc["SE"] +transport_costs.to_csv("biomass_transport_final.csv") From fa8f7887f94af007d49b07a6158ece8250413a25 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 2 Jul 2021 09:31:41 +0200 Subject: [PATCH 13/27] Update scripts/prepare_sector_network.py --- scripts/prepare_sector_network.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index ddcdc85b..c54e2a2e 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -72,9 +72,7 @@ def create_network_topology(n, prefix): candidates = pd.concat((candidates_p, candidates_n), sort=False) topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean() - topo.rename(index=lambda x: prefix + topo.at[x, "bus0"] - + connector + topo.at[x, "bus1"], - inplace=True) + topo.index = topo.apply(lambda x: prefix + x.bus0 + connector + x.bus1, axis=1) return topo From 4ebbd294ec47d48649ae2e3f401504e4acb0b771 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 2 Jul 2021 09:34:24 +0200 Subject: [PATCH 14/27] apply suggestions from code review --- scripts/prepare_sector_network.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index c54e2a2e..13b4751e 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -52,14 +52,12 @@ def add_lifetime_wind_solar(n): if carrier in index], 'lifetime']=costs.at[carrier_name,'lifetime'] -def create_network_topology(n, prefix): +def create_network_topology(n, prefix, connector=" -> "): """ create a network topology as the electric network, returns a pandas dataframe with bus0, bus1 and length """ - topo = pd.DataFrame(columns=["bus0", "bus1", "length"]) - connector = " -> " attrs = ["bus0", "bus1", "length"] candidates = pd.concat([n.lines[attrs], From fd1121af4a45f203ce97bd6c8fe1af23a9165f4e Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Fri, 2 Jul 2021 11:07:35 +0200 Subject: [PATCH 15/27] add rule for build_biomass_transport_costs --- Snakefile | 18 +++++- scripts/build_biomass_transport_costs.py | 70 ++++++++++++++++++++++ scripts/convert_biomass_transport_costs.py | 62 ------------------- 3 files changed, 85 insertions(+), 65 deletions(-) create mode 100644 scripts/build_biomass_transport_costs.py delete mode 100644 scripts/convert_biomass_transport_costs.py diff --git a/Snakefile b/Snakefile index aba8ff71..45c33da9 100644 --- a/Snakefile +++ b/Snakefile @@ -180,6 +180,18 @@ rule build_biomass_potentials: script: 'scripts/build_biomass_potentials.py' +rule build_biomass_transport_costs: + input: "data/biomass/biomass potentials in europe_web rev.pdf" + output: + supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", + supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", + transport_costs="resources/biomass_transport_costs.csv", + threads: 1 + resources: mem_mb=1000 + benchmark: "benchmarks/build_biomass_transport_costs" + script: 'scripts/build_biomass_transport_costs.py' + + rule build_ammonia_production: input: usgs="data/myb1-2017-nitro.xls" @@ -321,10 +333,10 @@ rule prepare_sector_network: energy_totals_name='resources/energy_totals.csv', co2_totals_name='resources/co2_totals.csv', transport_name='resources/transport_data.csv', - traffic_data_KFZ = "data/emobility/KFZ__count", - traffic_data_Pkw = "data/emobility/Pkw__count", + traffic_data_KFZ="data/emobility/KFZ__count", + traffic_data_Pkw="data/emobility/Pkw__count", biomass_potentials='resources/biomass_potentials.csv', - biomass_transport='data/biomass/biomass_transport_costs.csv', + biomass_transport="resources/biomass_transport_costs.csv", heat_profile="data/heat_load_profile_BDEW.csv", costs=CDIR + "costs_{planning_horizons}.csv", profile_offwind_ac=pypsaeur("resources/profile_offwind-ac.nc"), diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py new file mode 100644 index 00000000..ee681566 --- /dev/null +++ b/scripts/build_biomass_transport_costs.py @@ -0,0 +1,70 @@ +""" +Reads biomass transport costs for different countries of the JRC report + + "The JRC-EU-TIMES model. + Bioenergy potentials + for EU and neighbouring countries." + (2015) + +converts them from units 'EUR per km/ton' -> 'EUR/ (km MWh)' + +assuming as an approximation energy content of wood pellets + +@author: bw0928 +""" + +import pandas as pd +import tabula as tbl + +ENERGY_CONTENT = 4.8 # unit MWh/tonne (assuming wood pellets) + + +def build_biomass_transport_costs(): + + df_list = tbl.read_pdf( + snakemake.input[0], + pages="145-147", + multiple_tables=True, + ) + + countries = df_list[0][0].iloc[6:].rename(index=lambda x: x + 1) + + # supply chain 1 + df = df_list[1].copy().rename(index=countries.to_dict()) + df.rename( + columns=df.iloc[:6].apply(lambda col: col.str.cat(sep=" "), axis=0).to_dict(), + inplace=True, + ) + df = df.iloc[6:] + df.loc[6] = df.loc[6].str.replace("€", "EUR") + + # supply chain 2 + df2 = df_list[2].copy().rename(index=countries.to_dict()) + df2.rename( + columns=df2.iloc[:6].apply(lambda col: col.str.cat(sep=" "), axis=0).to_dict(), + inplace=True, + ) + df2 = df2.iloc[6:] + df2.loc[6] = df2.loc[6].str.replace("€", "EUR") + + df.to_csv(snakemake.output.supply_chain1) + df2.to_csv(snakemake.output.supply_chain1) + + transport_costs = pd.concat([df["per km/ton"], df2["per km/ton"]], axis=1).drop(6) + transport_costs = transport_costs.astype(float, errors="ignore").mean(axis=1) + + # convert unit to EUR/MWh + transport_costs /= ENERGY_CONTENT + transport_costs = pd.DataFrame(transport_costs, columns=["cost [EUR/(km MWh)]"]) + + # rename + transport_costs.rename({"UK": "GB", "XK": "KO", "EL": "GR"}, inplace=True) + + # add missing Norway + transport_costs.loc["NO"] = transport_costs.loc["SE"] + transport_costs.to_csv(snakemake.output.transport_costs) + + +if __name__ == "__main__": + + prepare_biomass_transport_costs() diff --git a/scripts/convert_biomass_transport_costs.py b/scripts/convert_biomass_transport_costs.py deleted file mode 100644 index 57bd455f..00000000 --- a/scripts/convert_biomass_transport_costs.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -Created on Thu Mar 12 19:11:20 2020 - -reads biomass transport costs for different countries of the JRC report - - "The JRC-EU-TIMES model. - Bioenergy potentials - for EU and neighbouring countries." - (2015) - -converts them from units 'EUR per km/ton' -> 'EUR/ (km MWh)' - -assuming as an approximation energy content of wood pellets - -@author: bw0928 -""" - -import pandas as pd -from tabula import read_pdf -import numpy as np - -# read pdf file -df_list = read_pdf("biomass potentials in europe_web rev.pdf", - pages="145-147", - multiple_tables=True) -energy_content = 4.8 # unit MWh/tonne (assuming wood pellets) -# %% -columns = ["Komponente", "Größe", "Einheit", 2020, 2025, 2030, 2035, 2040, - 2045, 2050] -countries = df_list[0][0].iloc[6:].rename(index=lambda x: x+1) - -# supply chain 1 -df = df_list[1].copy().rename(index=countries.to_dict()) -df.rename(columns= df.iloc[:6].apply(lambda col: col.str.cat(sep=" "), - axis=0).to_dict(), inplace=True) -df = df.iloc[6:] -df.loc[6]=df.loc[6].str.replace("€", "EUR") - -# supply chain 2 -df2 = df_list[2].copy().rename(index=countries.to_dict()) -df2.rename(columns= df2.iloc[:6].apply(lambda col: col.str.cat(sep=" "), - axis=0).to_dict(), inplace=True) -df2 = df2.iloc[6:] -df2.loc[6]=df2.loc[6].str.replace("€", "EUR") - -#%% -df.to_csv("biomass_transport_costs_supply_chain1.csv") -df2.to_csv("biomass_transport_costs_supply_chain2.csv") - -transport_costs = pd.concat([df['per km/ton'], df2['per km/ton']],axis=1).drop(6) -transport_costs = transport_costs.astype(float, errors="ignore").mean(axis=1) - -# convert unit to EUR/MWh -transport_costs /= energy_content -transport_costs = pd.DataFrame(transport_costs, columns=["cost [EUR/(km MWh)]"]) -# rename -transport_costs.rename({"UK": "GB", "XK": "KO", "EL": "GR"}, inplace=True) -# add missing Norway -transport_costs.loc["NO"] = transport_costs.loc["SE"] -transport_costs.to_csv("biomass_transport_final.csv") From d1298fa93d20f103de3c8a0d81d45f8e149d4df6 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 12 Jul 2021 12:31:18 +0200 Subject: [PATCH 16/27] use spatial namespace to manage biomass transport resolution --- scripts/build_biomass_transport_costs.py | 2 +- scripts/prepare_sector_network.py | 254 ++++++++++------------- 2 files changed, 109 insertions(+), 147 deletions(-) diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index ee681566..703d81ae 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -67,4 +67,4 @@ def build_biomass_transport_costs(): if __name__ == "__main__": - prepare_biomass_transport_costs() + build_biomass_transport_costs() diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index e77dac56..91bcc2e9 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -19,10 +19,43 @@ import logging logger = logging.getLogger(__name__) +from types import SimpleNamespace +spatial = SimpleNamespace() + + +def define_spatial(nodes): + """ + Namespace for spatial + + Parameters + ---------- + nodes : list-like + """ + + global spatial + global options + + spatial.nodes = nodes + + spatial.biomass = SimpleNamespace() + + if options["biomass_transport"]: + spatial.biomass.nodes = nodes + " solid biomass" + spatial.biomass.locations = nodes + spatial.biomass.industry = nodes + " solid biomass for industry" + spatial.biomass.industry_cc = nodes + " solid biomass for industry CC" + else: + spatial.biomass.nodes = ["EU solid biomass"] + spatial.biomass.locations = "EU" + spatial.biomass.industry = ["solid biomass for industry"] + spatial.biomass.industry_cc = ["solid biomass for industry CC"] + + spatial.biomass.df = pd.DataFrame(vars(spatial.biomass), index=nodes) + def emission_sectors_from_opts(opts): - sectors = ["electricity"] + sectors = ["electricity"] if "T" in opts: sectors += [ "rail non-elec", @@ -144,25 +177,50 @@ def add_lifetime_wind_solar(n, costs): n.generators.loc[gen_i, "lifetime"] = costs.at[carrier, 'lifetime'] -def create_network_topology(n, prefix, connector=" -> "): +def create_network_topology(n, prefix, connector=" -> ", bidirectional=True): """ - create a network topology as the electric network, - returns a pandas dataframe with bus0, bus1 and length + Create a network topology like the power transmission network. + + Parameters + ---------- + n : pypsa.Network + prefix : str + connector : str + bidirectional : bool, default True + True: one link for each connection + False: one link for each connection and direction (back and forth) + + Returns + ------- + pd.DataFrame with columns bus0, bus1 and length """ - attrs = ["bus0", "bus1", "length"] + ln_attrs = ["bus0", "bus1", "length"] + lk_attrs = ["bus0", "bus1", "length", "underwater_fraction"] - candidates = pd.concat([n.lines[attrs], - n.links.loc[n.links.carrier == "DC", attrs]]) + candidates = pd.concat([ + n.lines[ln_attrs], + n.links.loc[n.links.carrier == "DC", lk_attrs] + ]).fillna(0) positive_order = candidates.bus0 < candidates.bus1 candidates_p = candidates[positive_order] - candidates_n = (candidates[~ positive_order] - .rename(columns={"bus0": "bus1", "bus1": "bus0"})) - candidates = pd.concat((candidates_p, candidates_n), sort=False) + swap_buses = {"bus0": "bus1", "bus1": "bus0"} + candidates_n = candidates[~positive_order].rename(columns=swap_buses) + candidates = pd.concat([candidates_p, candidates_n]) + + def make_index(c): + return prefix + c.bus0 + connector + c.bus1 topo = candidates.groupby(["bus0", "bus1"], as_index=False).mean() - topo.index = topo.apply(lambda x: prefix + x.bus0 + connector + x.bus1, axis=1) + topo.index = topo.apply(make_index, axis=1) + + if not bidirectional: + topo_reverse = topo.copy() + topo_reverse.rename(columns=swap_buses, inplace=True) + topo_reverse.index = topo_reverse.apply(make_index, axis=1) + topo = topo.append(topo_reverse) + return topo @@ -1554,8 +1612,11 @@ def add_biomass(n, costs): biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0) - transport_costs = pd.read_csv(snakemake.input.biomass_transport, - index_col=0) + transport_costs = pd.read_csv( + snakemake.input.biomass_transport, + index_col=0, + squeeze=True + ) # potential per node distributed within country by population biomass_pot_node = (biomass_potentials.loc[pop_layout.ct] @@ -1572,7 +1633,8 @@ def add_biomass(n, costs): ) n.madd("Bus", - biomass_pot_node.index + " solid biomass", + spatial.biomass.nodes, + location=spatial.biomass.locations, carrier="solid biomass" ) @@ -1586,8 +1648,8 @@ def add_biomass(n, costs): ) n.madd("Store", - biomass_pot_node.index + " solid biomass", - bus=biomass_pot_node.index + " solid biomass", + spatial.biomass.nodes, + bus=spatial.biomass.nodes, carrier="solid biomass", e_nom=biomass_pot_node["solid biomass"].values, marginal_cost=costs.at['solid biomass', 'fuel'], @@ -1606,34 +1668,26 @@ def add_biomass(n, costs): p_nom_extendable=True ) - # add biomass transport - biomass_transport = create_network_topology(n, "Biomass transport ") - - # make transport in both directions - df = biomass_transport.copy() - df["bus1"] = biomass_transport.bus0 - df["bus0"] = biomass_transport.bus1 - df.rename(index=lambda x: "Biomass transport " + df.at[x, "bus0"] - + " -> " + df.at[x, "bus1"], inplace=True) - biomass_transport = pd.concat([biomass_transport, df]) - - # costs - bus0_costs = biomass_transport.bus0.apply(lambda x: transport_costs.loc[x[:2]]) - bus1_costs = biomass_transport.bus1.apply(lambda x: transport_costs.loc[x[:2]]) - biomass_transport["costs"] = pd.concat([bus0_costs, bus1_costs], - axis=1).mean(axis=1) + if options["biomass_transport"]: + + # add biomass transport + biomass_transport = create_network_topology(n, "biomass transport ", bidirectional=False) - n.madd("Link", - biomass_transport.index, - bus0=biomass_transport.bus0 + " solid biomass", - bus1=biomass_transport.bus1 + " solid biomass", - p_nom_extendable=True, - length=biomass_transport.length.values, - marginal_cost=biomass_transport.costs * biomass_transport.length.values, - capital_cost=1, - carrier="solid biomass transport" - ) + # costs + bus0_costs = biomass_transport.bus0.apply(lambda x: transport_costs[x[:2]]) + bus1_costs = biomass_transport.bus1.apply(lambda x: transport_costs[x[:2]]) + biomass_transport["costs"] = pd.concat([bus0_costs, bus1_costs], axis=1).mean(axis=1) + n.madd("Link", + biomass_transport.index, + bus0=biomass_transport.bus0 + " solid biomass", + bus1=biomass_transport.bus1 + " solid biomass", + p_nom_extendable=True, + length=biomass_transport.length.values, + marginal_cost=biomass_transport.costs * biomass_transport.length.values, + capital_cost=1, + carrier="solid biomass transport" + ) #AC buses with district heating urban_central = n.buses.index[n.buses.carrier == "urban central heat"] @@ -1644,7 +1698,7 @@ def add_biomass(n, costs): n.madd("Link", urban_central + " urban central solid biomass CHP", - bus0=urban_central + " solid biomass", + bus0=spatial.biomass.df.loc[urban_central, "nodes"].values, bus1=urban_central, bus2=urban_central + " urban central heat", carrier="urban central solid biomass CHP", @@ -1658,7 +1712,7 @@ def add_biomass(n, costs): n.madd("Link", urban_central + " urban central solid biomass CHP CC", - bus0=urban_central + " solid biomass", + bus0=spatial.biomass.df.loc[urban_central, "nodes"].values, bus1=urban_central, bus2=urban_central + " urban central heat", bus3="co2 atmosphere", @@ -1684,36 +1738,34 @@ def add_industry(n, costs): # 1e6 to convert TWh to MWh industrial_demand = pd.read_csv(snakemake.input.industrial_demand, index_col=0) * 1e6 - solid_biomass_by_country = industrial_demand["solid biomass"] - n.madd("Bus", - industrial_demand.index + " solid biomass for industry", - location="EU", + spatial.biomass.df.loc[industrial_demand.index, "industry"].values, + location=spatial.biomass.df.loc[industrial_demand.index, "locations"].values, carrier="solid biomass for industry" ) p_set = industrial_demand["solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760 n.madd("Load", - industrial_demand.index + " solid biomass for industry", - bus=industrial_demand.index + " solid biomass for industry", + spatial.biomass.df.loc[industrial_demand.index, "industry"].values, + bus=spatial.biomass.df.loc[industrial_demand.index, "industry"].values, carrier="solid biomass for industry", p_set=p_set ) n.madd("Link", - industrial_demand.index + " solid biomass for industry", - bus0=industrial_demand.index + " solid biomass", - bus1=industrial_demand.index + " solid biomass for industry", + spatial.biomass.df.loc[industrial_demand.index, "industry"].values, + bus0=spatial.biomass.df.loc[industrial_demand.index, "nodes"].values, + bus1=spatial.biomass.df.loc[industrial_demand.index, "industry"].values, carrier="solid biomass for industry", p_nom_extendable=True, efficiency=1. ) n.madd("Link", - industrial_demand.index + " solid biomass for industry CC", - bus0=industrial_demand.index + " solid biomass", - bus1=industrial_demand.index + " solid biomass for industry", + spatial.biomass.df.loc[industrial_demand.index, "industry_cc"].values, + bus0=spatial.biomass.df.loc[industrial_demand.index, "nodes"].values, + bus1=spatial.biomass.df.loc[industrial_demand.index, "industry_cc"].values, bus2="co2 atmosphere", bus3="co2 stored", carrier="solid biomass for industry CC", @@ -2000,93 +2052,6 @@ def maybe_adjust_costs_and_potentials(n, opts): print("changing", attr , "for", carrier, "by factor", factor) -def remove_biomass_transport(n): - - print("no transport of solid biomass considered") - - # remove country specific biomass buses - n.buses = n.buses[~n.buses.carrier.str.contains("biomass")] - - # biomass potential - biomass_pot = n.stores[n.stores.carrier=="solid biomass"].e_nom.sum() - - # remove biomass store per country - n.stores = n.stores[n.stores.carrier!="solid biomass"] - - # remove biomass transport links - n.links = n.links[n.links.carrier!="solid biomass transport"] - - # total industry demand for biomass - biomass_demand = n.loads[n.loads.carrier=="solid biomass for industry"].p_set.sum() - - # remove industry demand - n.loads = n.loads[n.loads.carrier!="solid biomass for industry"] - - # drop transport and industry links - sel = [ - 'solid biomass transport', - 'solid biomass for industry', - 'solid biomass for industry CC' - ] - n.links = n.links[~n.links.carrier.isin(sel)] - - # add back EU bus + load + store + industry links - n.add("Bus", - "EU solid biomass", - location="EU", - carrier="solid biomass" - ) - - n.add("Bus", - "solid biomass for industry", - location="EU", - carrier="solid biomass for industry" - ) - - n.add("Load", - "solid biomass for industry", - bus="solid biomass for industry", - carrier="solid biomass for industry", - p_set=biomass_demand - ) - - n.add("Store", - "EU solid biomass", - bus="EU solid biomass", - carrier="solid biomass", - e_nom=biomass_pot, - marginal_cost=costs.at['solid biomass','fuel'], - e_initial=biomass_pot - ) - - n.add("Link", - "solid biomass for industry", - bus0="EU solid biomass", - bus1="solid biomass for industry", - carrier="solid biomass for industry", - p_nom_extendable=True, - efficiency=1. - ) - - n.add("Link", - "solid biomass for industry CC", - bus0="EU solid biomass", - bus1="solid biomass for industry", - bus2="co2 atmosphere", - bus3="co2 stored", - carrier="solid biomass for industry CC", - p_nom_extendable=True, - capital_cost=costs.at["cement capture", "fixed"] * costs.at['solid biomass', 'CO2 intensity'], - efficiency=0.9, - efficiency2=-costs.at['solid biomass', 'CO2 intensity'] * costs.at["cement capture", "capture_rate"], - efficiency3=costs.at['solid biomass', 'CO2 intensity'] * costs.at["cement capture", "capture_rate"], - lifetime=costs.at['cement capture', 'lifetime'] - ) - - # set CHP buses from country to single EU bus - n.links.loc[n.links.carrier.str.contains("biomass CHP"), "bus0"] = "EU solid biomass" - - # TODO this should rather be a config no wildcard def limit_individual_line_extension(n, maxext): print(f"limiting new HVAC and HVDC extensions to {maxext} MW") @@ -2183,9 +2148,6 @@ def limit_individual_line_extension(n, maxext): if "noH2network" in opts: remove_h2_network(n) - if not options["biomass_transport"]: - remove_biomass_transport(n) - for o in opts: m = re.match(r'^\d+h$', o, re.IGNORECASE) if m is not None: From 3f43656a92c64d2ad0887c80943ad9480252e564 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 12 Jul 2021 12:37:37 +0200 Subject: [PATCH 17/27] call define_spatial --- scripts/prepare_sector_network.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 91bcc2e9..0ddafb37 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -2094,6 +2094,8 @@ def limit_individual_line_extension(n, maxext): patch_electricity_network(n) + define_spatial(pop_layout.index) + if snakemake.config["foresight"] == 'myopic': add_lifetime_wind_solar(n, costs) From ba9be74265d758b963382bd000dca1e50ae9088c Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Wed, 4 Aug 2021 15:05:37 +0200 Subject: [PATCH 18/27] make build_biomass_transport_costs fully optional --- Snakefile | 27 ++++++++++++++---------- scripts/build_biomass_transport_costs.py | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/Snakefile b/Snakefile index 45c33da9..caed1f42 100644 --- a/Snakefile +++ b/Snakefile @@ -180,16 +180,21 @@ rule build_biomass_potentials: script: 'scripts/build_biomass_potentials.py' -rule build_biomass_transport_costs: - input: "data/biomass/biomass potentials in europe_web rev.pdf" - output: - supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", - supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", - transport_costs="resources/biomass_transport_costs.csv", - threads: 1 - resources: mem_mb=1000 - benchmark: "benchmarks/build_biomass_transport_costs" - script: 'scripts/build_biomass_transport_costs.py' +if config["sector"]["biomass_transport"]: + rule build_biomass_transport_costs: + input: + transport_cost_data="data/biomass/biomass potentials in europe_web rev.pdf" + output: + supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", + supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", + transport_costs="resources/biomass_transport_costs.csv", + threads: 1 + resources: mem_mb=1000 + benchmark: "benchmarks/build_biomass_transport_costs" + script: 'scripts/build_biomass_transport_costs.py' + build_biomass_transport_costs_output = rules.build_biomass_transport_costs.output +else: + build_biomass_transport_costs_output = {} rule build_ammonia_production: @@ -336,7 +341,6 @@ rule prepare_sector_network: traffic_data_KFZ="data/emobility/KFZ__count", traffic_data_Pkw="data/emobility/Pkw__count", biomass_potentials='resources/biomass_potentials.csv', - biomass_transport="resources/biomass_transport_costs.csv", heat_profile="data/heat_load_profile_BDEW.csv", costs=CDIR + "costs_{planning_horizons}.csv", profile_offwind_ac=pypsaeur("resources/profile_offwind-ac.nc"), @@ -366,6 +370,7 @@ rule prepare_sector_network: solar_thermal_urban="resources/solar_thermal_urban_elec_s{simpl}_{clusters}.nc", solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc", **build_retro_cost_output + **build_biomass_transport_costs_output output: RDIR + '/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc' threads: 1 resources: mem_mb=2000 diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index 703d81ae..b17c97fd 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -22,7 +22,7 @@ def build_biomass_transport_costs(): df_list = tbl.read_pdf( - snakemake.input[0], + snakemake.input.transport_cost_data, pages="145-147", multiple_tables=True, ) From d428c1b77d7e8cd420f7caf5da2e77f73bb3ae26 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 14:43:51 +0200 Subject: [PATCH 19/27] fix syntax and auto-retrieve biomass transport cost --- Snakefile | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Snakefile b/Snakefile index d100c22a..e768bc90 100644 --- a/Snakefile +++ b/Snakefile @@ -1,4 +1,7 @@ +from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider +HTTP = HTTPRemoteProvider() + configfile: "config.yaml" @@ -183,7 +186,7 @@ rule build_biomass_potentials: if config["sector"]["biomass_transport"]: rule build_biomass_transport_costs: input: - transport_cost_data="data/biomass/biomass potentials in europe_web rev.pdf" + transport_cost_data=HTTP.remote("https://publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass%20potentials%20in%20europe_web%20rev.pdf", keep_local=True) output: supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", @@ -369,7 +372,7 @@ rule prepare_sector_network: solar_thermal_total="resources/solar_thermal_total_elec_s{simpl}_{clusters}.nc", solar_thermal_urban="resources/solar_thermal_urban_elec_s{simpl}_{clusters}.nc", solar_thermal_rural="resources/solar_thermal_rural_elec_s{simpl}_{clusters}.nc", - **build_retro_cost_output + **build_retro_cost_output, **build_biomass_transport_costs_output output: RDIR + '/prenetworks/elec_s{simpl}_{clusters}_lv{lv}_{opts}_{sector_opts}_{planning_horizons}.nc' threads: 1 From 6711d721b9f83719e8871ddf66b2627c25b45ef4 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 16:30:38 +0200 Subject: [PATCH 20/27] biomass_transport: fix cost calculation and get from remote --- Snakefile | 4 +- scripts/build_biomass_transport_costs.py | 91 +++++++++++++++--------- scripts/prepare_sector_network.py | 2 +- 3 files changed, 60 insertions(+), 37 deletions(-) diff --git a/Snakefile b/Snakefile index e768bc90..1ef55c86 100644 --- a/Snakefile +++ b/Snakefile @@ -186,11 +186,11 @@ rule build_biomass_potentials: if config["sector"]["biomass_transport"]: rule build_biomass_transport_costs: input: - transport_cost_data=HTTP.remote("https://publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass%20potentials%20in%20europe_web%20rev.pdf", keep_local=True) + transport_cost_data=HTTP.remote("publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass potentials in europe_web rev.pdf", keep_local=True) output: supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", - transport_costs="resources/biomass_transport_costs.csv", + biomass_transport_costs="resources/biomass_transport_costs.csv", threads: 1 resources: mem_mb=1000 benchmark: "benchmarks/build_biomass_transport_costs" diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index b17c97fd..dc1137ca 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -16,52 +16,75 @@ import pandas as pd import tabula as tbl -ENERGY_CONTENT = 4.8 # unit MWh/tonne (assuming wood pellets) +ENERGY_CONTENT = 4.8 # unit MWh/t (wood pellets) +def get_countries(): + + pandas_options = dict( + skiprows=list(range(6)), + header=None, + index_col=0 + ) + + return tbl.read_pdf( + str(snakemake.input.transport_cost_data), + pages="145", + multiple_tables=False, + pandas_options=pandas_options + )[0].index -def build_biomass_transport_costs(): - df_list = tbl.read_pdf( - snakemake.input.transport_cost_data, - pages="145-147", - multiple_tables=True, +def get_cost_per_tkm(page, countries): + + pandas_options = dict( + skiprows=range(6), + header=0, + sep=' |,', + engine='python', + index_col=False, ) + + sc = tbl.read_pdf( + str(snakemake.input.transport_cost_data), + pages=page, + multiple_tables=False, + pandas_options=pandas_options + )[0] + sc.index = countries + sc.columns = sc.columns.str.replace("€", "EUR") - countries = df_list[0][0].iloc[6:].rename(index=lambda x: x + 1) + return sc - # supply chain 1 - df = df_list[1].copy().rename(index=countries.to_dict()) - df.rename( - columns=df.iloc[:6].apply(lambda col: col.str.cat(sep=" "), axis=0).to_dict(), - inplace=True, - ) - df = df.iloc[6:] - df.loc[6] = df.loc[6].str.replace("€", "EUR") - - # supply chain 2 - df2 = df_list[2].copy().rename(index=countries.to_dict()) - df2.rename( - columns=df2.iloc[:6].apply(lambda col: col.str.cat(sep=" "), axis=0).to_dict(), - inplace=True, - ) - df2 = df2.iloc[6:] - df2.loc[6] = df2.loc[6].str.replace("€", "EUR") - df.to_csv(snakemake.output.supply_chain1) - df2.to_csv(snakemake.output.supply_chain1) +def build_biomass_transport_costs(): + + countries = get_countries() - transport_costs = pd.concat([df["per km/ton"], df2["per km/ton"]], axis=1).drop(6) - transport_costs = transport_costs.astype(float, errors="ignore").mean(axis=1) + sc1 = get_cost_per_tkm(146, countries) + sc2 = get_cost_per_tkm(147, countries) - # convert unit to EUR/MWh + sc1.to_csv(snakemake.output.supply_chain1) + sc2.to_csv(snakemake.output.supply_chain2) + + # take mean of both supply chains + to_concat = [sc1["EUR/km/ton"], sc2["EUR/km/ton"]] + transport_costs = pd.concat(to_concat, axis=1).mean(axis=1) + + # convert tonnes to MWh transport_costs /= ENERGY_CONTENT - transport_costs = pd.DataFrame(transport_costs, columns=["cost [EUR/(km MWh)]"]) + transport_costs.name = "EUR/km/MWh" + + # rename country names + to_rename = { + "UK": "GB", + "XK": "KO", + "EL": "GR" + } + transport_costs.rename(to_rename, inplace=True) - # rename - transport_costs.rename({"UK": "GB", "XK": "KO", "EL": "GR"}, inplace=True) + # add missing Norway with data from Sweden + transport_costs["NO"] = transport_costs["SE"] - # add missing Norway - transport_costs.loc["NO"] = transport_costs.loc["SE"] transport_costs.to_csv(snakemake.output.transport_costs) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 0749a8c3..1dd92b28 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1613,7 +1613,7 @@ def add_biomass(n, costs): biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0) transport_costs = pd.read_csv( - snakemake.input.biomass_transport, + snakemake.input.biomass_transport_costs, index_col=0, squeeze=True ) From 3eb404ff680706537727a2c2689c0a12e07be50a Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 17:51:37 +0200 Subject: [PATCH 21/27] biomass_transport: correct and simplify spatial management --- scripts/prepare_sector_network.py | 54 +++++++++++++++++-------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index 1dd92b28..baddcc81 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -46,7 +46,7 @@ def define_spatial(nodes): spatial.biomass.industry_cc = nodes + " solid biomass for industry CC" else: spatial.biomass.nodes = ["EU solid biomass"] - spatial.biomass.locations = "EU" + spatial.biomass.locations = ["EU"] spatial.biomass.industry = ["solid biomass for industry"] spatial.biomass.industry_cc = ["solid biomass for industry CC"] @@ -1612,16 +1612,13 @@ def add_biomass(n, costs): biomass_potentials = pd.read_csv(snakemake.input.biomass_potentials, index_col=0) - transport_costs = pd.read_csv( - snakemake.input.biomass_transport_costs, - index_col=0, - squeeze=True - ) - - # potential per node distributed within country by population - biomass_pot_node = (biomass_potentials.loc[pop_layout.ct] - .set_index(pop_layout.index) - .mul(pop_layout.fraction, axis="index")) + if options["biomass_transport"]: + # potential per node distributed within country by population + biomass_potentials_spatial = (biomass_potentials.loc[pop_layout.ct] + .set_index(pop_layout.index) + .mul(pop_layout.fraction, axis="index")) + else: + biomass_potentials_spatial = pd.DataFrame(biomass_potentials.sum()).T n.add("Carrier", "biogas") n.add("Carrier", "solid biomass") @@ -1651,9 +1648,9 @@ def add_biomass(n, costs): spatial.biomass.nodes, bus=spatial.biomass.nodes, carrier="solid biomass", - e_nom=biomass_pot_node["solid biomass"].values, + e_nom=biomass_potentials_spatial["solid biomass"].values, marginal_cost=costs.at['solid biomass', 'fuel'], - e_initial=biomass_pot_node["solid biomass"].values + e_initial=biomass_potentials_spatial["solid biomass"].values ) n.add("Link", @@ -1669,6 +1666,12 @@ def add_biomass(n, costs): ) if options["biomass_transport"]: + + transport_costs = pd.read_csv( + snakemake.input.biomass_transport_costs, + index_col=0, + squeeze=True + ) # add biomass transport biomass_transport = create_network_topology(n, "biomass transport ", bidirectional=False) @@ -1739,33 +1742,36 @@ def add_industry(n, costs): industrial_demand = pd.read_csv(snakemake.input.industrial_demand, index_col=0) * 1e6 n.madd("Bus", - spatial.biomass.df.loc[industrial_demand.index, "industry"].values, - location=spatial.biomass.df.loc[industrial_demand.index, "locations"].values, + spatial.biomass.industry, + location=spatial.biomass.locations, carrier="solid biomass for industry" ) - p_set = industrial_demand["solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760 + if options["biomass_transport"]: + p_set = industrial_demand.loc[spatial.biomass.locations, "solid biomass"].rename(index=lambda x: x + " solid biomass for industry") / 8760 + else: + p_set = industrial_demand["solid biomass"].sum() / 8760 n.madd("Load", - spatial.biomass.df.loc[industrial_demand.index, "industry"].values, - bus=spatial.biomass.df.loc[industrial_demand.index, "industry"].values, + spatial.biomass.industry, + bus=spatial.biomass.industry, carrier="solid biomass for industry", p_set=p_set ) n.madd("Link", - spatial.biomass.df.loc[industrial_demand.index, "industry"].values, - bus0=spatial.biomass.df.loc[industrial_demand.index, "nodes"].values, - bus1=spatial.biomass.df.loc[industrial_demand.index, "industry"].values, + spatial.biomass.industry, + bus0=spatial.biomass.nodes, + bus1=spatial.biomass.industry, carrier="solid biomass for industry", p_nom_extendable=True, efficiency=1. ) n.madd("Link", - spatial.biomass.df.loc[industrial_demand.index, "industry_cc"].values, - bus0=spatial.biomass.df.loc[industrial_demand.index, "nodes"].values, - bus1=spatial.biomass.df.loc[industrial_demand.index, "industry_cc"].values, + spatial.biomass.industry_cc, + bus0=spatial.biomass.nodes, + bus1=spatial.biomass.industry, bus2="co2 atmosphere", bus3="co2 stored", carrier="solid biomass for industry CC", From 7ca4f04611c23e63d49cc42505df8af4cdbadaed Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 17:53:14 +0200 Subject: [PATCH 22/27] biomass_transport: reduce output files --- Snakefile | 2 -- scripts/build_biomass_transport_costs.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/Snakefile b/Snakefile index 1ef55c86..d747b8fe 100644 --- a/Snakefile +++ b/Snakefile @@ -188,8 +188,6 @@ if config["sector"]["biomass_transport"]: input: transport_cost_data=HTTP.remote("publications.jrc.ec.europa.eu/repository/bitstream/JRC98626/biomass potentials in europe_web rev.pdf", keep_local=True) output: - supply_chain1="resources/biomass_transport_costs_supply_chain1.csv", - supply_chain2="resources/biomass_transport_costs_supply_chain2.csv", biomass_transport_costs="resources/biomass_transport_costs.csv", threads: 1 resources: mem_mb=1000 diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index dc1137ca..972ff9f8 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -63,9 +63,6 @@ def build_biomass_transport_costs(): sc1 = get_cost_per_tkm(146, countries) sc2 = get_cost_per_tkm(147, countries) - sc1.to_csv(snakemake.output.supply_chain1) - sc2.to_csv(snakemake.output.supply_chain2) - # take mean of both supply chains to_concat = [sc1["EUR/km/ton"], sc2["EUR/km/ton"]] transport_costs = pd.concat(to_concat, axis=1).mean(axis=1) From edf3a5f5fed6ff15f846910e98dbd9993c3f2d69 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 17:55:43 +0200 Subject: [PATCH 23/27] biomass_transport: clarify meaning in config --- config.default.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.default.yaml b/config.default.yaml index 73e7f964..6671bbe9 100644 --- a/config.default.yaml +++ b/config.default.yaml @@ -233,7 +233,7 @@ sector: electricity_grid_connection: true # only applies to onshore wind and utility PV gas_distribution_grid: true gas_distribution_grid_cost_factor: 1.0 #multiplies cost in data/costs.csv - biomass_transport: false # biomass potential per country + transport between countries + biomass_transport: false # biomass transport between nodes conventional_generation: # generator : carrier OCGT: gas From 928d1f23edf3d21a9ee943188fd00ee06ad862b1 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 18:03:20 +0200 Subject: [PATCH 24/27] biomass_transport: improve spatial biomass potential handling --- scripts/prepare_sector_network.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/prepare_sector_network.py b/scripts/prepare_sector_network.py index baddcc81..c2fec14f 100644 --- a/scripts/prepare_sector_network.py +++ b/scripts/prepare_sector_network.py @@ -1616,9 +1616,10 @@ def add_biomass(n, costs): # potential per node distributed within country by population biomass_potentials_spatial = (biomass_potentials.loc[pop_layout.ct] .set_index(pop_layout.index) - .mul(pop_layout.fraction, axis="index")) + .mul(pop_layout.fraction, axis="index") + .rename(index=lambda x: x + " solid biomass")) else: - biomass_potentials_spatial = pd.DataFrame(biomass_potentials.sum()).T + biomass_potentials_spatial = biomass_potentials.sum() n.add("Carrier", "biogas") n.add("Carrier", "solid biomass") @@ -1648,9 +1649,9 @@ def add_biomass(n, costs): spatial.biomass.nodes, bus=spatial.biomass.nodes, carrier="solid biomass", - e_nom=biomass_potentials_spatial["solid biomass"].values, + e_nom=biomass_potentials_spatial["solid biomass"], marginal_cost=costs.at['solid biomass', 'fuel'], - e_initial=biomass_potentials_spatial["solid biomass"].values + e_initial=biomass_potentials_spatial["solid biomass"] ) n.add("Link", From 12f385ef3f58aa76dc4606b1a6de7e46135a2ab1 Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 18:06:24 +0200 Subject: [PATCH 25/27] biomass_transport: simplify read-in --- scripts/build_biomass_transport_costs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index 972ff9f8..fc29853b 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -21,7 +21,7 @@ def get_countries(): pandas_options = dict( - skiprows=list(range(6)), + skiprows=range(6), header=None, index_col=0 ) From 2e2a66ef3ce2bc5253249c2110c938e9faad86ec Mon Sep 17 00:00:00 2001 From: Fabian Neumann Date: Mon, 9 Aug 2021 18:20:07 +0200 Subject: [PATCH 26/27] biomass_transport: add release notes and documentation --- doc/release_notes.rst | 6 ++++++ doc/spatial_resolution.rst | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/release_notes.rst b/doc/release_notes.rst index 15419438..5e92c897 100644 --- a/doc/release_notes.rst +++ b/doc/release_notes.rst @@ -66,6 +66,12 @@ Future release * The share of shipping transformed into hydrogen fuel cell can be now defined for different years in the ``config.yaml`` file. The carbon emission from the remaining share is treated as a negative load on the atmospheric carbon dioxide bus, just like aviation and land transport emissions. * The transformation of the Steel and Aluminium production can be now defined for different years in the ``config.yaml`` file. * Include the option to alter the maximum energy capacity of a store via the ``carrier+factor`` in the ``{sector_opts}`` wildcard. This can be useful for sensitivity analyses. Example: ``co2 stored+e2`` multiplies the ``e_nom_max`` by factor 2. In this example, ``e_nom_max`` represents the CO2 sequestration potential in Europe. +* Add option to regionally disaggregate biomass potential to individual nodes + (currently given per country, then distributed by population density within) + and allow the transport of solid biomass. + The transport costs are determined based on the `JRC-EU-Times Bioenergy report `_ + in the new optional rule ``build_biomass_transport_costs``. + Biomass transport can be activated with the setting ``sector: biomass_transport: true``. PyPSA-Eur-Sec 0.5.0 (21st May 2021) =================================== diff --git a/doc/spatial_resolution.rst b/doc/spatial_resolution.rst index 1be9f3ad..b4e65417 100644 --- a/doc/spatial_resolution.rst +++ b/doc/spatial_resolution.rst @@ -44,8 +44,10 @@ Hydrogen network: nodal. Methane network: single node for Europe, since future demand is so low and no bottlenecks are expected. -Solid biomass: single node for Europe, until transport costs can be -incorporated. +Solid biomass: choice between single node for Europe and nodal where biomass +potential is regionally disaggregated (currently given per country, +then distributed by population density within) +and transport of solid biomass is possible. CO2: single node for Europe, but a transport and storage cost is added for sequestered CO2. From b391aa6475c9be664efe47b5c60828698af4f782 Mon Sep 17 00:00:00 2001 From: lisazeyen <35347358+lisazeyen@users.noreply.github.com> Date: Fri, 24 Sep 2021 14:59:53 +0200 Subject: [PATCH 27/27] Update build_biomass_transport_costs.py change snakemake output file format --- scripts/build_biomass_transport_costs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_biomass_transport_costs.py b/scripts/build_biomass_transport_costs.py index fc29853b..aaec215b 100644 --- a/scripts/build_biomass_transport_costs.py +++ b/scripts/build_biomass_transport_costs.py @@ -82,7 +82,7 @@ def build_biomass_transport_costs(): # add missing Norway with data from Sweden transport_costs["NO"] = transport_costs["SE"] - transport_costs.to_csv(snakemake.output.transport_costs) + transport_costs.to_csv(snakemake.output[0]) if __name__ == "__main__":