From a49e8d8ea8a3d9a6bfef3a5cd44eef0214d8516b Mon Sep 17 00:00:00 2001 From: maartenbrinkerink <65602545+maartenbrinkerink@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:56:22 -0500 Subject: [PATCH 1/5] updated config --- config/config.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/config/config.yaml b/config/config.yaml index 28ddff4..c9e203a 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -23,8 +23,25 @@ geographic_scope: - "BTN" - "IND" +# Set to True if transmission should be included crossborderTrade: True +# Set to True if existing transmission capacities should be included +# from the Global Transmission Database (Brinkerink et al., 2024). +transmission_existing: True + +# Set to True if planned transmission capacities should be included +# from the Global Transmission Database (Brinkerink et al., 2024). +transmission_planned: True + +# Set to True if existing storage capacities should be included +# from the Global Energy Storage Database (DOE/Sandia). +storage_existing: True + +# Set to True if planned storage capacities should be included +# from the Global Energy Storage Database (DOE/Sandia). +storage_planned: True + # Emission Parameters emission_penalty: # - [EMISSION, COUNTRY, START_YEAR, END_YEAR, VALUE] From 974f08e89f4a88f9e981b0e2801de74fb99d73b6 Mon Sep 17 00:00:00 2001 From: maartenbrinkerink <65602545+maartenbrinkerink@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:58:57 -0500 Subject: [PATCH 2/5] Update preprocess.smk --- workflow/rules/preprocess.smk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workflow/rules/preprocess.smk b/workflow/rules/preprocess.smk index 2ebdfd8..3c777b0 100644 --- a/workflow/rules/preprocess.smk +++ b/workflow/rules/preprocess.smk @@ -234,6 +234,8 @@ rule transmission: transmission_build_rates = 'resources/data/transmission_build_rates.csv', params: trade = config['crossborderTrade'], + transmission_existing = config['transmission_existing'], + transmission_planned = config['transmission_planned'], start_year = config['startYear'], end_year = config['endYear'], region_name = 'GLOBAL', From 7c2a1783c1f43e4144a9e92f7b51a4acdc9eaf04 Mon Sep 17 00:00:00 2001 From: maartenbrinkerink <65602545+maartenbrinkerink@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:53:53 -0500 Subject: [PATCH 3/5] Integrated on/off toggles for GTD in config --- .../osemosys_global/transmission/main.py | 18 +++- .../transmission/residual_capacity.py | 95 +++++++++++-------- 2 files changed, 68 insertions(+), 45 deletions(-) diff --git a/workflow/scripts/osemosys_global/transmission/main.py b/workflow/scripts/osemosys_global/transmission/main.py index 5763089..4d7e44d 100644 --- a/workflow/scripts/osemosys_global/transmission/main.py +++ b/workflow/scripts/osemosys_global/transmission/main.py @@ -124,7 +124,8 @@ def main( region_name) # Set residual capacity. - res_cap_trn = res_capacity_transmission(gtd_exist_corrected, gtd_planned_corrected, + res_cap_trn = res_capacity_transmission(gtd_exist_corrected, gtd_planned_corrected, + transmission_existing, transmission_planned, res_cap_base, op_life_dict, start_year, end_year, region_name, RETIREMENT_YEAR_TRANSMISSION, @@ -230,6 +231,8 @@ def main( no_investment_techs = snakemake.params.no_investment_techs transmission_parameters = snakemake.params.transmission_parameters cross_border_trade = snakemake.params.trade + transmission_existing = snakemake.params.transmission_existing + transmission_planned = snakemake.params.transmission_planned output_data_dir = snakemake.params.output_data_dir input_data_dir = snakemake.params.input_data_dir powerplant_data_dir = snakemake.params.powerplant_data_dir @@ -263,10 +266,13 @@ def main( region_name = 'GLOBAL' custom_nodes = [] - tech_capacity_trn = {'trn1': ['TRNINDEAINDNE', 5, 1975, 2030, 10, 350, 13, 4, 95], - 'trn2': ['TRNINDEAINDNE', 1, 1990, 2030, 10, 350, 13, 4, 95], - 'trn3': ['TRNINDEAINDNE', 2, 2035, 2030, 10, 350, 13, 4, 95], - 'trn4': ['TRNINDNOINDSO', 0, 2020, 2025, 0.5, 620, 24, 4, 92]} + tech_capacity_trn = {'trn1': ['TRNINDEAINDNE', 5, 1975, 2025, 2025, 0, 350, 13, 4, 95], + 'trn2': ['TRNINDEAINDNE', 1, 1990, 2025, 2025, 0, 350, 13, 4, 95], + 'trn3': ['TRNINDEAINDNE', 2, 2035, 2025, 2025, 0, 350, 13, 4, 95], + 'trn4': ['TRNINDEAINDNE', 0, 2020, 2025, 2025, 1, 350, 13, 4, 95], + 'trn5': ['TRNINDEAINDNE', 0, 2020, 2030, 2040, 2, 350, 13, 4, 95], + 'trn6': ['TRNINDEAINDNE', 0, 2020, 2040, 2050, 3, 350, 13, 4, 95], + 'trn7': ['TRNINDNOINDSO', 0, 2020, 2025, 2025, 0.5, 620, 24, 4, 92]} no_investment_techs = ["CSP", "WAV", "URN", "OTH", "WAS", "COG", "GEO", "BIO", "PET"] @@ -274,6 +280,8 @@ def main( 'HVDC': [238, 297509, 3.5, 1.3, 3.5, 4], 'HVDC_subsea': [295, 297509, 3.5, 1.3, 3.5, 4]} cross_border_trade = True + transmission_existing = True + transmission_planned = True output_data_dir = 'results/data' input_data_dir = 'resources/data' powerplant_data_dir = 'results/data/powerplant' diff --git a/workflow/scripts/osemosys_global/transmission/residual_capacity.py b/workflow/scripts/osemosys_global/transmission/residual_capacity.py index 5b83863..7f34546 100644 --- a/workflow/scripts/osemosys_global/transmission/residual_capacity.py +++ b/workflow/scripts/osemosys_global/transmission/residual_capacity.py @@ -4,52 +4,67 @@ from data import get_years def res_capacity_transmission(df_exist_corrected, df_plan_corrected, + transmission_existing, transmission_planned, res_cap_base, op_life_dict, start_year, end_year, region_name, retirement_year_transmission, planned_build_year_transmission): - """Set build year for existing capacities as the differential between assumed - retirement year and the set operational life for transmission technologies.""" - df_res_cap_exist = df_exist_corrected.copy() - df_res_cap_exist['YEAR'] = retirement_year_transmission - op_life_dict.get('TRN') + df_res_cap = None - """Set assumed build year for planned capacities from the GTD dataset that do not have - a commissioning year attached.""" - df_res_cap_plan = df_plan_corrected.copy() - df_res_cap_plan.loc[df_res_cap_plan['YEAR'] == '-', 'YEAR'] = planned_build_year_transmission + if transmission_existing: + """Set build year for existing capacities as the differential between assumed + retirement year and the set operational life for transmission technologies.""" + df_res_cap_exist = df_exist_corrected.copy() + df_res_cap_exist['YEAR'] = retirement_year_transmission - op_life_dict.get('TRN') + + if transmission_planned: + """Set assumed build year for planned capacities from the GTD dataset that do not have + a commissioning year attached.""" + df_res_cap_plan = df_plan_corrected.copy() + df_res_cap_plan.loc[df_res_cap_plan['YEAR'] == '-', 'YEAR'] = planned_build_year_transmission - # Combine dfs and filter out rows without capacities. - df_res_cap = pd.concat([df_res_cap_exist, df_res_cap_plan]) - df_res_cap = df_res_cap.loc[df_res_cap['VALUE'] != 0 - ].reset_index(drop = True - ).rename(columns = {'YEAR' :'build_year'}) - - # Set retirement years. - df_res_cap['build_year'] = df_res_cap['build_year'].astype(int) - df_res_cap['retirement_year'] = df_res_cap['build_year'] + op_life_dict.get('TRN') - - # Set residual capacity for all model horizon years. - df_res_cap['YEAR'] = [get_years(start_year, end_year)] * len(df_res_cap) - df_res_cap = df_res_cap.explode('YEAR').reset_index(drop = True) - - # Convert from MW to GW. - df_res_cap['VALUE'] = df_res_cap['VALUE'] / 1000 - - df_res_cap.loc[~ - ((df_res_cap["YEAR"] >= df_res_cap["build_year"]) - & (df_res_cap["YEAR"] <= df_res_cap["retirement_year"])), - "VALUE", - ] = 0 - - # Group capacities by year and technology. - df_res_cap = df_res_cap.groupby(['TECHNOLOGY', 'YEAR']).sum('VALUE').reset_index() - - # Reorder columns - df_res_cap['REGION'] = region_name - df_res_cap = df_res_cap[['REGION', 'TECHNOLOGY', 'YEAR', 'VALUE']] - - # Combine powerplant and transmission resisdual capacity df's. - df_res_cap = pd.concat([res_cap_base, df_res_cap]).reset_index(drop = True) + if transmission_existing and transmission_planned: + # Combine dfs and filter out rows without capacities. + df_res_cap = pd.concat([df_res_cap_exist, df_res_cap_plan]) + if transmission_existing and not transmission_planned: + df_res_cap = df_res_cap_exist.copy() + if not transmission_existing and transmission_planned: + df_res_cap = df_res_cap_plan.copy() + + if df_res_cap is not None: + df_res_cap = df_res_cap.loc[df_res_cap['VALUE'] != 0 + ].reset_index(drop = True + ).rename(columns = {'YEAR' :'build_year'}) + + # Set retirement years. + df_res_cap['build_year'] = df_res_cap['build_year'].astype(int) + df_res_cap['retirement_year'] = df_res_cap['build_year'] + op_life_dict.get('TRN') + + # Set residual capacity for all model horizon years. + df_res_cap['YEAR'] = [get_years(start_year, end_year)] * len(df_res_cap) + df_res_cap = df_res_cap.explode('YEAR').reset_index(drop = True) + + # Convert from MW to GW. + df_res_cap['VALUE'] = df_res_cap['VALUE'] / 1000 + + df_res_cap.loc[~ + ((df_res_cap["YEAR"] >= df_res_cap["build_year"]) + & (df_res_cap["YEAR"] <= df_res_cap["retirement_year"])), + "VALUE", + ] = 0 + + # Group capacities by year and technology. + df_res_cap = df_res_cap.groupby(['TECHNOLOGY', 'YEAR']).sum('VALUE').reset_index() + + # Reorder columns + df_res_cap['REGION'] = region_name + df_res_cap = df_res_cap[['REGION', 'TECHNOLOGY', 'YEAR', 'VALUE']] + + # Combine powerplant and transmission resisdual capacity df's. + df_res_cap = pd.concat([res_cap_base, df_res_cap]).reset_index(drop = True) + + else: + df_res_cap = res_cap_base.copy() return df_res_cap \ No newline at end of file From 37cb126219f3bfca0915512d9e41d11e617b96e5 Mon Sep 17 00:00:00 2001 From: maartenbrinkerink <65602545+maartenbrinkerink@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:01:18 -0500 Subject: [PATCH 4/5] Update preprocess.smk --- workflow/rules/preprocess.smk | 2 ++ 1 file changed, 2 insertions(+) diff --git a/workflow/rules/preprocess.smk b/workflow/rules/preprocess.smk index 3c777b0..69a2af7 100644 --- a/workflow/rules/preprocess.smk +++ b/workflow/rules/preprocess.smk @@ -264,6 +264,8 @@ rule storage: gesdb_project_data = 'resources/data/GESDB_Project_Data.json', gesdb_regional_mapping = 'resources/data/GESDB_region_mapping.csv', params: + storage_existing = config['storage_existing'], + storage_planned = config['storage_planned'], start_year = config['startYear'], end_year = config['endYear'], region_name = 'GLOBAL', From 309e2d9a7982f4395a6006c72eb7bf5f095d82c5 Mon Sep 17 00:00:00 2001 From: maartenbrinkerink <65602545+maartenbrinkerink@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:56:51 -0500 Subject: [PATCH 5/5] Integrated on/off toggles for GSDB in config --- .../scripts/osemosys_global/storage/main.py | 14 +++-- .../storage/residual_capacity.py | 60 ++++++++++++------- .../storage/user_defined_capacity.py | 9 ++- 3 files changed, 53 insertions(+), 30 deletions(-) diff --git a/workflow/scripts/osemosys_global/storage/main.py b/workflow/scripts/osemosys_global/storage/main.py index 529df5d..99bee57 100644 --- a/workflow/scripts/osemosys_global/storage/main.py +++ b/workflow/scripts/osemosys_global/storage/main.py @@ -1,6 +1,7 @@ import pandas as pd import os -from typing import Optional, Any + +from typing import Optional, Any import logging @@ -155,7 +156,8 @@ def main( # Set residual capacity ('PWR') and residual capacity storage. res_cap, res_cap_storage = res_capacity_storage(gesdb_data, gesdb_mapping, - res_cap_base, op_life_dict, + res_cap_base, storage_existing, + storage_planned, op_life_dict, storage_parameters, GESDB_TECH_MAP, DURATION_TYPE, BUILD_YEAR, RETIREMENT_YEAR, @@ -250,7 +252,9 @@ def main( file_storage_build_rates = snakemake.input.storage_build_rates file_default_op_life = snakemake.input.default_op_life file_gesdb_project_data = snakemake.input.gesdb_project_data - file_gesdb_regional_mapping = snakemake.input.gesdb_regional_mapping + file_gesdb_regional_mapping = snakemake.input.gesdb_regional_mapping + storage_existing = snakemake.params.storage_existing + storage_planned = snakemake.params.storage_planned start_year = snakemake.params.start_year end_year = snakemake.params.end_year region_name = snakemake.params.region_name @@ -282,7 +286,9 @@ def main( file_storage_build_rates = 'resources/data/storage_build_rates.csv' file_default_op_life = 'resources/data/operational_life.csv' file_gesdb_project_data = 'resources/data/GESDB_Project_Data.json' - file_gesdb_regional_mapping = 'resources/data/GESDB_region_mapping.csv' + file_gesdb_regional_mapping = 'resources/data/GESDB_region_mapping.csv' + storage_existing = True + storage_planned = True start_year = 2021 end_year = 2050 region_name = 'GLOBAL' diff --git a/workflow/scripts/osemosys_global/storage/residual_capacity.py b/workflow/scripts/osemosys_global/storage/residual_capacity.py index b043ead..76d2b30 100644 --- a/workflow/scripts/osemosys_global/storage/residual_capacity.py +++ b/workflow/scripts/osemosys_global/storage/residual_capacity.py @@ -10,6 +10,8 @@ def res_capacity_storage(gesdb_data, gesdb_regional_mapping, res_cap_base, + storage_existing, + storage_planned, op_life_dict, storage_param, gesdb_tech_map, @@ -137,29 +139,41 @@ def res_capacity_storage(gesdb_data, (residual['YEAR'] == (year - 1))] new_row.loc[new_row['YEAR'] == (year - 1), 'YEAR'] = year - residual = pd.concat([residual, new_row]) + residual = pd.concat([residual, new_row]) + + if not storage_existing: + residual = residual.loc[residual['YEAR'] > start_year] + + if not storage_planned: + residual = residual.loc[residual['YEAR'] == start_year] + + if not residual.empty: - residual['REGION'] = region_name - residual = residual.drop_duplicates(subset=['REGION', 'STORAGE', 'YEAR'] - , keep = 'last').set_index(['REGION', 'STORAGE', 'YEAR']) - - # Pull power rating data for ResidualCapacity.csv - residual_capacity = residual[['rated_power']].reset_index( - drop = False).rename(columns = {'rated_power' : 'VALUE', 'STORAGE' : 'TECHNOLOGY'}) - - # Convert from kW to GW - residual_capacity['VALUE'] = round(residual_capacity['VALUE'] / 1000000, 4) - residual_capacity['TECHNOLOGY'] = 'PWR' + residual_capacity['TECHNOLOGY'] - - # Pull storage capacity data for ResidualStorageCapacity.csv - residual_storage_capacity = residual[['storage_capacity']].reset_index( - drop = False).rename(columns = {'storage_capacity' : 'VALUE'}) - - # Convert from kWh to PJ - residual_storage_capacity['VALUE'] = round(residual_storage_capacity['VALUE' - ] / 277777777.77778, 5) - - # Combine residual capacity df's. - residual_capacity = pd.concat([res_cap_base, residual_capacity]).reset_index(drop = True) + residual['REGION'] = region_name + residual = residual.drop_duplicates(subset=['REGION', 'STORAGE', 'YEAR'] + , keep = 'last').set_index(['REGION', 'STORAGE', 'YEAR']) + + # Pull power rating data for ResidualCapacity.csv + residual_capacity = residual[['rated_power']].reset_index( + drop = False).rename(columns = {'rated_power' : 'VALUE', 'STORAGE' : 'TECHNOLOGY'}) + + # Convert from kW to GW + residual_capacity['VALUE'] = round(residual_capacity['VALUE'] / 1000000, 4) + residual_capacity['TECHNOLOGY'] = 'PWR' + residual_capacity['TECHNOLOGY'] + + # Pull storage capacity data for ResidualStorageCapacity.csv + residual_storage_capacity = residual[['storage_capacity']].reset_index( + drop = False).rename(columns = {'storage_capacity' : 'VALUE'}) + + # Convert from kWh to PJ + residual_storage_capacity['VALUE'] = round(residual_storage_capacity['VALUE' + ] / 277777777.77778, 5) + + # Combine residual capacity df's. + residual_capacity = pd.concat([res_cap_base, residual_capacity]).reset_index(drop = True) + + else: + residual_capacity = res_cap_base.copy() + residual_storage_capacity = pd.DataFrame(columns = ['REGION', 'STORAGE', 'YEAR','VALUE']) return residual_capacity, residual_storage_capacity \ No newline at end of file diff --git a/workflow/scripts/osemosys_global/storage/user_defined_capacity.py b/workflow/scripts/osemosys_global/storage/user_defined_capacity.py index e9c0b8c..d2fd710 100644 --- a/workflow/scripts/osemosys_global/storage/user_defined_capacity.py +++ b/workflow/scripts/osemosys_global/storage/user_defined_capacity.py @@ -144,9 +144,12 @@ def set_user_defined_capacity_sto(tech_capacity_sto, 'STORAGE'].str.startswith(tech), 'VALUE'] = df_res_cap_sto_ud_final.loc[ df_res_cap_sto_ud_final['STORAGE'].str.startswith(tech), 'VALUE'] * \ duration / 277.777778 - - df_res_sto_cap = pd.concat([res_cap_sto_base, df_res_cap_sto_ud_final - if not df_res_cap_sto_ud_final.empty else None]) + + if not res_cap_sto_base.empty: + df_res_sto_cap = pd.concat([res_cap_sto_base, df_res_cap_sto_ud_final + if not df_res_cap_sto_ud_final.empty else None]) + else: + df_res_sto_cap = df_res_cap_sto_ud_final.copy() # Group residual capacities in case user defined technology entries already exist. df_res_sto_cap = df_res_sto_cap.groupby(['REGION', 'STORAGE', 'YEAR']