Merge branch 'master' into co2-network

This commit is contained in:
Fabian Neumann 2021-08-06 15:51:40 +02:00 committed by GitHub
commit 54a509f3dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 29 deletions

2
.gitignore vendored
View File

@ -28,7 +28,7 @@ gurobi.log
/data/.nfs* /data/.nfs*
/data/Industrial_Database.csv /data/Industrial_Database.csv
/data/retro/tabula-calculator-calcsetbuilding.csv /data/retro/tabula-calculator-calcsetbuilding.csv
/data
*.org *.org
*.nc *.nc

View File

@ -220,10 +220,10 @@ rule build_industrial_production_per_country_tomorrow:
input: input:
industrial_production_per_country="resources/industrial_production_per_country.csv" industrial_production_per_country="resources/industrial_production_per_country.csv"
output: output:
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow.csv" industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow_{planning_horizons}.csv"
threads: 1 threads: 1
resources: mem_mb=1000 resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_production_per_country_tomorrow" benchmark: "benchmarks/build_industrial_production_per_country_tomorrow_{planning_horizons}"
script: 'scripts/build_industrial_production_per_country_tomorrow.py' script: 'scripts/build_industrial_production_per_country_tomorrow.py'
@ -243,25 +243,25 @@ rule build_industrial_distribution_key:
rule build_industrial_production_per_node: rule build_industrial_production_per_node:
input: input:
industrial_distribution_key="resources/industrial_distribution_key_elec_s{simpl}_{clusters}.csv", industrial_distribution_key="resources/industrial_distribution_key_elec_s{simpl}_{clusters}.csv",
industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow.csv" industrial_production_per_country_tomorrow="resources/industrial_production_per_country_tomorrow_{planning_horizons}.csv"
output: output:
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}.csv" industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
threads: 1 threads: 1
resources: mem_mb=1000 resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_production_per_node/s{simpl}_{clusters}" benchmark: "benchmarks/build_industrial_production_per_node/s{simpl}_{clusters}_{planning_horizons}"
script: 'scripts/build_industrial_production_per_node.py' script: 'scripts/build_industrial_production_per_node.py'
rule build_industrial_energy_demand_per_node: rule build_industrial_energy_demand_per_node:
input: input:
industry_sector_ratios="resources/industry_sector_ratios.csv", industry_sector_ratios="resources/industry_sector_ratios.csv",
industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}.csv", industrial_production_per_node="resources/industrial_production_elec_s{simpl}_{clusters}_{planning_horizons}.csv",
industrial_energy_demand_per_node_today="resources/industrial_energy_demand_today_elec_s{simpl}_{clusters}.csv" industrial_energy_demand_per_node_today="resources/industrial_energy_demand_today_elec_s{simpl}_{clusters}.csv"
output: output:
industrial_energy_demand_per_node="resources/industrial_energy_demand_elec_s{simpl}_{clusters}.csv" industrial_energy_demand_per_node="resources/industrial_energy_demand_elec_s{simpl}_{clusters}_{planning_horizons}.csv"
threads: 1 threads: 1
resources: mem_mb=1000 resources: mem_mb=1000
benchmark: "benchmarks/build_industrial_energy_demand_per_node/s{simpl}_{clusters}" benchmark: "benchmarks/build_industrial_energy_demand_per_node/s{simpl}_{clusters}_{planning_horizons}"
script: 'scripts/build_industrial_energy_demand_per_node.py' script: 'scripts/build_industrial_energy_demand_per_node.py'
@ -333,7 +333,7 @@ rule prepare_sector_network:
busmap=pypsaeur("resources/busmap_elec_s{simpl}_{clusters}.csv"), busmap=pypsaeur("resources/busmap_elec_s{simpl}_{clusters}.csv"),
clustered_pop_layout="resources/pop_layout_elec_s{simpl}_{clusters}.csv", clustered_pop_layout="resources/pop_layout_elec_s{simpl}_{clusters}.csv",
simplified_pop_layout="resources/pop_layout_elec_s{simpl}.csv", simplified_pop_layout="resources/pop_layout_elec_s{simpl}.csv",
industrial_demand="resources/industrial_energy_demand_elec_s{simpl}_{clusters}.csv", industrial_demand="resources/industrial_energy_demand_elec_s{simpl}_{clusters}_{planning_horizons}.csv",
heat_demand_urban="resources/heat_demand_urban_elec_s{simpl}_{clusters}.nc", heat_demand_urban="resources/heat_demand_urban_elec_s{simpl}_{clusters}.nc",
heat_demand_rural="resources/heat_demand_rural_elec_s{simpl}_{clusters}.nc", heat_demand_rural="resources/heat_demand_rural_elec_s{simpl}_{clusters}.nc",
heat_demand_total="resources/heat_demand_total_elec_s{simpl}_{clusters}.nc", heat_demand_total="resources/heat_demand_total_elec_s{simpl}_{clusters}.nc",

View File

@ -30,6 +30,7 @@ scenario:
# B for biomass supply, I for industry, shipping and aviation # B for biomass supply, I for industry, shipping and aviation
# solar+c0.5 reduces the capital cost of solar to 50\% of reference value # solar+c0.5 reduces the capital cost of solar to 50\% of reference value
# solar+p3 multiplies the available installable potential by factor 3 # solar+p3 multiplies the available installable potential by factor 3
# co2 stored+e2 multiplies the potential of CO2 sequestration by a factor 2
# dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv # dist{n} includes distribution grids with investment cost of n times cost in data/costs.csv
# for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative # for myopic/perfect foresight cb states the carbon budget in GtCO2 (cumulative
# emissions throughout the transition path in the timeframe determined by the # emissions throughout the transition path in the timeframe determined by the
@ -174,6 +175,15 @@ sector:
transport_fuel_cell_efficiency: 0.5 transport_fuel_cell_efficiency: 0.5
transport_internal_combustion_efficiency: 0.3 transport_internal_combustion_efficiency: 0.3
shipping_average_efficiency: 0.4 #For conversion of fuel oil to propulsion in 2011 shipping_average_efficiency: 0.4 #For conversion of fuel oil to propulsion in 2011
shipping_hydrogen_liquefaction: true # whether to consider liquefaction costs for shipping H2 demands
shipping_hydrogen_share: # 1 means all hydrogen FC
2020: 0
2025: 0
2030: 0.05
2035: 0.15
2040: 0.3
2045: 0.6
2050: 1
time_dep_hp_cop: true #time dependent heat pump coefficient of performance time_dep_hp_cop: true #time dependent heat pump coefficient of performance
heat_pump_sink_T: 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py heat_pump_sink_T: 55. # Celsius, based on DTU / large area radiators; used in build_cop_profiles.py
# conservatively high to cover hot water and space heating in poorly-insulated buildings # conservatively high to cover hot water and space heating in poorly-insulated buildings
@ -229,10 +239,32 @@ sector:
industry: industry:
St_primary_fraction: 0.3 # fraction of steel produced via primary route (DRI + EAF) versus secondary route (EAF); today fraction is 0.6 St_primary_fraction: # fraction of steel produced via primary route versus secondary route (scrap+EAF); today fraction is 0.6
2020: 0.6
2025: 0.55
2030: 0.5
2035: 0.45
2040: 0.4
2045: 0.35
2050: 0.3
DRI_fraction: # fraction of the primary route converted to DRI + EAF
2020: 0
2025: 0
2030: 0.05
2035: 0.2
2040: 0.4
2045: 0.7
2050: 1
H2_DRI: 1.7 #H2 consumption in Direct Reduced Iron (DRI), MWh_H2,LHV/ton_Steel from 51kgH2/tSt in Vogl et al (2018) doi:10.1016/j.jclepro.2018.08.279 H2_DRI: 1.7 #H2 consumption in Direct Reduced Iron (DRI), MWh_H2,LHV/ton_Steel from 51kgH2/tSt in Vogl et al (2018) doi:10.1016/j.jclepro.2018.08.279
elec_DRI: 0.322 #electricity consumption in Direct Reduced Iron (DRI) shaft, MWh/tSt HYBRIT brochure https://ssabwebsitecdn.azureedge.net/-/media/hybrit/files/hybrit_brochure.pdf elec_DRI: 0.322 #electricity consumption in Direct Reduced Iron (DRI) shaft, MWh/tSt HYBRIT brochure https://ssabwebsitecdn.azureedge.net/-/media/hybrit/files/hybrit_brochure.pdf
Al_primary_fraction: 0.2 # fraction of aluminium produced via the primary route versus scrap; today fraction is 0.4 Al_primary_fraction: # fraction of aluminium produced via the primary route versus scrap; today fraction is 0.4
2020: 0.4
2025: 0.375
2030: 0.35
2035: 0.325
2040: 0.3
2045: 0.25
2050: 0.2
MWh_CH4_per_tNH3_SMR: 10.8 # 2012's demand from https://ec.europa.eu/docsroom/documents/4165/attachments/1/translations/en/renditions/pdf MWh_CH4_per_tNH3_SMR: 10.8 # 2012's demand from https://ec.europa.eu/docsroom/documents/4165/attachments/1/translations/en/renditions/pdf
MWh_elec_per_tNH3_SMR: 0.7 # same source, assuming 94-6% split methane-elec of total energy demand 11.5 MWh/tNH3 MWh_elec_per_tNH3_SMR: 0.7 # same source, assuming 94-6% split methane-elec of total energy demand 11.5 MWh/tNH3
MWh_H2_per_tNH3_electrolysis: 6.5 # from https://doi.org/10.1016/j.joule.2018.04.017, around 0.197 tH2/tHN3 (>3/17 since some H2 lost and used for energy) MWh_H2_per_tNH3_electrolysis: 6.5 # from https://doi.org/10.1016/j.joule.2018.04.017, around 0.197 tH2/tHN3 (>3/17 since some H2 lost and used for energy)
@ -472,4 +504,8 @@ plotting:
solid biomass: '#DAA520' solid biomass: '#DAA520'
today: '#D2691E' today: '#D2691E'
shipping: '#6495ED' shipping: '#6495ED'
shipping oil: "#6495ED"
shipping oil emissions: "#6495ED"
electricity distribution grid: '#333333' electricity distribution grid: '#333333'
H2 for industry: "#222222"
H2 for shipping: "#6495ED"

View File

@ -68,8 +68,12 @@ Future release
nor other modes of CO2 transport (by ship, road or rail) are considered. nor other modes of CO2 transport (by ship, road or rail) are considered.
The regional representation of CO2 is activated with the config setting ``sector: co2_network: true`` but is deactivated by default. The regional representation of CO2 is activated with the config setting ``sector: co2_network: true`` but is deactivated by default.
The global limit for CO2 sequestration now applies to the sum of all CO2 stores via an ``extra_functionality`` constraint. The global limit for CO2 sequestration now applies to the sum of all CO2 stores via an ``extra_functionality`` constraint.
* Added option for hydrogen liquefaction costs for hydrogen demand in shipping.
This introduces a new ``H2 liquid`` bus at each location.
It is activated via ``sector: shipping_hydrogen_liquefaction: true``.
* 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.
PyPSA-Eur-Sec 0.5.0 (21st May 2021) PyPSA-Eur-Sec 0.5.0 (21st May 2021)
=================================== ===================================

View File

@ -2,6 +2,8 @@
import pandas as pd import pandas as pd
from prepare_sector_network import get
if __name__ == '__main__': if __name__ == '__main__':
if 'snakemake' not in globals(): if 'snakemake' not in globals():
from helper import mock_snakemake from helper import mock_snakemake
@ -9,27 +11,35 @@ if __name__ == '__main__':
config = snakemake.config["industry"] config = snakemake.config["industry"]
investment_year = int(snakemake.wildcards.planning_horizons)
fn = snakemake.input.industrial_production_per_country fn = snakemake.input.industrial_production_per_country
production = pd.read_csv(fn, index_col=0) production = pd.read_csv(fn, index_col=0)
keys = ["Integrated steelworks", "Electric arc"] keys = ["Integrated steelworks", "Electric arc"]
total_steel = production[keys].sum(axis=1) total_steel = production[keys].sum(axis=1)
st_primary_fraction = get(config["St_primary_fraction"], investment_year)
dri_fraction = get(config["DRI_fraction"], investment_year)
int_steel = production["Integrated steelworks"].sum() int_steel = production["Integrated steelworks"].sum()
fraction_persistent_primary = config["St_primary_fraction"] * total_steel.sum() / int_steel fraction_persistent_primary = st_primary_fraction * total_steel.sum() / int_steel
dri = fraction_persistent_primary * production["Integrated steelworks"] dri = dri_fraction * fraction_persistent_primary * production["Integrated steelworks"]
production.insert(2, "DRI + Electric arc", dri) production.insert(2, "DRI + Electric arc", dri)
production["Electric arc"] = total_steel - production["DRI + Electric arc"] not_dri = (1 - dri_fraction)
production["Integrated steelworks"] = 0. production["Integrated steelworks"] = not_dri * fraction_persistent_primary * production["Integrated steelworks"]
production["Electric arc"] = total_steel - production["DRI + Electric arc"] - production["Integrated steelworks"]
keys = ["Aluminium - primary production", "Aluminium - secondary production"] keys = ["Aluminium - primary production", "Aluminium - secondary production"]
total_aluminium = production[keys].sum(axis=1) total_aluminium = production[keys].sum(axis=1)
key_pri = "Aluminium - primary production" key_pri = "Aluminium - primary production"
key_sec = "Aluminium - secondary production" key_sec = "Aluminium - secondary production"
fraction_persistent_primary = config["Al_primary_fraction"] * total_aluminium.sum() / production[key_pri].sum()
al_primary_fraction = get(config["Al_primary_fraction"], investment_year)
fraction_persistent_primary = al_primary_fraction * total_aluminium.sum() / production[key_pri].sum()
production[key_pri] = fraction_persistent_primary * production[key_pri] production[key_pri] = fraction_persistent_primary * production[key_pri]
production[key_sec] = total_aluminium - production[key_pri] production[key_sec] = total_aluminium - production[key_pri]

View File

@ -34,7 +34,9 @@ def rename_techs(label):
rename_if_contains_dict = { rename_if_contains_dict = {
"water tanks": "hot water storage", "water tanks": "hot water storage",
"retrofitting": "building retrofitting", "retrofitting": "building retrofitting",
"H2": "hydrogen storage", "H2 Electrolysis": "hydrogen storage",
"H2 Fuel Cell": "hydrogen storage",
"H2 pipeline": "hydrogen storage",
"battery": "battery storage", "battery": "battery storage",
"CC": "CC" "CC": "CC"
} }

View File

@ -992,7 +992,7 @@ def add_storage(n, costs):
) )
# hydrogen stored overground (where not already underground) # hydrogen stored overground (where not already underground)
h2_capital_cost = costs.at["hydrogen storage tank", "fixed"] h2_capital_cost = costs.at["hydrogen storage tank incl. compressor", "fixed"]
nodes_overground = cavern_nodes.index.symmetric_difference(nodes) nodes_overground = cavern_nodes.index.symmetric_difference(nodes)
n.madd("Store", n.madd("Store",
@ -1027,9 +1027,9 @@ def add_storage(n, costs):
p_min_pu=-1, p_min_pu=-1,
p_nom_extendable=True, p_nom_extendable=True,
length=h2_links.length.values, length=h2_links.length.values,
capital_cost=costs.at['H2 pipeline', 'fixed'] * h2_links.length.values, capital_cost=costs.at['H2 (g) pipeline', 'fixed'] * h2_links.length.values,
carrier="H2 pipeline", carrier="H2 pipeline",
lifetime=costs.at['H2 pipeline', 'lifetime'] lifetime=costs.at['H2 (g) pipeline', 'lifetime']
) )
n.add("Carrier", "battery") n.add("Carrier", "battery")
@ -1084,7 +1084,7 @@ def add_storage(n, costs):
carrier="Sabatier", carrier="Sabatier",
efficiency=costs.at["methanation", "efficiency"], efficiency=costs.at["methanation", "efficiency"],
efficiency2=-costs.at["methanation", "efficiency"] * costs.at['gas', 'CO2 intensity'], efficiency2=-costs.at["methanation", "efficiency"] * costs.at['gas', 'CO2 intensity'],
capital_cost=costs.at["methanation", "fixed"], capital_cost=costs.at["methanation", "fixed"] * costs.at["methanation", "efficiency"], # costs given per kW_gas
lifetime=costs.at['methanation', 'lifetime'] lifetime=costs.at['methanation', 'lifetime']
) )
@ -1805,18 +1805,66 @@ def add_industry(n, costs):
p_set=industrial_demand.loc[nodes, "hydrogen"] / 8760 p_set=industrial_demand.loc[nodes, "hydrogen"] / 8760
) )
if options["shipping_hydrogen_liquefaction"]:
n.madd("Bus",
nodes,
suffix=" H2 liquid",
carrier="H2 liquid",
location=nodes
)
n.madd("Link",
nodes + " H2 liquefaction",
bus0=nodes + " H2",
bus1=nodes + " H2 liquid",
carrier="H2 liquefaction",
efficiency=costs.at["H2 liquefaction", 'efficiency'],
capital_cost=costs.at["H2 liquefaction", 'fixed'],
p_nom_extendable=True,
lifetime=costs.at['H2 liquefaction', 'lifetime']
)
shipping_bus = nodes + " H2 liquid"
else:
shipping_bus = nodes + " H2"
all_navigation = ["total international navigation", "total domestic navigation"] all_navigation = ["total international navigation", "total domestic navigation"]
efficiency = options['shipping_average_efficiency'] / costs.at["fuel cell", "efficiency"] efficiency = options['shipping_average_efficiency'] / costs.at["fuel cell", "efficiency"]
p_set = nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 * efficiency / 8760 shipping_hydrogen_share = get(options['shipping_hydrogen_share'], investment_year)
p_set = shipping_hydrogen_share * nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 * efficiency / 8760
n.madd("Load", n.madd("Load",
nodes, nodes,
suffix=" H2 for shipping", suffix=" H2 for shipping",
bus=nodes + " H2", bus=shipping_bus,
carrier="H2 for shipping", carrier="H2 for shipping",
p_set=p_set p_set=p_set
) )
if shipping_hydrogen_share < 1:
shipping_oil_share = 1 - shipping_hydrogen_share
p_set = shipping_oil_share * nodal_energy_totals.loc[nodes, all_navigation].sum(axis=1) * 1e6 / 8760.
n.madd("Load",
nodes,
suffix=" shipping oil",
bus="EU oil",
carrier="shipping oil",
p_set=p_set
)
co2 = shipping_oil_share * nodal_energy_totals.loc[nodes, all_navigation].sum().sum() * 1e6 / 8760 * costs.at["oil", "CO2 intensity"]
n.add("Load",
"shipping oil emissions",
bus="co2 atmosphere",
carrier="shipping oil emissions",
p_set=-co2
)
if "EU oil" not in n.buses.index: if "EU oil" not in n.buses.index:
n.add("Bus", n.add("Bus",
@ -2017,14 +2065,19 @@ def maybe_adjust_costs_and_potentials(n, opts):
suptechs = map(lambda c: c.split("-", 2)[0], carrier_list) suptechs = map(lambda c: c.split("-", 2)[0], carrier_list)
if oo[0].startswith(tuple(suptechs)): if oo[0].startswith(tuple(suptechs)):
carrier = oo[0] carrier = oo[0]
attr_lookup = {"p": "p_nom_max", "c": "capital_cost"} attr_lookup = {"p": "p_nom_max", "e": "e_nom_max", "c": "capital_cost"}
attr = attr_lookup[oo[1][0]] attr = attr_lookup[oo[1][0]]
factor = float(oo[1][1:]) factor = float(oo[1][1:])
#beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan #beware if factor is 0 and p_nom_max is np.inf, 0*np.inf is nan
if carrier == "AC": # lines do not have carrier if carrier == "AC": # lines do not have carrier
n.lines[attr] *= factor n.lines[attr] *= factor
else: else:
comps = {"Generator", "Link", "StorageUnit"} if attr == 'p_nom_max' else {"Generator", "Link", "StorageUnit", "Store"} if attr == 'p_nom_max':
comps = {"Generator", "Link", "StorageUnit"}
elif attr == 'e_nom_max':
comps = {"Store"}
else:
comps = {"Generator", "Link", "StorageUnit", "Store"}
for c in n.iterate_components(comps): for c in n.iterate_components(comps):
if carrier=='solar': if carrier=='solar':
sel = c.df.carrier.str.contains(carrier) & ~c.df.carrier.str.contains("solar rooftop") sel = c.df.carrier.str.contains(carrier) & ~c.df.carrier.str.contains("solar rooftop")

View File

@ -169,7 +169,6 @@ def add_co2_sequestration_limit(n, sns):
def extra_functionality(n, snapshots): def extra_functionality(n, snapshots):
add_chp_constraints(n)
add_battery_constraints(n) add_battery_constraints(n)
add_co2_sequestration_limit(n, snapshots) add_co2_sequestration_limit(n, snapshots)