Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into multiyear
Browse files Browse the repository at this point in the history
  • Loading branch information
davide-f committed Jul 11, 2024
2 parents 5c19e94 + 2b643f4 commit ad2f5ad
Show file tree
Hide file tree
Showing 26 changed files with 942 additions and 1,369 deletions.
217 changes: 150 additions & 67 deletions Snakefile

Large diffs are not rendered by default.

70 changes: 13 additions & 57 deletions config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,16 @@ scenario:
- "AB"

policy_config:
policy: "no_res_matching" #either "H2_export_yearly_constraint", "H2_export_monthly_constraint", "no_res_matching"
monthly: # Specify attributes for the monthly constraint
hydrogen:
temporal_matching: "no_res_matching" #either "h2_yearly_matching", "h2_monthly_matching", "no_res_matching"
spatial_matching: false
additionality: false # RE electricity is equal to the amount required for additional hydrogen export compared to the 0 export case ("reference_case")
allowed_excess: 1.0
reference_case: false # RE electricity is equal to the amount required for additional hydrogen export compared to the 0 export case ("reference_case")
yearly: # Specify attributes for the yearly constraint
is_reference: false # Whether or not this network is a reference case network, relevant only if additionality is _true_
remove_h2_load: false #Whether or not to remove the h2 load from the network, relevant only if is_reference is _true_
path_to_ref: "" # Path to the reference case network for additionality calculation, relevant only if additionality is _true_ and is_reference is _false_
re_country_load: false # Set to "True" to force the RE electricity to be equal to the electricity required for hydrogen export and the country electricity load. "False" excludes the country electricity load from the constraint.


clustering_options:
alternative_clustering: true

Expand Down Expand Up @@ -110,51 +112,6 @@ costs: # Costs used in PyPSA-Earth-Sec. Year depends on the wildcard planning_ho


industry:
St_primary_fraction: 0.9 # 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: 0.5 # 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
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
# 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_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_elec_per_tNH3_electrolysis: 1.17 # from https://doi.org/10.1016/j.joule.2018.04.017 Table 13 (air separation and HB)
NH3_process_emissions: 24.5 # in MtCO2/a from SMR for H2 production for NH3 from UNFCCC for 2015 for EU28
petrochemical_process_emissions: 25.5 # in MtCO2/a for petrochemical and other from UNFCCC for 2015 for EU28
HVC_primary_fraction: 1. # fraction of today's HVC produced via primary route
HVC_mechanical_recycling_fraction: 0. # fraction of today's HVC produced via mechanical recycling
HVC_chemical_recycling_fraction: 0. # fraction of today's HVC produced via chemical recycling
HVC_production_today: 52. # MtHVC/a from DECHEMA (2017), Figure 16, page 107; includes ethylene, propylene and BTX
MWh_elec_per_tHVC_mechanical_recycling: 0.547 # from SI of https://doi.org/10.1016/j.resconrec.2020.105010, Table S5, for HDPE, PP, PS, PET. LDPE would be 0.756.
MWh_elec_per_tHVC_chemical_recycling: 6.9 # Material Economics (2019), page 125; based on pyrolysis and electric steam cracking
chlorine_production_today: 9.58 # MtCl/a from DECHEMA (2017), Table 7, page 43
MWh_elec_per_tCl: 3.6 # DECHEMA (2017), Table 6, page 43
MWh_H2_per_tCl: -0.9372 # DECHEMA (2017), page 43; negative since hydrogen produced in chloralkali process
methanol_production_today: 1.5 # MtMeOH/a from DECHEMA (2017), page 62
MWh_elec_per_tMeOH: 0.167 # DECHEMA (2017), Table 14, page 65
MWh_CH4_per_tMeOH: 10.25 # DECHEMA (2017), Table 14, page 65
hotmaps_locate_missing: false
reference_year: 2015

solar_thermal:
Expand Down Expand Up @@ -187,6 +144,10 @@ sector:
network_routes: gas # "gas or "greenfield". If "gas" -> the network data are fetched from ["sector"]["gas"]["network_data"]. If "greenfield" -> the network follows the topology of electrical transmission lines
gas_network_repurposing: true # If true -> ["sector"]["gas"]["network"] is automatically false
underground_storage: false
hydrogen_colors: false
set_color_shares: false
blue_share: 0.40
pink_share: 0.05

international_bunkers: false #Whether or not to count the emissions of international aviation and navigation

Expand Down Expand Up @@ -215,13 +176,7 @@ sector:
# 2040: 0.16
# 2045: 0.21
# 2050: 0.29
retrofitting: # co-optimises building renovation to reduce space heat demand
retro_endogen: false # co-optimise space heat savings
cost_factor: 1.0 # weight costs for building renovation
interest_rate: 0.04 # for investment in building components
annualise_cost: true # annualise the investment costs
tax_weighting: false # weight costs depending on taxes in countries
construction_index: true # weight costs depending on labour/material costs per country

tes: true
tes_tau: # 180 day time constant for centralised, 3 day for decentralised
decentral: 3
Expand Down Expand Up @@ -580,3 +535,4 @@ plotting:
H2 for shipping: "#6495ED"
biomass EOP: "green"
biomass: "green"
high-temp electrolysis: "magenta"
2 changes: 2 additions & 0 deletions data_custom/TEMPLATE_energy_totals_AB_2030.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
,Unnamed: 0,agriculture biomass,agriculture electricity,agriculture oil,district heat share,electricity rail,electricity residential,electricity residential space,electricity residential water,electricity services space,electricity services water,residential biomass,residential gas,residential heat biomass,residential heat gas,residential heat oil,residential oil,services biomass,services electricity,services gas,services oil,total domestic aviation,total domestic navigation,total international aviation,total international navigation,total navigation hydrogen,total navigation oil,total rail,total residential space,total residential water,total road,total road ev,total road fcev,total road ice,total services space,total services water
AE,0.0,0.0,0.0,0.0,0,0.0,64.38404562665289,0.4505805278999999,0.30038701859999994,0,0,0.0,0.0,0.0,0.0,0.8099939957379862,0.417141615,0.0,69.53247242631582,0.0,0.0,6.6599243305365174,0.0,111.1180823122369,242.89285120432652,0.0,0.0,0.0,0.9365769253427916,0.6243846168951945,155.84141405284205,0.0,0.0,0.0,0.0,0.0
19 changes: 19 additions & 0 deletions data_custom/TEMPLATE_h2_underground_AB_2030 copy.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
country,id_region,storage_cap_MWh
AE,AE.8_AC,0
AE,AE.9_AC,0
AE,AE.10_AC,0
AE,AE.3.9_AC,0
AE,AE.3.5_AC,0
AE,AE.1.3_AC,0
AE,AE.1.2_AC,0
AE,AE.11_AC,266458967703478
AE,AE.12_AC,101020158085387
AE,AE.5.58_1_AC,0
AE,AE.13_AC,0
AE,AE.14_AC,0
AE,AE_2_43_AC,0
AE,AE.7.1_1_AC,0
AE,AE.15_AC,0
AE,AE.16_AC,0
AE,AE.17_AC,0
AE,AE.18_AC,0
26 changes: 13 additions & 13 deletions scripts/add_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ def select_ports(n):
logger.error(
"No export ports chosen, please add ports to the file data/export_ports.csv"
)
gadm_level = snakemake.config["sector"]["gadm_level"]
gadm_level = snakemake.params.gadm_level

ports["gadm_{}".format(gadm_level)] = ports[["x", "y", "country"]].apply(
lambda port: locate_bus(
port[["x", "y"]],
port["country"],
gadm_level,
snakemake.input["shapes_path"],
snakemake.config["clustering_options"]["alternative_clustering"],
snakemake.params.alternative_clustering,
),
axis=1,
)
Expand Down Expand Up @@ -97,16 +97,16 @@ def add_export(n, hydrogen_buses_ports, export_profile):

# add store depending on config settings

if snakemake.config["export"]["store"] == True:
if snakemake.config["export"]["store_capital_costs"] == "no_costs":
if snakemake.params.store == True:
if snakemake.params.store_capital_costs == "no_costs":
capital_cost = 0
elif snakemake.config["export"]["store_capital_costs"] == "standard_costs":
elif snakemake.params.store_capital_costs == "standard_costs":
capital_cost = costs.at[
"hydrogen storage tank type 1 including compressor", "fixed"
]
else:
logger.error(
f"Value {snakemake.config['export']['store_capital_costs']} for ['export']['store_capital_costs'] is not valid"
f"Value {snakemake.params.store_capital_costs} for ['export']['store_capital_costs'] is not valid"
)

n.add(
Expand All @@ -121,7 +121,7 @@ def add_export(n, hydrogen_buses_ports, export_profile):
e_cyclic=True,
)

elif snakemake.config["export"]["store"] == False:
elif snakemake.params.store == False:
pass

# add load
Expand All @@ -141,12 +141,12 @@ def create_export_profile():

export_h2 = eval(snakemake.wildcards["h2export"]) * 1e6 # convert TWh to MWh

if snakemake.config["export"]["export_profile"] == "constant":
if snakemake.params.export_profile == "constant":
export_profile = export_h2 / 8760
snapshots = pd.date_range(freq="h", **snakemake.config["snapshots"])
snapshots = pd.date_range(freq="h", **snakemake.params.snapshots)
export_profile = pd.Series(export_profile, index=snapshots)

elif snakemake.config["export"]["export_profile"] == "ship":
elif snakemake.params.export_profile == "ship":
# Import hydrogen export ship profile and check if it matches the export demand obtained from the wildcard
export_profile = pd.read_csv(snakemake.input.ship_profile, index_col=0)
export_profile.index = pd.to_datetime(export_profile.index)
Expand All @@ -167,7 +167,7 @@ def create_export_profile():
export_profile = export_profile.resample(sopts[0]).mean()

# revise logger msg
export_type = snakemake.config["export"]["export_profile"]
export_type = snakemake.params.export_profile
logger.info(
f"The yearly export demand is {export_h2/1e6} TWh, profile generated based on {export_type} method and resampled to {sopts[0]}"
)
Expand Down Expand Up @@ -206,10 +206,10 @@ def create_export_profile():

costs = prepare_costs(
snakemake.input.costs,
snakemake.config["costs"]["USD2013_to_EUR2013"],
snakemake.params.USD_to_EUR,
eval(snakemake.wildcards.discountrate),
Nyears,
snakemake.config["costs"]["lifetime"],
snakemake.params.lifetime,
)

# get hydrogen export buses/ports
Expand Down
14 changes: 7 additions & 7 deletions scripts/build_base_energy_totals.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ def calc_sector(sector):
round(
df_sector[df_sector.Commodity.isin(heat)].Quantity_TWh.sum(), 4
)
* snakemake.config["sector"]["space_heat_share"]
* snakemake.params.space_heat_share
)
energy_totals_base.at[country, "total residential water"] = round(
df_sector[df_sector.Commodity.isin(heat)].Quantity_TWh.sum(), 4
) * (1 - snakemake.config["sector"]["space_heat_share"])
) * (1 - snakemake.params.space_heat_share)

elif sector == "services":
energy_totals_base.at[country, "services electricity"] = round(
Expand All @@ -166,11 +166,11 @@ def calc_sector(sector):
round(
df_sector[df_sector.Commodity.isin(heat)].Quantity_TWh.sum(), 4
)
* snakemake.config["sector"]["space_heat_share"]
* snakemake.params.space_heat_share
)
energy_totals_base.at[country, "total services water"] = round(
df_sector[df_sector.Commodity.isin(heat)].Quantity_TWh.sum(), 4
) * (1 - snakemake.config["sector"]["space_heat_share"])
) * (1 - snakemake.params.space_heat_share)

elif sector == "road":
energy_totals_base.at[country, "total road"] = round(
Expand Down Expand Up @@ -273,7 +273,7 @@ def calc_sector(sector):
df = df.to_dict("dict")
d = df["Link"]

if snakemake.config["demand_data"]["update_data"]:
if snakemake.params.update_data:
# Delete and existing files to avoid duplication and double counting

files = glob.glob("data/demand/unsd/data/*.txt")
Expand Down Expand Up @@ -363,8 +363,8 @@ def calc_sector(sector):
}

# Fetch country list and demand base year from the config file
year = snakemake.config["demand_data"]["base_year"]
countries = snakemake.config["countries"]
year = snakemake.params.base_year
countries = snakemake.params.countries
# countries = ["NG", "BJ"]

# Filter for the year and country
Expand Down
8 changes: 4 additions & 4 deletions scripts/build_base_industry_totals.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,17 @@ def create_industry_base_totals(df):

# Loading config file and wild cards

year = snakemake.config["demand_data"]["base_year"]
countries = snakemake.config["countries"]
year = snakemake.params.base_year
countries = snakemake.params.countries
# countries = ["DE", "US", "EG", "MA", "UA", "UK"]
# countries = ["EG", "BH"]

investment_year = int(snakemake.wildcards.planning_horizons)
demand_sc = snakemake.wildcards.demand
no_years = int(snakemake.wildcards.planning_horizons) - int(
snakemake.config["demand_data"]["base_year"]
snakemake.params.base_year
)
include_other = snakemake.config["demand_data"]["other_industries"]
include_other = snakemake.params.other_industries

industry_list = [
"iron and steel",
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_cop_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def coefficient_of_performance(delta_T, source="air"):
for source in ["air", "soil"]:
source_T = xr.open_dataarray(snakemake.input[f"temp_{source}_{area}"])

delta_T = snakemake.config["sector"]["heat_pump_sink_T"] - source_T
delta_T = snakemake.params.heat_pump_sink_T - source_T

cop = coefficient_of_performance(delta_T, source)

Expand Down
2 changes: 1 addition & 1 deletion scripts/build_heat_demand.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
snakemake = mock_snakemake("build_heat_demand", simpl="", clusters="10")
sets_path_to_root("pypsa-earth-sec")

time = pd.date_range(freq="h", **snakemake.config["snapshots"])
time = pd.date_range(freq="h", **snakemake.params.snapshots)
cutout_config = snakemake.input.cutout
cutout = atlite.Cutout(cutout_config).sel(time=time)

Expand Down
31 changes: 17 additions & 14 deletions scripts/build_industrial_distribution_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
gpd_version = StrictVersion(gpd.__version__)


def map_industry_to_buses(df):
def map_industry_to_buses(df, countries, gadm_level, shapes_path, gadm_clustering):
"""
Load hotmaps database of industrial sites and map onto bus regions.
Build industrial demand... Change name and add other functions.
Function similar to aviation/shipping. Use functions to disaggregate.
Only cement not steel - proof of concept.
Change hotmaps to more descriptive name, etc.
"""
df = df[df.country.isin(snakemake.config["countries"])]
df = df[df.country.isin(countries)]
df["gadm_{}".format(gadm_level)] = df[["x", "y", "country"]].apply(
lambda site: locate_bus(
site[["x", "y"]].astype("float"),
Expand All @@ -36,11 +36,11 @@ def map_industry_to_buses(df):
axis=1,
)

return df.set_index("gadm_" + str(snakemake.config["sector"]["gadm_level"]))
return df.set_index("gadm_" + str(gadm_level))


def build_nodal_distribution_key(
industrial_database, regions
industrial_database, regions, industry, countries
): # returns percentage of co2 emissions
"""Build nodal distribution keys for each sector."""

Expand Down Expand Up @@ -123,12 +123,12 @@ def match_technology(df):
)
sets_path_to_root("pypsa-earth-sec")

options = snakemake.config["sector"]
gadm_level = options["gadm_level"]

regions = gpd.read_file(snakemake.input.regions_onshore)
shapes_path = snakemake.input.shapes_path

countries = snakemake.config["countries"]
gadm_level = snakemake.params.gadm_level
countries = snakemake.params.countries
gadm_clustering = snakemake.params.alternative_clustering

# countries = ["EG", "BH"]

Expand All @@ -139,7 +139,7 @@ def match_technology(df):
lambda name: three_2_two_digits_country(name[:3]) + name[3:]
)

if snakemake.config["custom_data"]["industry_database"]:
if snakemake.params.industry_database:
logger.info(
"Using custom industry database from 'data_custom/industrial_database.csv' instead of default"
)
Expand All @@ -166,17 +166,20 @@ def match_technology(df):

geo_locs.capacity = pd.to_numeric(geo_locs.capacity)

gadm_clustering = snakemake.config["clustering_options"]["alternative_clustering"]

geo_locs = geo_locs[geo_locs.quality != "nonexistent"]

industry = geo_locs.industry.unique()

shapes_path = snakemake.input.shapes_path
industrial_database = map_industry_to_buses(
geo_locs[geo_locs.quality != "unavailable"]
geo_locs[geo_locs.quality != "unavailable"],
countries,
gadm_level,
shapes_path,
gadm_clustering,
)

keys = build_nodal_distribution_key(industrial_database, regions)
keys = build_nodal_distribution_key(
industrial_database, regions, industry, countries
)

keys.to_csv(snakemake.output.industrial_distribution_key)
Loading

0 comments on commit ad2f5ad

Please sign in to comment.