From 6caf3c64ffc7e1184516164f5150bc7184223da5 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:15:52 +0000 Subject: [PATCH 1/6] Add ruff to pyproject --- pyproject.toml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b661a0b2..c46b28fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,7 @@ jupyter = "^1.1.1" arviz = "^0.20.0" oggm = "^1.6.2" ruamel-yaml = "^0.18.10" +ruff = ">0.9.6" [tool.poetry.scripts] initialize = "pygem.bin.op.initialize:main" @@ -53,4 +54,16 @@ duplicate_gdirs = "pygem.bin.op.duplicate_gdirs:main" [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" + +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "C", # mccabe complexity + "E", "W", # Pycodestyle + "F", # Pyflakes + "I", # isort +] From 0c97a078809fb40e8691ace38519c018b8b03c37 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:16:19 +0000 Subject: [PATCH 2/6] Ignore some linting errors for now --- pyproject.toml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c46b28fb..d1fe5a37 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,3 +67,20 @@ select = [ "F", # Pyflakes "I", # isort ] +ignore = [ + "B006", # Mutable data structures in argument defaults + "B007", # Loop control variable not used within loop body + "B008", # Function call `range` in argument defaults + "B023", # Function definition does not bind loop variable + "C405", # Unnecessary list literal + "C408", # Unnecessary `dict()` call + "C414", # Unnecessary `list()` call + "C416", # Unnecessary list comprehension + "C901", # Function too complex + "E402", # Module level import not at top of file + "E501", # Line too long + "E712", # Avoid equality comparisons to `False` + "E721", # Use `is` and `is not` for type comparisons, or `isinstance()` + "E722", # Using bare `except` + "F841", # Local variable assigned to but never used +] From c9c85109321618a8b661822cdc2e2fe658632678 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:20:21 +0000 Subject: [PATCH 3/6] Add 'sample_data/' to .gitignore --- .gitignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 91f5ccd2..8ac222a3 100755 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ -# pycache +# Subdirectories __pycache__/ - -# vscode +sample_data/ .vscode/ # python bytecode From db519b74e0bba8f0841170ef3f1444588f0b5b41 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:21:02 +0000 Subject: [PATCH 4/6] Apply linting and formatting --- docs/conf.py | 45 +- pygem/__init__.py | 4 +- pygem/bin/op/duplicate_gdirs.py | 51 +- pygem/bin/op/initialize.py | 72 +- pygem/bin/op/list_failed_simulations.py | 196 +- .../postproc/postproc_binned_monthly_mass.py | 205 +- .../postproc/postproc_compile_simulations.py | 1336 ++++-- pygem/bin/postproc/postproc_distribute_ice.py | 172 +- pygem/bin/postproc/postproc_monthly_mass.py | 129 +- pygem/bin/preproc/preproc_fetch_mbdata.py | 63 +- pygem/bin/preproc/preproc_wgms_estimate_kp.py | 559 ++- pygem/bin/run/__init__.py | 2 +- pygem/bin/run/run_calibration.py | 3313 ++++++++++---- .../run/run_calibration_frontalablation.py | 4071 ++++++++++++----- pygem/bin/run/run_calibration_reg_glena.py | 610 ++- pygem/bin/run/run_mcmc_priors.py | 658 ++- pygem/bin/run/run_simulation.py | 2933 ++++++++---- pygem/class_climate.py | 696 ++- pygem/gcmbiasadj.py | 662 ++- pygem/glacierdynamics.py | 1112 +++-- pygem/massbalance.py | 1460 ++++-- pygem/mcmc.py | 392 +- pygem/oggm_compat.py | 247 +- pygem/output.py | 1394 ++++-- pygem/pygem_modelsetup.py | 424 +- pygem/scraps/dummy_task_module.py | 10 +- pygem/scraps/run.py | 18 +- pygem/setup/__init__.py | 2 +- pygem/setup/config.py | 41 +- pygem/shop/debris.py | 225 +- pygem/shop/icethickness.py | 163 +- pygem/shop/mbdata.py | 178 +- pygem/shop/oib.py | 297 +- pygem/tests/__init__.py | 2 +- pygem/tests/test_basics.py | 3 +- pygem/tests/test_oggm_compat.py | 42 +- pygem/utils/_funcs.py | 53 +- pygem/utils/_funcs_selectglaciers.py | 47 +- setup.py | 4 +- 39 files changed, 14885 insertions(+), 7006 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 930c3c44..f18f90b4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,25 +8,27 @@ import os import sys -sys.path.insert(0, os.path.abspath('../pygem/')) -project = 'PyGEM' -copyright = '2023, David Rounce' -author = 'David Rounce' -release = '1.0.1' +sys.path.insert(0, os.path.abspath("../pygem/")) + +project = "PyGEM" +copyright = "2023, David Rounce" +author = "David Rounce" +release = "1.0.1" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -extensions = ['sphinx_book_theme', - 'myst_parser', - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', - 'sphinx.ext.intersphinx', - 'numpydoc', - 'sphinx.ext.viewcode', - 'sphinx_togglebutton', - ] +extensions = [ + "sphinx_book_theme", + "myst_parser", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.intersphinx", + "numpydoc", + "sphinx.ext.viewcode", + "sphinx_togglebutton", +] myst_enable_extensions = [ "amsmath", @@ -39,21 +41,20 @@ "html_image", ] -#templates_path = ['_templates'] -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] - +# templates_path = ['_templates'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = 'sphinx_book_theme' -html_static_path = ['_static'] +html_theme = "sphinx_book_theme" +html_static_path = ["_static"] html_theme_options = { "repository_url": "https://github.com/PyGEM-Community/PyGEM", "use_repository_button": True, - "show_nav_level":2, - "navigation_depth":3, - } \ No newline at end of file + "show_nav_level": 2, + "navigation_depth": 3, +} diff --git a/pygem/__init__.py b/pygem/__init__.py index 4d90acd4..65c2ce17 100755 --- a/pygem/__init__.py +++ b/pygem/__init__.py @@ -5,8 +5,10 @@ Distrubted under the MIT lisence """ + from importlib.metadata import version + try: __version__ = version(__name__) except: - __version__ = None \ No newline at end of file + __version__ = None diff --git a/pygem/bin/op/duplicate_gdirs.py b/pygem/bin/op/duplicate_gdirs.py index 016f8c29..4b59cdb2 100644 --- a/pygem/bin/op/duplicate_gdirs.py +++ b/pygem/bin/op/duplicate_gdirs.py @@ -7,43 +7,64 @@ duplicate OGGM glacier directories """ + import argparse import os import shutil + # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() + def main(): - parser = argparse.ArgumentParser(description="Script to make duplicate oggm glacier directories - primarily to avoid corruption if parellelizing runs on a single glacier") + parser = argparse.ArgumentParser( + description="Script to make duplicate oggm glacier directories - primarily to avoid corruption if parellelizing runs on a single glacier" + ) # add arguments - parser.add_argument('-rgi_glac_number', type=str, default=None, - help='Randoph Glacier Inventory region') - parser.add_argument('-num_copies', type=int, default=1, - help='Number of copies to create of the glacier directory data') + parser.add_argument( + "-rgi_glac_number", + type=str, + default=None, + help="Randoph Glacier Inventory region", + ) + parser.add_argument( + "-num_copies", + type=int, + default=1, + help="Number of copies to create of the glacier directory data", + ) args = parser.parse_args() num_copies = args.num_copies glac_num = args.rgi_glac_number - if (glac_num is not None) and (num_copies)>1: - reg,id = glac_num.split('.') + if (glac_num is not None) and (num_copies) > 1: + reg, id = glac_num.split(".") reg = reg.zfill(2) thous = id[:2] - - root = pygem_prms['root'] + '/' + pygem_prms['oggm']['oggm_gdir_relpath'] - sfix = '/per_glacier/' + f'RGI60-{reg}/' + f'RGI60-{reg}.{thous}/' + + root = ( + pygem_prms["root"] + "/" + pygem_prms["oggm"]["oggm_gdir_relpath"] + ) + sfix = "/per_glacier/" + f"RGI60-{reg}/" + f"RGI60-{reg}.{thous}/" for n in range(num_copies): - nroot = os.path.abspath(root.replace('gdirs',f'gdirs_{n+1}')) + nroot = os.path.abspath(root.replace("gdirs", f"gdirs_{n + 1}")) # duplicate structure - os.makedirs(nroot + sfix + f'RGI60-{reg}.{id}', exist_ok=True) + os.makedirs(nroot + sfix + f"RGI60-{reg}.{id}", exist_ok=True) # copy directory data - shutil.copytree(root + sfix + f'RGI60-{reg}.{id}', nroot + sfix + f'RGI60-{reg}.{id}', dirs_exist_ok=True) + shutil.copytree( + root + sfix + f"RGI60-{reg}.{id}", + nroot + sfix + f"RGI60-{reg}.{id}", + dirs_exist_ok=True, + ) return -if __name__ == '__main__': - main() \ No newline at end of file + +if __name__ == "__main__": + main() diff --git a/pygem/bin/op/initialize.py b/pygem/bin/op/initialize.py index 21685a20..1b4ff3a1 100644 --- a/pygem/bin/op/initialize.py +++ b/pygem/bin/op/initialize.py @@ -7,42 +7,49 @@ initialization script (ensure config.yaml and get sample datasets) """ -import requests -import zipfile -import os,sys + +import os import shutil +import zipfile + +import requests from ruamel.yaml import YAML + # set up config.yaml import pygem.setup.config as config + config.ensure_config(overwrite=True) + def update_config_root(conf_path, datapath): yaml = YAML() yaml.preserve_quotes = True # Preserve quotes around string values - + # Read the YAML file - with open(conf_path, 'r') as file: + with open(conf_path, "r") as file: config = yaml.load(file) # Update the key with the new value - config['root'] = datapath + config["root"] = datapath # Save the updated configuration back to the file - with open(conf_path, 'w') as file: + with open(conf_path, "w") as file: yaml.dump(config, file) + def print_file_tree(start_path, indent=""): # Loop through all files and directories in the current directory for item in os.listdir(start_path): path = os.path.join(start_path, item) - + # Print the current item with indentation print(indent + "|-- " + item) - + # Recursively call this function if the item is a directory if os.path.isdir(path): print_file_tree(path, indent + " ") - + + def get_confirm_token(response): """Extract confirmation token for Google Drive large file download.""" for key, value in response.cookies.items(): @@ -50,6 +57,7 @@ def get_confirm_token(response): return value return None + def save_response_content(response, destination): """Save the response content to a file.""" chunk_size = 32768 @@ -58,6 +66,7 @@ def save_response_content(response, destination): if chunk: # Filter out keep-alive chunks file.write(chunk) + def get_unique_folder_name(dir): """Generate a unique folder name by appending a suffix if the folder already exists.""" counter = 1 @@ -67,14 +76,15 @@ def get_unique_folder_name(dir): counter += 1 return unique_dir + def download_and_unzip_from_google_drive(file_id, output_dir): """ Download a ZIP file from Google Drive and extract its contents. - + Args: file_id (str): The Google Drive file ID. output_dir (str): The directory to save and extract the contents of the ZIP file. - + Returns: int: 1 if the ZIP file was successfully downloaded and extracted, 0 otherwise. """ @@ -90,19 +100,29 @@ def download_and_unzip_from_google_drive(file_id, output_dir): try: # Start the download process with requests.Session() as session: - response = session.get(base_url, params={"id": file_id}, stream=True) + response = session.get( + base_url, params={"id": file_id}, stream=True + ) token = get_confirm_token(response) if token: - response = session.get(base_url, params={"id": file_id, "confirm": token}, stream=True) + response = session.get( + base_url, + params={"id": file_id, "confirm": token}, + stream=True, + ) save_response_content(response, zip_path) # Unzip the file - tmppath = os.path.join(output_dir, 'tmp') - with zipfile.ZipFile(zip_path, 'r') as zip_ref: + tmppath = os.path.join(output_dir, "tmp") + with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(tmppath) # get root dir name of zipped files - dir = [item for item in os.listdir(tmppath) if os.path.isdir(os.path.join(tmppath, item))][0] + dir = [ + item + for item in os.listdir(tmppath) + if os.path.isdir(os.path.join(tmppath, item)) + ][0] unzip_dir = os.path.join(tmppath, dir) # get unique name if root dir name already exists in output_dir output_dir = get_unique_folder_name(os.path.join(output_dir, dir)) @@ -112,19 +132,20 @@ def download_and_unzip_from_google_drive(file_id, output_dir): os.remove(zip_path) return output_dir # Success - except (requests.RequestException, zipfile.BadZipFile, Exception) as e: + except (requests.RequestException, zipfile.BadZipFile, Exception): return None # Failure - + + def main(): # Define the base directory - basedir = os.path.join(os.path.expanduser('~'), 'PyGEM') + basedir = os.path.join(os.path.expanduser("~"), "PyGEM") # Google Drive file id for sample dataset file_id = "1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ" # download and unzip out = download_and_unzip_from_google_drive(file_id, basedir) if out: - print(f"Downloaded PyGEM sample dataset:") + print("Downloaded PyGEM sample dataset:") print(os.path.abspath(out)) try: print_file_tree(out) @@ -132,13 +153,14 @@ def main(): pass else: - print(f'Error downloading PyGEM sample dataset.') + print("Error downloading PyGEM sample dataset.") # update root path in config.yaml try: - update_config_root(config.config_file, out+'/sample_data/') + update_config_root(config.config_file, out + "/sample_data/") except: pass - + + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/op/list_failed_simulations.py b/pygem/bin/op/list_failed_simulations.py index 66f83c15..e73b26e0 100644 --- a/pygem/bin/op/list_failed_simulations.py +++ b/pygem/bin/op/list_failed_simulations.py @@ -7,64 +7,90 @@ script to check for failed glaciers for a given simulation and export a pickle file containing a list of said glacier numbers to be reprocessed """ + # imports -import os +import argparse import glob -import sys import json -import argparse +import os +import sys + import numpy as np + # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() import pygem.pygem_modelsetup as modelsetup -def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_endyear): +def run( + reg, + simpath, + gcm, + scenario, + calib_opt, + bias_adj, + gcm_startyear, + gcm_endyear, +): # define base directory base_dir = simpath + "/" + str(reg).zfill(2) + "/" - # get all glaciers in region to see which fraction ran successfully - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], - rgi_regionsO2='all', rgi_glac_number='all', - glac_no=None, - debug=True) + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + glac_no=None, + debug=True, + ) - glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) + glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) - # get list of glacier simulation files + # get list of glacier simulation files if scenario: - sim_dir = base_dir + gcm + '/' + scenario + '/stats/' + sim_dir = base_dir + gcm + "/" + scenario + "/stats/" else: - sim_dir = base_dir + gcm + '/stats/' + sim_dir = base_dir + gcm + "/stats/" # check if gcm has given scenario - assert os.path.isdir(sim_dir), f'Error: simulation path not found, {sim_dir}' + assert os.path.isdir(sim_dir), ( + f"Error: simulation path not found, {sim_dir}" + ) # instantiate list of galcnos that are not in sim_dir failed_glacnos = [] - fps = glob.glob(sim_dir + f'*_{calib_opt}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc') + fps = glob.glob( + sim_dir + + f"*_{calib_opt}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc" + ) # Glaciers with successful runs to process - glacno_ran = [x.split('/')[-1].split('_')[0] for x in fps] - glacno_ran = [x.split('.')[0].zfill(2) + '.' + x[-5:] for x in glacno_ran] + glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] + glacno_ran = [x.split(".")[0].zfill(2) + "." + x[-5:] for x in glacno_ran] # print stats of successfully simualated glaciers - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1)] - print(f'{gcm} {str(scenario).replace('None','')} glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0]/main_glac_rgi_all.shape[0]*100,3)}%)') - print(f' - {np.round(main_glac_rgi.Area.sum(),0)} km2 of {np.round(main_glac_rgi_all.Area.sum(),0)} km2 ({np.round(main_glac_rgi.Area.sum()/main_glac_rgi_all.Area.sum()*100,3)}%)') - - glacno_ran = ['{0:0.5f}'.format(float(x)) for x in glacno_ran] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1) + ] + print( + f"{gcm} {str(scenario).replace('None', '')} glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0] / main_glac_rgi_all.shape[0] * 100, 3)}%)" + ) + print( + f" - {np.round(main_glac_rgi.Area.sum(), 0)} km2 of {np.round(main_glac_rgi_all.Area.sum(), 0)} km2 ({np.round(main_glac_rgi.Area.sum() / main_glac_rgi_all.Area.sum() * 100, 3)}%)" + ) + + glacno_ran = ["{0:0.5f}".format(float(x)) for x in glacno_ran] # loop through each glacier in batch list for i, glacno in enumerate(glacno_list_all): # gat glacier string and file name - glacier_str = '{0:0.5f}'.format(float(glacno)) + glacier_str = "{0:0.5f}".format(float(glacno)) if glacier_str not in glacno_ran: failed_glacnos.append(glacier_str) @@ -72,40 +98,80 @@ def run(reg, simpath, gcm, scenario, calib_opt, bias_adj, gcm_startyear, gcm_end def main(): - # Set up CLI parser = argparse.ArgumentParser( - description="""description: script to check for failed PyGEM glacier simulations\n\nexample call: $python list_failed_simulations.py -rgi_region01=1 -gcm_name=CanESM5 -scenrio=ssp585 -outdir=/path/to/output/failed/glaciers/""", - formatter_class=argparse.RawTextHelpFormatter) - requiredNamed = parser.add_argument_group('required named arguments') - requiredNamed.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-gcm_name', type=str, default=None, - help='GCM name to compile results from (ex. ERA5 or CESM2)') - parser.add_argument('-scenario', action='store', type=str, default=None, - help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-option_calibration', action='store', type=str, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ + description="""description: script to check for failed PyGEM glacier simulations\n\nexample call: $python list_failed_simulations.py -rgi_region01=1 -gcm_name=CanESM5 -scenrio=ssp585 -outdir=/path/to/output/failed/glaciers/""", + formatter_class=argparse.RawTextHelpFormatter, + ) + requiredNamed = parser.add_argument_group("required named arguments") + requiredNamed.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-gcm_name", + type=str, + default=None, + help="GCM name to compile results from (ex. ERA5 or CESM2)", + ) + parser.add_argument( + "-scenario", + action="store", + type=str, + default=None, + help="rcp or ssp scenario used for model run (ex. rcp26 or ssp585)", + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help="Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ 1: new prec scheme and temp building on HH2015, \ - 2: HH2015 methods, 3: quantile delta mapping)') - parser.add_argument('-outdir', type=str, default=None, help='directory to output json file containing list of failed glaciers in each RGI region') - parser.add_argument('-v', '--verbose', action='store_true', - help='verbose flag') + 2: HH2015 methods, 3: quantile delta mapping)", + ) + parser.add_argument( + "-outdir", + type=str, + default=None, + help="directory to output json file containing list of failed glaciers in each RGI region", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="verbose flag" + ) args = parser.parse_args() region = args.rgi_region01 scenario = args.scenario gcm_name = args.gcm_name bias_adj = args.option_bias_adjustment - simpath = pygem_prms['root']+'/Output/simulations/' + simpath = pygem_prms["root"] + "/Output/simulations/" - if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: + if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: scenario = None bias_adj = 0 @@ -113,20 +179,38 @@ def main(): region = [region] if args.outdir and not os.path.isdir(args.outdir): - print(f'Specified output path does not exist: {args.outdir}') + print(f"Specified output path does not exist: {args.outdir}") sys.exit(1) for reg in region: - failed_glacs = run(reg, simpath, args.gcm_name, scenario, args.option_calibration, bias_adj, args.gcm_startyear, args.gcm_endyear) - if len(failed_glacs)>0: + failed_glacs = run( + reg, + simpath, + args.gcm_name, + scenario, + args.option_calibration, + bias_adj, + args.gcm_startyear, + args.gcm_endyear, + ) + if len(failed_glacs) > 0: if args.outdir: - fout = os.path.join(args.outdir, f'R{str(reg).zfill(2)}_{args.gcm_name}_{scenario}_{args.gcm_startyear}_{args.gcm_endyear}_failed_rgiids.json').replace('None_','') - with open(fout, 'w') as f: + fout = os.path.join( + args.outdir, + f"R{str(reg).zfill(2)}_{args.gcm_name}_{scenario}_{args.gcm_startyear}_{args.gcm_endyear}_failed_rgiids.json", + ).replace("None_", "") + with open(fout, "w") as f: json.dump(failed_glacs, f) - print(f'List of failed glaciers for {gcm_name} {str(scenario).replace('None','')} exported to: {fout}') + print( + f"List of failed glaciers for {gcm_name} {str(scenario).replace('None', '')} exported to: {fout}" + ) if args.verbose: - print(f'Failed glaciers for RGI region R{str(reg).zfill(2)} {args.gcm_name} {str(scenario).replace('None','')} {args.gcm_startyear}-{args.gcm_endyear}:') + print( + f"Failed glaciers for RGI region R{str(reg).zfill(2)} {args.gcm_name} {str(scenario).replace('None', '')} {args.gcm_startyear}-{args.gcm_endyear}:" + ) print(failed_glacs) - else: - print(f'No glaciers failed from R{region}, for {gcm_name} {scenario.replace('None','')}') \ No newline at end of file + else: + print( + f"No glaciers failed from R{region}, for {gcm_name} {scenario.replace('None', '')}" + ) diff --git a/pygem/bin/postproc/postproc_binned_monthly_mass.py b/pygem/bin/postproc/postproc_binned_monthly_mass.py index 3fea6ba1..d2d1197e 100644 --- a/pygem/bin/postproc/postproc_binned_monthly_mass.py +++ b/pygem/bin/postproc/postproc_binned_monthly_mass.py @@ -7,37 +7,57 @@ derive binned monthly ice thickness and mass from PyGEM simulation """ + # Built-in libraries import argparse import collections -import copy -import inspect +import glob import multiprocessing import os -import glob -import sys import time + # External libraries import numpy as np import xarray as xr + # pygem imports import pygem.setup.config as config + # read config pygem_prms = config.read_config() + # ----- FUNCTIONS ----- def getparser(): """ Use argparse to add arguments from the command line """ - parser = argparse.ArgumentParser(description="process monthly ice thickness for PyGEM simulation") + parser = argparse.ArgumentParser( + description="process monthly ice thickness for PyGEM simulation" + ) # add arguments - parser.add_argument('-simpath', action='store', type=str, nargs='+', default=None, - help='path to PyGEM binned simulation (can take multiple)') - parser.add_argument('-binned_simdir', action='store', type=str, default=None, - help='directory with binned simulations for which to process monthly thickness') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') + parser.add_argument( + "-simpath", + action="store", + type=str, + nargs="+", + default=None, + help="path to PyGEM binned simulation (can take multiple)", + ) + parser.add_argument( + "-binned_simdir", + action="store", + type=str, + default=None, + help="directory with binned simulations for which to process monthly thickness", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) return parser @@ -48,19 +68,19 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): from annual climatic mass balance and annual ice thickness products to determine monthlyt thickness and mass, we must account for flux divergence - this is not so straight-forward, as PyGEM accounts for ice dynamics at the + this is not so straight-forward, as PyGEM accounts for ice dynamics at the end of each model year and not on a monthly timestep. - here, monthly thickness and mass is determined assuming + here, monthly thickness and mass is determined assuming the flux divergence is constant throughout the year. - - annual flux divergence is first estimated by combining the annual binned change in ice - thickness and the annual binned mass balance. then, assume flux divergence is constant + + annual flux divergence is first estimated by combining the annual binned change in ice + thickness and the annual binned mass balance. then, assume flux divergence is constant throughout the year (divide annual by 12 to get monthly flux divergence). - monthly binned flux divergence can then be combined with + monthly binned flux divergence can then be combined with monthly binned climatic mass balance to get monthly binned change in ice thickness - + Parameters ---------- dotb_monthly : float @@ -84,14 +104,23 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): """ ### get monthly ice thickness ### # convert mass balance from m w.e. yr^-1 to m ice yr^-1 - dotb_monthly = dotb_monthly * (pygem_prms['constants']['density_water'] / pygem_prms['constants']['density_ice']) - assert dotb_monthly.shape[2] % 12 == 0, "Number of months is not a multiple of 12!" + dotb_monthly = dotb_monthly * ( + pygem_prms["constants"]["density_water"] + / pygem_prms["constants"]["density_ice"] + ) + assert dotb_monthly.shape[2] % 12 == 0, ( + "Number of months is not a multiple of 12!" + ) # obtain annual mass balance rate, sum monthly for each year - dotb_annual = dotb_monthly.reshape(dotb_monthly.shape[0], dotb_monthly.shape[1], -1, 12).sum(axis=-1) # climatic mass balance [m ice a^-1] + dotb_annual = dotb_monthly.reshape( + dotb_monthly.shape[0], dotb_monthly.shape[1], -1, 12 + ).sum(axis=-1) # climatic mass balance [m ice a^-1] # compute the thickness change per year - delta_h_annual = np.diff(h_annual, axis=-1) # [m ice a^-1] (nbins, nyears-1) + delta_h_annual = np.diff( + h_annual, axis=-1 + ) # [m ice a^-1] (nbins, nyears-1) # compute flux divergence for each bin flux_div_annual = dotb_annual - delta_h_annual # [m ice a^-1] @@ -102,15 +131,15 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): flux_div_monthly = np.repeat(flux_div_annual / 12, 12, axis=-1) # get monthly binned change in thickness - delta_h_monthly = dotb_monthly - flux_div_monthly # [m ice per month] + delta_h_monthly = dotb_monthly - flux_div_monthly # [m ice per month] # get binned monthly thickness = running thickness change + initial thickness running_delta_h_monthly = np.cumsum(delta_h_monthly, axis=-1) - h_monthly = running_delta_h_monthly + h_annual[:,:,0][:,:,np.newaxis] + h_monthly = running_delta_h_monthly + h_annual[:, :, 0][:, :, np.newaxis] # convert to mass per unit area - m_spec_monthly = h_monthly * pygem_prms['constants']['density_ice'] - + m_spec_monthly = h_monthly * pygem_prms["constants"]["density_ice"] + ### get monthly mass ### # note, binned monthly thickness and mass is currently per unit area # obtaining binned monthly mass requires knowledge of binned glacier area @@ -118,14 +147,15 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): # so we'll resort to using the annual binned glacier mass and thickness in order to get to binned glacier area ######################## # first convert m_annual to bin_voluma_annual - v_annual = m_annual / pygem_prms['constants']['density_ice'] + v_annual = m_annual / pygem_prms["constants"]["density_ice"] # now get area: use numpy divide where denominator is greater than 0 to avoid divide error # note, indexing of [:,:,1:] so that annual area array has same shape as flux_div_annual a_annual = np.divide( - v_annual[:,:,1:], - h_annual[:,:,1:], - out=np.full(h_annual[:,:,1:].shape, np.nan), - where=h_annual[:,:,1:]>0) + v_annual[:, :, 1:], + h_annual[:, :, 1:], + out=np.full(h_annual[:, :, 1:].shape, np.nan), + where=h_annual[:, :, 1:] > 0, + ) # tile to get monthly area, assuming area is constant thoughout the year a_monthly = np.tile(a_annual, 12) @@ -144,7 +174,7 @@ def update_xrdataset(input_ds, h_monthly, m_spec_monthly, m_monthly): ---------- xrdataset : xarray Dataset existing xarray dataset - newdata : ndarray + newdata : ndarray new data array description: str describing new data field @@ -160,38 +190,51 @@ def update_xrdataset(input_ds, h_monthly, m_spec_monthly, m_monthly): bin_values = input_ds.bin.values output_coords_dict = collections.OrderedDict() - output_coords_dict['bin_thick_monthly'] = ( - collections.OrderedDict([('glac', glac_values), ('bin',bin_values), ('time', time_values)])) - output_coords_dict['bin_mass_spec_monthly'] = ( - collections.OrderedDict([('glac', glac_values), ('bin',bin_values), ('time', time_values)])) - output_coords_dict['bin_mass_monthly'] = ( - collections.OrderedDict([('glac', glac_values), ('bin',bin_values), ('time', time_values)])) + output_coords_dict["bin_thick_monthly"] = collections.OrderedDict( + [("glac", glac_values), ("bin", bin_values), ("time", time_values)] + ) + output_coords_dict["bin_mass_spec_monthly"] = collections.OrderedDict( + [("glac", glac_values), ("bin", bin_values), ("time", time_values)] + ) + output_coords_dict["bin_mass_monthly"] = collections.OrderedDict( + [("glac", glac_values), ("bin", bin_values), ("time", time_values)] + ) # Attributes dictionary output_attrs_dict = {} - output_attrs_dict['bin_thick_monthly'] = { - 'long_name': 'binned monthly ice thickness', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly ice thickness binned by surface elevation (assuming constant flux divergence throughout a given year)'} - output_attrs_dict['bin_mass_spec_monthly'] = { - 'long_name': 'binned monthly specific ice mass', - 'units': 'kg m^-2', - 'temporal_resolution': 'monthly', - 'comment': 'monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)'} - output_attrs_dict['bin_mass_monthly'] = { - 'long_name': 'binned monthly ice mass', - 'units': 'kg', - 'temporal_resolution': 'monthly', - 'comment': 'monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)'} + output_attrs_dict["bin_thick_monthly"] = { + "long_name": "binned monthly ice thickness", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly ice thickness binned by surface elevation (assuming constant flux divergence throughout a given year)", + } + output_attrs_dict["bin_mass_spec_monthly"] = { + "long_name": "binned monthly specific ice mass", + "units": "kg m^-2", + "temporal_resolution": "monthly", + "comment": "monthly ice mass per unit area binned by surface elevation (assuming constant flux divergence throughout a given year)", + } + output_attrs_dict["bin_mass_monthly"] = { + "long_name": "binned monthly ice mass", + "units": "kg", + "temporal_resolution": "monthly", + "comment": "monthly ice mass binned by surface elevation (assuming constant flux divergence and area throughout a given year)", + } # Add variables to empty dataset and merge together count_vn = 0 encoding = {} for vn in output_coords_dict.keys(): - empty_holder = np.zeros([len(output_coords_dict[vn][i]) for i in list(output_coords_dict[vn].keys())]) - output_ds = xr.Dataset({vn: (list(output_coords_dict[vn].keys()), empty_holder)}, - coords=output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(output_coords_dict[vn][i]) + for i in list(output_coords_dict[vn].keys()) + ] + ) + output_ds = xr.Dataset( + {vn: (list(output_coords_dict[vn].keys()), empty_holder)}, + coords=output_coords_dict[vn], + ) count_vn += 1 # Merge datasets of stats into one output if count_vn == 1: @@ -205,20 +248,11 @@ def update_xrdataset(input_ds, h_monthly, m_spec_monthly, m_monthly): except: pass # Encoding (specify _FillValue, offsets, etc.) - encoding[vn] = {'_FillValue': None, - 'zlib':True, - 'complevel':9 - } - - output_ds_all['bin_thick_monthly'].values = ( - h_monthly - ) - output_ds_all['bin_mass_spec_monthly'].values = ( - m_spec_monthly - ) - output_ds_all['bin_mass_monthly'].values = ( - m_monthly - ) + encoding[vn] = {"_FillValue": None, "zlib": True, "complevel": 9} + + output_ds_all["bin_thick_monthly"].values = h_monthly + output_ds_all["bin_mass_spec_monthly"].values = m_spec_monthly + output_ds_all["bin_mass_monthly"].values = m_monthly return output_ds_all, encoding @@ -242,19 +276,23 @@ def run(simpath): # calculate monthly thickness and mass h_monthly, m_spec_monthly, m_monthly = get_binned_monthly( - binned_ds.bin_massbalclim_monthly.values, - binned_ds.bin_mass_annual.values, - binned_ds.bin_thick_annual.values - ) + binned_ds.bin_massbalclim_monthly.values, + binned_ds.bin_mass_annual.values, + binned_ds.bin_thick_annual.values, + ) # update dataset to add monthly mass change - output_ds_binned, encoding_binned = update_xrdataset(binned_ds, h_monthly, m_spec_monthly, m_monthly) + output_ds_binned, encoding_binned = update_xrdataset( + binned_ds, h_monthly, m_spec_monthly, m_monthly + ) # close input ds before write binned_ds.close() # append to existing binned netcdf - output_ds_binned.to_netcdf(simpath, mode='a', encoding=encoding_binned, engine='netcdf4') + output_ds_binned.to_netcdf( + simpath, mode="a", encoding=encoding_binned, engine="netcdf4" + ) # close datasets output_ds_binned.close() @@ -269,10 +307,10 @@ def main(): if args.simpath: # filter out non-file paths simpath = [p for p in args.simpath if os.path.isfile(p)] - + elif args.binned_simdir: # get list of sims - simpath = glob.glob(args.binned_simdir+'*.nc') + simpath = glob.glob(args.binned_simdir + "*.nc") if simpath: # number of cores for parallel processing if args.ncores > 1: @@ -281,11 +319,12 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(ncores) + ' cores...') + print("Processing with " + str(ncores) + " cores...") with multiprocessing.Pool(ncores) as p: - p.map(run,simpath) + p.map(run, simpath) + + print("Total processing time:", time.time() - time_start, "s") + - print('Total processing time:', time.time()-time_start, 's') - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/postproc/postproc_compile_simulations.py b/pygem/bin/postproc/postproc_compile_simulations.py index 2f097dca..4cb4857c 100644 --- a/pygem/bin/postproc/postproc_compile_simulations.py +++ b/pygem/bin/postproc/postproc_compile_simulations.py @@ -7,73 +7,90 @@ compile individual glacier simulations to the regional level """ + # imports -import os +import argparse import glob -import sys +import multiprocessing +import os import time -import argparse -import xarray as xr +from datetime import datetime + import numpy as np +import xarray as xr + import pygem -from datetime import datetime -import multiprocessing # pygem imports -import pygem import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() import pygem.pygem_modelsetup as modelsetup -rgi_reg_dict = {'all':'Global', - 'all_no519':'Global, excl. GRL and ANT', - 'global':'Global', - 1:'Alaska', - 2:'W Canada & US', - 3:'Arctic Canada North', - 4:'Arctic Canada South', - 5:'Greenland Periphery', - 6:'Iceland', - 7:'Svalbard', - 8:'Scandinavia', - 9:'Russian Arctic', - 10:'North Asia', - 11:'Central Europe', - 12:'Caucasus & Middle East', - 13:'Central Asia', - 14:'South Asia West', - 15:'South Asia East', - 16:'Low Latitudes', - 17:'Southern Andes', - 18:'New Zealand', - 19:'Antarctic & Subantarctic' - } +rgi_reg_dict = { + "all": "Global", + "all_no519": "Global, excl. GRL and ANT", + "global": "Global", + 1: "Alaska", + 2: "W Canada & US", + 3: "Arctic Canada North", + 4: "Arctic Canada South", + 5: "Greenland Periphery", + 6: "Iceland", + 7: "Svalbard", + 8: "Scandinavia", + 9: "Russian Arctic", + 10: "North Asia", + 11: "Central Europe", + 12: "Caucasus & Middle East", + 13: "Central Asia", + 14: "South Asia West", + 15: "South Asia East", + 16: "Low Latitudes", + 17: "Southern Andes", + 18: "New Zealand", + 19: "Antarctic & Subantarctic", +} def run(args): # unpack arguments - reg, simpath, gcms, realizations, scenario, calibration, bias_adj, gcm_startyear, gcm_endyear, vars = args - print(f'RGI region {reg}') + ( + reg, + simpath, + gcms, + realizations, + scenario, + calibration, + bias_adj, + gcm_startyear, + gcm_endyear, + vars, + ) = args + print(f"RGI region {reg}") # #%% ----- PROCESS DATASETS FOR INDIVIDUAL GLACIERS AND ELEVATION BINS ----- - comppath = simpath + '/compile/' + comppath = simpath + "/compile/" # define base directory base_dir = simpath + "/" + str(reg).zfill(2) + "/" # get all glaciers in region to see which fraction ran successfully - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], - rgi_regionsO2='all', rgi_glac_number='all', - glac_no=None, - debug=True) + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + glac_no=None, + debug=True, + ) - glacno_list_all = list(main_glac_rgi_all['rgino_str'].values) + glacno_list_all = list(main_glac_rgi_all["rgino_str"].values) ### CREATE BATCHES ### # get last glacier number to define number of batches - lastn = int(sorted(glacno_list_all)[-1].split('.')[1]) + lastn = int(sorted(glacno_list_all)[-1].split(".")[1]) # round up to thosand batch_interval = 1000 last_thous = np.ceil(lastn / batch_interval) * batch_interval @@ -81,13 +98,15 @@ def run(args): nbatches = last_thous // batch_interval # split glaciers into groups of a thousand based on all glaciers in region - glacno_list_batches = modelsetup.split_list(glacno_list_all, n=nbatches, group_thousands=True) + glacno_list_batches = modelsetup.split_list( + glacno_list_all, n=nbatches, group_thousands=True + ) # make sure batch sublists are sorted properly and that each goes from NN001 to N(N+1)000 - glacno_list_batches = sorted(glacno_list_batches, key=lambda x:x[0]) - for i in range(len(glacno_list_batches) - 1): - glacno_list_batches[i].append(glacno_list_batches[i+1][0]) - glacno_list_batches[i+1].pop(0) + glacno_list_batches = sorted(glacno_list_batches, key=lambda x: x[0]) + for i in range(len(glacno_list_batches) - 1): + glacno_list_batches[i].append(glacno_list_batches[i + 1][0]) + glacno_list_batches[i + 1].pop(0) ############################################################ ### get time values - should be the same across all sims ### @@ -95,14 +114,28 @@ def run(args): if scenario: # ensure scenario has been run for each gcm for gcm in gcms: - if scenario not in os.listdir(base_dir + '/' + gcm): + if scenario not in os.listdir(base_dir + "/" + gcm): # remove the gcm from our gcm list if the desired scenario is not contained gcms.remove(gcm) - print(f'scenario {scenario} not found for {gcm}, skipping') - fn = glob.glob(base_dir + gcm + "/" + scenario + "/stats/" + f'*{gcm}_{scenario}_{realizations[0]}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_'))[0] + print(f"scenario {scenario} not found for {gcm}, skipping") + fn = glob.glob( + base_dir + + gcm + + "/" + + scenario + + "/stats/" + + f"*{gcm}_{scenario}_{realizations[0]}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + )[0] else: - fn = glob.glob(base_dir + gcm + "/stats/" + f'*{gcm}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc')[0] - nsets = fn.split('/')[-1].split('_')[-4] + fn = glob.glob( + base_dir + + gcm + + "/stats/" + + f"*{gcm}_{calibration}_ba{bias_adj}_*_{gcm_startyear}_{gcm_endyear}_all.nc" + )[0] + nsets = fn.split("/")[-1].split("_")[-4] ds_glac = xr.open_dataset(fn) year_values = ds_glac.year.values @@ -112,29 +145,33 @@ def run(args): missing_vars = list(set(vars) - set(ds_vars)) if len(missing_vars) > 0: vars = list(set(vars).intersection(ds_vars)) - print(f'Warning: Requested variables are missing: {missing_vars}') + print(f"Warning: Requested variables are missing: {missing_vars}") ############################################################ - print(f'Compiling GCMS: {gcms}') - print(f'Realizations: {realizations}') - print(f'Variables: {vars}') + print(f"Compiling GCMS: {gcms}") + print(f"Realizations: {realizations}") + print(f"Variables: {vars}") ### LEVEL I ### # loop through glacier batches of 1000 for nbatch, glacno_list in enumerate(glacno_list_batches): - print(f'Batch {nbatch}:') + print(f"Batch {nbatch}:") # batch start timer loop_start = time.time() # get batch start and end numbers - batch_start = glacno_list[0].split('.')[1] - batch_end = glacno_list[-1].split('.')[1] + batch_start = glacno_list[0].split(".")[1] + batch_end = glacno_list[-1].split(".")[1] print(nbatch, batch_start, batch_end) # get all glacier info for glaciers in batch - main_glac_rgi_batch = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_list, axis=1)] + main_glac_rgi_batch = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply( + lambda x: x.rgino_str in glacno_list, axis=1 + ) + ] # instantiate variables that will hold all concatenated data for GCMs/realizations # monthly vars @@ -150,27 +187,43 @@ def run(args): # annual vars reg_glac_allgcms_area_annual = None - reg_glac_allgcms_mass_annual = None + reg_glac_allgcms_mass_annual = None ### LEVEL II ### # for each batch, loop through GCM(s) and realization(s) for gcm in gcms: - # get list of glacier simulation files - sim_dir = base_dir + gcm + '/' + scenario + '/stats/' + # get list of glacier simulation files + sim_dir = base_dir + gcm + "/" + scenario + "/stats/" ### LEVEL III ### for realization in realizations: - print(f'GCM: {gcm} {realization}') - fps = glob.glob(sim_dir + f'*{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) + print(f"GCM: {gcm} {realization}") + fps = glob.glob( + sim_dir + + f"*{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) # during 0th batch, print the regional stats of glaciers and area successfully simulated for all regional glaciers for given gcm scenario - if nbatch==0: + if nbatch == 0: # Glaciers with successful runs to process - glacno_ran = [x.split('/')[-1].split('_')[0] for x in fps] - glacno_ran = [x.split('.')[0].zfill(2) + '.' + x[-5:] for x in glacno_ran] - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all.apply(lambda x: x.rgino_str in glacno_ran, axis=1)] - print(f'Glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0]/main_glac_rgi_all.shape[0]*100,3)}%)') - print(f' - {np.round(main_glac_rgi.Area.sum(),0)} km2 of {np.round(main_glac_rgi_all.Area.sum(),0)} km2 ({np.round(main_glac_rgi.Area.sum()/main_glac_rgi_all.Area.sum()*100,3)}%)') + glacno_ran = [x.split("/")[-1].split("_")[0] for x in fps] + glacno_ran = [ + x.split(".")[0].zfill(2) + "." + x[-5:] + for x in glacno_ran + ] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all.apply( + lambda x: x.rgino_str in glacno_ran, axis=1 + ) + ] + print( + f"Glaciers successfully simulated:\n - {main_glac_rgi.shape[0]} of {main_glac_rgi_all.shape[0]} glaciers ({np.round(main_glac_rgi.shape[0] / main_glac_rgi_all.shape[0] * 100, 3)}%)" + ) + print( + f" - {np.round(main_glac_rgi.Area.sum(), 0)} km2 of {np.round(main_glac_rgi_all.Area.sum(), 0)} km2 ({np.round(main_glac_rgi.Area.sum() / main_glac_rgi_all.Area.sum() * 100, 3)}%)" + ) # instantiate variables that will hold concatenated data for the current GCM # monthly vars @@ -186,61 +239,112 @@ def run(args): # annual vars reg_glac_gcm_area_annual = None - reg_glac_gcm_mass_annual = None - + reg_glac_gcm_mass_annual = None ### LEVEL IV ### # loop through each glacier in batch list for i, glacno in enumerate(glacno_list): # get glacier string and file name - glacier_str = '{0:0.5f}'.format(float(glacno)) - glacno_fn = f'{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + glacier_str = "{0:0.5f}".format(float(glacno)) + glacno_fn = f"{sim_dir}/{glacier_str}_{gcm}_{scenario}_{realization}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) # try to load all glaciers in region try: # open netcdf file ds_glac = xr.open_dataset(glacno_fn) # get monthly vars - glac_runoff_monthly = ds_glac.glac_runoff_monthly.values - offglac_runoff_monthly = ds_glac.offglac_runoff_monthly.values + glac_runoff_monthly = ( + ds_glac.glac_runoff_monthly.values + ) + offglac_runoff_monthly = ( + ds_glac.offglac_runoff_monthly.values + ) # try extra vars try: glac_acc_monthly = ds_glac.glac_acc_monthly.values - glac_melt_monthly = ds_glac.glac_melt_monthly.values - glac_refreeze_monthly = ds_glac.glac_refreeze_monthly.values - glac_frontalablation_monthly = ds_glac.glac_frontalablation_monthly.values - glac_massbaltotal_monthly = ds_glac.glac_massbaltotal_monthly.values - glac_prec_monthly = ds_glac.glac_prec_monthly.values - glac_mass_monthly = ds_glac.glac_mass_monthly.values + glac_melt_monthly = ( + ds_glac.glac_melt_monthly.values + ) + glac_refreeze_monthly = ( + ds_glac.glac_refreeze_monthly.values + ) + glac_frontalablation_monthly = ( + ds_glac.glac_frontalablation_monthly.values + ) + glac_massbaltotal_monthly = ( + ds_glac.glac_massbaltotal_monthly.values + ) + glac_prec_monthly = ( + ds_glac.glac_prec_monthly.values + ) + glac_mass_monthly = ( + ds_glac.glac_mass_monthly.values + ) except: - glac_acc_monthly = np.full((1,len(time_values)), np.nan) - glac_melt_monthly = np.full((1,len(time_values)), np.nan) - glac_refreeze_monthly = np.full((1,len(time_values)), np.nan) - glac_frontalablation_monthly = np.full((1,len(time_values)), np.nan) - glac_massbaltotal_monthly = np.full((1,len(time_values)), np.nan) - glac_prec_monthly = np.full((1,len(time_values)), np.nan) - glac_mass_monthly = np.full((1,len(time_values)), np.nan) + glac_acc_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_melt_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_refreeze_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_frontalablation_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_massbaltotal_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_prec_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_mass_monthly = np.full( + (1, len(time_values)), np.nan + ) # get annual vars glac_area_annual = ds_glac.glac_area_annual.values glac_mass_annual = ds_glac.glac_mass_annual.values - # if glacier output DNE in sim output file, create empty nan arrays to keep record of missing glaciers except: # monthly vars - glac_runoff_monthly = np.full((1,len(time_values)), np.nan) - offglac_runoff_monthly = np.full((1,len(time_values)), np.nan) - glac_acc_monthly = np.full((1,len(time_values)), np.nan) - glac_melt_monthly = np.full((1,len(time_values)), np.nan) - glac_refreeze_monthly = np.full((1,len(time_values)), np.nan) - glac_frontalablation_monthly = np.full((1,len(time_values)), np.nan) - glac_massbaltotal_monthly = np.full((1,len(time_values)), np.nan) - glac_prec_monthly = np.full((1,len(time_values)), np.nan) - glac_mass_monthly = np.full((1,len(time_values)), np.nan) + glac_runoff_monthly = np.full( + (1, len(time_values)), np.nan + ) + offglac_runoff_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_acc_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_melt_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_refreeze_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_frontalablation_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_massbaltotal_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_prec_monthly = np.full( + (1, len(time_values)), np.nan + ) + glac_mass_monthly = np.full( + (1, len(time_values)), np.nan + ) # annual vars - glac_area_annual = np.full((1,year_values.shape[0]), np.nan) - glac_mass_annual = np.full((1,year_values.shape[0]), np.nan) + glac_area_annual = np.full( + (1, year_values.shape[0]), np.nan + ) + glac_mass_annual = np.full( + (1, year_values.shape[0]), np.nan + ) - # append each glacier output to master regional set of arrays if reg_glac_gcm_mass_annual is None: # monthly vars @@ -249,365 +353,614 @@ def run(args): reg_glac_gcm_acc_monthly = glac_acc_monthly reg_glac_gcm_melt_monthly = glac_melt_monthly reg_glac_gcm_refreeze_monthly = glac_refreeze_monthly - reg_glac_gcm_frontalablation_monthly = glac_frontalablation_monthly - reg_glac_gcm_massbaltotal_monthly = glac_massbaltotal_monthly + reg_glac_gcm_frontalablation_monthly = ( + glac_frontalablation_monthly + ) + reg_glac_gcm_massbaltotal_monthly = ( + glac_massbaltotal_monthly + ) reg_glac_gcm_prec_monthly = glac_prec_monthly reg_glac_gcm_mass_monthly = glac_mass_monthly # annual vars reg_glac_gcm_area_annual = glac_area_annual - reg_glac_gcm_mass_annual = glac_mass_annual + reg_glac_gcm_mass_annual = glac_mass_annual # otherwise concatenate existing arrays else: # monthly vars - reg_glac_gcm_runoff_monthly = np.concatenate((reg_glac_gcm_runoff_monthly, glac_runoff_monthly), axis=0) - reg_offglac_gcm_runoff_monthly = np.concatenate((reg_offglac_gcm_runoff_monthly, offglac_runoff_monthly), axis=0) - reg_glac_gcm_acc_monthly = np.concatenate((reg_glac_gcm_acc_monthly, glac_acc_monthly), axis=0) - reg_glac_gcm_melt_monthly = np.concatenate((reg_glac_gcm_melt_monthly, glac_melt_monthly), axis=0) - reg_glac_gcm_refreeze_monthly = np.concatenate((reg_glac_gcm_refreeze_monthly, glac_refreeze_monthly), axis=0) - reg_glac_gcm_frontalablation_monthly = np.concatenate((reg_glac_gcm_frontalablation_monthly, glac_frontalablation_monthly), axis=0) - reg_glac_gcm_massbaltotal_monthly = np.concatenate((reg_glac_gcm_massbaltotal_monthly, glac_massbaltotal_monthly), axis=0) - reg_glac_gcm_prec_monthly = np.concatenate((reg_glac_gcm_prec_monthly, glac_prec_monthly), axis=0) - reg_glac_gcm_mass_monthly = np.concatenate((reg_glac_gcm_mass_monthly, glac_mass_monthly), axis=0) + reg_glac_gcm_runoff_monthly = np.concatenate( + (reg_glac_gcm_runoff_monthly, glac_runoff_monthly), + axis=0, + ) + reg_offglac_gcm_runoff_monthly = np.concatenate( + ( + reg_offglac_gcm_runoff_monthly, + offglac_runoff_monthly, + ), + axis=0, + ) + reg_glac_gcm_acc_monthly = np.concatenate( + (reg_glac_gcm_acc_monthly, glac_acc_monthly), + axis=0, + ) + reg_glac_gcm_melt_monthly = np.concatenate( + (reg_glac_gcm_melt_monthly, glac_melt_monthly), + axis=0, + ) + reg_glac_gcm_refreeze_monthly = np.concatenate( + ( + reg_glac_gcm_refreeze_monthly, + glac_refreeze_monthly, + ), + axis=0, + ) + reg_glac_gcm_frontalablation_monthly = np.concatenate( + ( + reg_glac_gcm_frontalablation_monthly, + glac_frontalablation_monthly, + ), + axis=0, + ) + reg_glac_gcm_massbaltotal_monthly = np.concatenate( + ( + reg_glac_gcm_massbaltotal_monthly, + glac_massbaltotal_monthly, + ), + axis=0, + ) + reg_glac_gcm_prec_monthly = np.concatenate( + (reg_glac_gcm_prec_monthly, glac_prec_monthly), + axis=0, + ) + reg_glac_gcm_mass_monthly = np.concatenate( + (reg_glac_gcm_mass_monthly, glac_mass_monthly), + axis=0, + ) # annual vars - reg_glac_gcm_area_annual = np.concatenate((reg_glac_gcm_area_annual, glac_area_annual), axis=0) - reg_glac_gcm_mass_annual = np.concatenate((reg_glac_gcm_mass_annual, glac_mass_annual), axis=0) + reg_glac_gcm_area_annual = np.concatenate( + (reg_glac_gcm_area_annual, glac_area_annual), + axis=0, + ) + reg_glac_gcm_mass_annual = np.concatenate( + (reg_glac_gcm_mass_annual, glac_mass_annual), + axis=0, + ) # aggregate gcms if reg_glac_allgcms_runoff_monthly is None: # monthly vars - reg_glac_allgcms_runoff_monthly = reg_glac_gcm_runoff_monthly[np.newaxis,:,:] - reg_offglac_allgcms_runoff_monthly = reg_offglac_gcm_runoff_monthly[np.newaxis,:,:] - reg_glac_allgcms_acc_monthly = reg_glac_gcm_acc_monthly[np.newaxis,:,:] - reg_glac_allgcms_melt_monthly = reg_glac_gcm_melt_monthly[np.newaxis,:,:] - reg_glac_allgcms_refreeze_monthly = reg_glac_gcm_refreeze_monthly[np.newaxis,:,:] - reg_glac_allgcms_frontalablation_monthly = reg_glac_gcm_frontalablation_monthly[np.newaxis,:,:] - reg_glac_allgcms_massbaltotal_monthly = reg_glac_gcm_massbaltotal_monthly[np.newaxis,:,:] - reg_glac_allgcms_prec_monthly = reg_glac_gcm_prec_monthly[np.newaxis,:,:] - reg_glac_allgcms_mass_monthly = reg_glac_gcm_mass_monthly[np.newaxis,:,:] + reg_glac_allgcms_runoff_monthly = ( + reg_glac_gcm_runoff_monthly[np.newaxis, :, :] + ) + reg_offglac_allgcms_runoff_monthly = ( + reg_offglac_gcm_runoff_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_acc_monthly = reg_glac_gcm_acc_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_melt_monthly = reg_glac_gcm_melt_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_refreeze_monthly = ( + reg_glac_gcm_refreeze_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_frontalablation_monthly = ( + reg_glac_gcm_frontalablation_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_massbaltotal_monthly = ( + reg_glac_gcm_massbaltotal_monthly[np.newaxis, :, :] + ) + reg_glac_allgcms_prec_monthly = reg_glac_gcm_prec_monthly[ + np.newaxis, :, : + ] + reg_glac_allgcms_mass_monthly = reg_glac_gcm_mass_monthly[ + np.newaxis, :, : + ] # annual vars - reg_glac_allgcms_area_annual = reg_glac_gcm_area_annual[np.newaxis,:,:] - reg_glac_allgcms_mass_annual = reg_glac_gcm_mass_annual[np.newaxis,:,:] + reg_glac_allgcms_area_annual = reg_glac_gcm_area_annual[ + np.newaxis, :, : + ] + reg_glac_allgcms_mass_annual = reg_glac_gcm_mass_annual[ + np.newaxis, :, : + ] else: # monthly vrs - reg_glac_allgcms_runoff_monthly = np.concatenate((reg_glac_allgcms_runoff_monthly, reg_glac_gcm_runoff_monthly[np.newaxis,:,:]), axis=0) - reg_offglac_allgcms_runoff_monthly = np.concatenate((reg_offglac_allgcms_runoff_monthly, reg_offglac_gcm_runoff_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_acc_monthly = np.concatenate((reg_glac_allgcms_acc_monthly, reg_glac_gcm_acc_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_melt_monthly = np.concatenate((reg_glac_allgcms_melt_monthly, reg_glac_gcm_melt_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_refreeze_monthly = np.concatenate((reg_glac_allgcms_refreeze_monthly, reg_glac_gcm_refreeze_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_frontalablation_monthly = np.concatenate((reg_glac_allgcms_frontalablation_monthly, reg_glac_gcm_frontalablation_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_massbaltotal_monthly = np.concatenate((reg_glac_allgcms_massbaltotal_monthly, reg_glac_gcm_massbaltotal_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_prec_monthly = np.concatenate((reg_glac_allgcms_prec_monthly, reg_glac_gcm_prec_monthly[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_mass_monthly = np.concatenate((reg_glac_allgcms_mass_monthly, reg_glac_gcm_mass_monthly[np.newaxis,:,:]), axis=0) + reg_glac_allgcms_runoff_monthly = np.concatenate( + ( + reg_glac_allgcms_runoff_monthly, + reg_glac_gcm_runoff_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_offglac_allgcms_runoff_monthly = np.concatenate( + ( + reg_offglac_allgcms_runoff_monthly, + reg_offglac_gcm_runoff_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_acc_monthly = np.concatenate( + ( + reg_glac_allgcms_acc_monthly, + reg_glac_gcm_acc_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_melt_monthly = np.concatenate( + ( + reg_glac_allgcms_melt_monthly, + reg_glac_gcm_melt_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_refreeze_monthly = np.concatenate( + ( + reg_glac_allgcms_refreeze_monthly, + reg_glac_gcm_refreeze_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_frontalablation_monthly = np.concatenate( + ( + reg_glac_allgcms_frontalablation_monthly, + reg_glac_gcm_frontalablation_monthly[ + np.newaxis, :, : + ], + ), + axis=0, + ) + reg_glac_allgcms_massbaltotal_monthly = np.concatenate( + ( + reg_glac_allgcms_massbaltotal_monthly, + reg_glac_gcm_massbaltotal_monthly[ + np.newaxis, :, : + ], + ), + axis=0, + ) + reg_glac_allgcms_prec_monthly = np.concatenate( + ( + reg_glac_allgcms_prec_monthly, + reg_glac_gcm_prec_monthly[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_mass_monthly = np.concatenate( + ( + reg_glac_allgcms_mass_monthly, + reg_glac_gcm_mass_monthly[np.newaxis, :, :], + ), + axis=0, + ) # annual vars - reg_glac_allgcms_area_annual = np.concatenate((reg_glac_allgcms_area_annual, reg_glac_gcm_area_annual[np.newaxis,:,:]), axis=0) - reg_glac_allgcms_mass_annual = np.concatenate((reg_glac_allgcms_mass_annual, reg_glac_gcm_mass_annual[np.newaxis,:,:]), axis=0) - - - #===== CREATE NETCDF FILES===== + reg_glac_allgcms_area_annual = np.concatenate( + ( + reg_glac_allgcms_area_annual, + reg_glac_gcm_area_annual[np.newaxis, :, :], + ), + axis=0, + ) + reg_glac_allgcms_mass_annual = np.concatenate( + ( + reg_glac_allgcms_mass_annual, + reg_glac_gcm_mass_annual[np.newaxis, :, :], + ), + axis=0, + ) + + # ===== CREATE NETCDF FILES===== # get common attributes - rgiid_list = ['RGI60-' + x for x in glacno_list] + rgiid_list = ["RGI60-" + x for x in glacno_list] cenlon_list = list(main_glac_rgi_batch.CenLon.values) cenlat_list = list(main_glac_rgi_batch.CenLat.values) - attrs_dict = {'Region':str(reg) + ' - ' + rgi_reg_dict[reg], - 'source': f'PyGEMv{pygem.__version__}', - 'institution': pygem_prms['user']['institution'], - 'history': f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + datetime.today().strftime('%Y-%m-%d'), - 'references': 'doi:10.1126/science.abo1324', - 'Conventions': 'CF-1.9', - 'featureType': 'timeSeries'} + attrs_dict = { + "Region": str(reg) + " - " + rgi_reg_dict[reg], + "source": f"PyGEMv{pygem.__version__}", + "institution": pygem_prms["user"]["institution"], + "history": f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + + datetime.today().strftime("%Y-%m-%d"), + "references": "doi:10.1126/science.abo1324", + "Conventions": "CF-1.9", + "featureType": "timeSeries", + } # loop through variables for var in vars: - # get common coords - if 'annual' in var: + if "annual" in var: tvals = year_values else: tvals = time_values if realizations[0]: - coords_dict=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model= (["realization"], realizations), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), - time=tvals, - ) - coord_order = ["realization","glacier","time"] + coords_dict = dict( + RGIId=(["glacier"], rgiid_list), + Climate_Model=(["realization"], realizations), + lon=(["glacier"], cenlon_list), + lat=(["glacier"], cenlat_list), + time=tvals, + ) + coord_order = ["realization", "glacier", "time"] else: - coords_dict=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model= (["model"], gcms), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), - time=tvals, - ) - coord_order = ["model","glacier","time"] - - #glac_runoff_monthly - if var=='glac_runoff_monthly': + coords_dict = dict( + RGIId=(["glacier"], rgiid_list), + Climate_Model=(["model"], gcms), + lon=(["glacier"], cenlon_list), + lat=(["glacier"], cenlat_list), + time=tvals, + ) + coord_order = ["model", "glacier", "time"] + + # glac_runoff_monthly + if var == "glac_runoff_monthly": ds = xr.Dataset( - data_vars=dict( - glac_runoff_monthly=(coord_order, reg_glac_allgcms_runoff_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_runoff_monthly.attrs['long_name'] = 'glacier-wide runoff' - ds.glac_runoff_monthly.attrs['units'] = 'm3' - ds.glac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_runoff_monthly.attrs['comment'] = 'runoff from the glacier terminus, which moves over time' - ds.glac_runoff_monthly.attrs['grid_mapping'] = 'crs' - - #offglac_runoff_monthly - elif var=='offglac_runoff_monthly': + data_vars=dict( + glac_runoff_monthly=( + coord_order, + reg_glac_allgcms_runoff_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_runoff_monthly.attrs["long_name"] = ( + "glacier-wide runoff" + ) + ds.glac_runoff_monthly.attrs["units"] = "m3" + ds.glac_runoff_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_runoff_monthly.attrs["comment"] = ( + "runoff from the glacier terminus, which moves over time" + ) + ds.glac_runoff_monthly.attrs["grid_mapping"] = "crs" + + # offglac_runoff_monthly + elif var == "offglac_runoff_monthly": ds = xr.Dataset( - data_vars=dict( - offglac_runoff_monthly=(coord_order, reg_offglac_allgcms_runoff_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.offglac_runoff_monthly.attrs['long_name'] = 'off-glacier-wide runoff' - ds.offglac_runoff_monthly.attrs['units'] = 'm3' - ds.offglac_runoff_monthly.attrs['temporal_resolution'] = 'monthly' - ds.offglac_runoff_monthly.attrs['comment'] = 'off-glacier runoff from area where glacier no longer exists' - ds.offglac_runoff_monthly.attrs['grid_mapping'] = 'crs' - - #glac_acc_monthly - elif var=='glac_acc_monthly': + data_vars=dict( + offglac_runoff_monthly=( + coord_order, + reg_offglac_allgcms_runoff_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.offglac_runoff_monthly.attrs["long_name"] = ( + "off-glacier-wide runoff" + ) + ds.offglac_runoff_monthly.attrs["units"] = "m3" + ds.offglac_runoff_monthly.attrs["temporal_resolution"] = ( + "monthly" + ) + ds.offglac_runoff_monthly.attrs["comment"] = ( + "off-glacier runoff from area where glacier no longer exists" + ) + ds.offglac_runoff_monthly.attrs["grid_mapping"] = "crs" + + # glac_acc_monthly + elif var == "glac_acc_monthly": ds = xr.Dataset( - data_vars=dict( - glac_acc_monthly=(coord_order, reg_glac_allgcms_acc_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_acc_monthly.attrs['long_name'] = 'glacier-wide accumulation, in water equivalent' - ds.glac_acc_monthly.attrs['units'] = 'm3' - ds.glac_acc_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_acc_monthly.attrs['comment'] = 'only the solid precipitation' - ds.glac_acc_monthly.attrs['grid_mapping'] = 'crs' - - #glac_melt_monthly - elif var=='glac_melt_monthly': + data_vars=dict( + glac_acc_monthly=( + coord_order, + reg_glac_allgcms_acc_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_acc_monthly.attrs["long_name"] = ( + "glacier-wide accumulation, in water equivalent" + ) + ds.glac_acc_monthly.attrs["units"] = "m3" + ds.glac_acc_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_acc_monthly.attrs["comment"] = ( + "only the solid precipitation" + ) + ds.glac_acc_monthly.attrs["grid_mapping"] = "crs" + + # glac_melt_monthly + elif var == "glac_melt_monthly": ds = xr.Dataset( - data_vars=dict( - glac_melt_monthly=(coord_order, reg_glac_allgcms_melt_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_melt_monthly.attrs['long_name'] = 'glacier-wide melt, in water equivalent' - ds.glac_melt_monthly.attrs['units'] = 'm3' - ds.glac_melt_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_melt_monthly.attrs['grid_mapping'] = 'crs' - - #glac_refreeze_monthly - elif var=='glac_refreeze_monthly': + data_vars=dict( + glac_melt_monthly=( + coord_order, + reg_glac_allgcms_melt_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_melt_monthly.attrs["long_name"] = ( + "glacier-wide melt, in water equivalent" + ) + ds.glac_melt_monthly.attrs["units"] = "m3" + ds.glac_melt_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_melt_monthly.attrs["grid_mapping"] = "crs" + + # glac_refreeze_monthly + elif var == "glac_refreeze_monthly": ds = xr.Dataset( - data_vars=dict( - glac_refreeze_monthly=(coord_order, reg_glac_allgcms_refreeze_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_refreeze_monthly.attrs['long_name'] = 'glacier-wide refreeze, in water equivalent' - ds.glac_refreeze_monthly.attrs['units'] = 'm3' - ds.glac_refreeze_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_refreeze_monthly.attrs['grid_mapping'] = 'crs' - - #glac_frontalablation_monthly - elif var=='glac_frontalablation_monthly': + data_vars=dict( + glac_refreeze_monthly=( + coord_order, + reg_glac_allgcms_refreeze_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_refreeze_monthly.attrs["long_name"] = ( + "glacier-wide refreeze, in water equivalent" + ) + ds.glac_refreeze_monthly.attrs["units"] = "m3" + ds.glac_refreeze_monthly.attrs["temporal_resolution"] = ( + "monthly" + ) + ds.glac_refreeze_monthly.attrs["grid_mapping"] = "crs" + + # glac_frontalablation_monthly + elif var == "glac_frontalablation_monthly": ds = xr.Dataset( - data_vars=dict( - glac_frontalablation_monthly=(coord_order, reg_glac_allgcms_frontalablation_monthly), - crs = np.nan - ), - coords=dict( - RGIId=(["glacier"], rgiid_list), - Climate_Model= (["model"], gcms), - lon=(["glacier"], cenlon_list), - lat=(["glacier"], cenlat_list), - time=time_values, - ), - attrs=attrs_dict - ) - ds.glac_frontalablation_monthly.attrs['long_name'] = 'glacier-wide frontal ablation, in water equivalent' - ds.glac_frontalablation_monthly.attrs['units'] = 'm3' - ds.glac_frontalablation_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_frontalablation_monthly.attrs['comment'] = 'mass losses from calving, subaerial frontal melting, \ + data_vars=dict( + glac_frontalablation_monthly=( + coord_order, + reg_glac_allgcms_frontalablation_monthly, + ), + crs=np.nan, + ), + coords=dict( + RGIId=(["glacier"], rgiid_list), + Climate_Model=(["model"], gcms), + lon=(["glacier"], cenlon_list), + lat=(["glacier"], cenlat_list), + time=time_values, + ), + attrs=attrs_dict, + ) + ds.glac_frontalablation_monthly.attrs["long_name"] = ( + "glacier-wide frontal ablation, in water equivalent" + ) + ds.glac_frontalablation_monthly.attrs["units"] = "m3" + ds.glac_frontalablation_monthly.attrs[ + "temporal_resolution" + ] = "monthly" + ds.glac_frontalablation_monthly.attrs["comment"] = ( + "mass losses from calving, subaerial frontal melting, \ sublimation above the waterline and subaqueous frontal melting below the waterline; \ - positive values indicate mass lost like melt' - ds.glac_frontalablation_monthly.attrs['grid_mapping'] = 'crs' + positive values indicate mass lost like melt" + ) + ds.glac_frontalablation_monthly.attrs["grid_mapping"] = "crs" - #glac_massbaltotal_monthly - elif var=='glac_massbaltotal_monthly': + # glac_massbaltotal_monthly + elif var == "glac_massbaltotal_monthly": ds = xr.Dataset( - data_vars=dict( - glac_massbaltotal_monthly=(coord_order, reg_glac_allgcms_massbaltotal_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_massbaltotal_monthly.attrs['long_name'] = 'glacier-wide total mass balance, in water equivalent' - ds.glac_massbaltotal_monthly.attrs['units'] = 'm3' - ds.glac_massbaltotal_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_massbaltotal_monthly.attrs['comment'] = 'total mass balance is the sum of the climatic mass balance and frontal ablation' - ds.glac_massbaltotal_monthly.attrs['grid_mapping'] = 'crs' - - - #glac_prec_monthly - elif var=='glac_prec_monthly': + data_vars=dict( + glac_massbaltotal_monthly=( + coord_order, + reg_glac_allgcms_massbaltotal_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_massbaltotal_monthly.attrs["long_name"] = ( + "glacier-wide total mass balance, in water equivalent" + ) + ds.glac_massbaltotal_monthly.attrs["units"] = "m3" + ds.glac_massbaltotal_monthly.attrs["temporal_resolution"] = ( + "monthly" + ) + ds.glac_massbaltotal_monthly.attrs["comment"] = ( + "total mass balance is the sum of the climatic mass balance and frontal ablation" + ) + ds.glac_massbaltotal_monthly.attrs["grid_mapping"] = "crs" + + # glac_prec_monthly + elif var == "glac_prec_monthly": ds = xr.Dataset( - data_vars=dict( - glac_prec_monthly=(coord_order, reg_glac_allgcms_prec_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_prec_monthly.attrs['long_name'] = 'glacier-wide precipitation (liquid)' - ds.glac_prec_monthly.attrs['units'] = 'm3' - ds.glac_prec_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_prec_monthly.attrs['comment'] = 'only the liquid precipitation, solid precipitation excluded' - ds.glac_prec_monthly.attrs['grid_mapping'] = 'crs' - - #glac_mass_monthly - elif var=='glac_mass_monthly': + data_vars=dict( + glac_prec_monthly=( + coord_order, + reg_glac_allgcms_prec_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_prec_monthly.attrs["long_name"] = ( + "glacier-wide precipitation (liquid)" + ) + ds.glac_prec_monthly.attrs["units"] = "m3" + ds.glac_prec_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_prec_monthly.attrs["comment"] = ( + "only the liquid precipitation, solid precipitation excluded" + ) + ds.glac_prec_monthly.attrs["grid_mapping"] = "crs" + + # glac_mass_monthly + elif var == "glac_mass_monthly": ds = xr.Dataset( - data_vars=dict( - glac_mass_monthly=(coord_order, reg_glac_allgcms_mass_monthly), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_mass_monthly.attrs['long_name'] = 'glacier mass' - ds.glac_mass_monthly.attrs['units'] = 'kg' - ds.glac_mass_monthly.attrs['temporal_resolution'] = 'monthly' - ds.glac_mass_monthly.attrs['comment'] = 'mass of ice based on area and ice thickness at start of the year and the monthly total mass balance' - ds.glac_mass_monthly.attrs['grid_mapping'] = 'crs' - - #glac_area_annual - elif var=='glac_area_annual': + data_vars=dict( + glac_mass_monthly=( + coord_order, + reg_glac_allgcms_mass_monthly, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_mass_monthly.attrs["long_name"] = "glacier mass" + ds.glac_mass_monthly.attrs["units"] = "kg" + ds.glac_mass_monthly.attrs["temporal_resolution"] = "monthly" + ds.glac_mass_monthly.attrs["comment"] = ( + "mass of ice based on area and ice thickness at start of the year and the monthly total mass balance" + ) + ds.glac_mass_monthly.attrs["grid_mapping"] = "crs" + + # glac_area_annual + elif var == "glac_area_annual": ds = xr.Dataset( - data_vars=dict( - glac_area_annual=(coord_order, reg_glac_allgcms_area_annual), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_area_annual.attrs['long_name'] = 'glacier area' - ds.glac_area_annual.attrs['units'] = 'm2' - ds.glac_area_annual.attrs['temporal_resolution'] = 'annual' - ds.glac_area_annual.attrs['comment'] = 'area at start of the year' - ds.glac_area_annual.attrs['grid_mapping'] = 'crs' - - #glac_mass_annual - elif var=='glac_mass_annual': + data_vars=dict( + glac_area_annual=( + coord_order, + reg_glac_allgcms_area_annual, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_area_annual.attrs["long_name"] = "glacier area" + ds.glac_area_annual.attrs["units"] = "m2" + ds.glac_area_annual.attrs["temporal_resolution"] = "annual" + ds.glac_area_annual.attrs["comment"] = ( + "area at start of the year" + ) + ds.glac_area_annual.attrs["grid_mapping"] = "crs" + + # glac_mass_annual + elif var == "glac_mass_annual": ds = xr.Dataset( - data_vars=dict( - glac_mass_annual=(coord_order, reg_glac_allgcms_mass_annual), - crs = np.nan - ), - coords=coords_dict, - attrs=attrs_dict - ) - ds.glac_mass_annual.attrs['long_name'] = 'glacier mass' - ds.glac_mass_annual.attrs['units'] = 'kg' - ds.glac_mass_annual.attrs['temporal_resolution'] = 'annual' - ds.glac_mass_annual.attrs['comment'] = 'mass of ice based on area and ice thickness at start of the year' - ds.glac_mass_annual.attrs['grid_mapping'] = 'crs' - + data_vars=dict( + glac_mass_annual=( + coord_order, + reg_glac_allgcms_mass_annual, + ), + crs=np.nan, + ), + coords=coords_dict, + attrs=attrs_dict, + ) + ds.glac_mass_annual.attrs["long_name"] = "glacier mass" + ds.glac_mass_annual.attrs["units"] = "kg" + ds.glac_mass_annual.attrs["temporal_resolution"] = "annual" + ds.glac_mass_annual.attrs["comment"] = ( + "mass of ice based on area and ice thickness at start of the year" + ) + ds.glac_mass_annual.attrs["grid_mapping"] = "crs" + # crs attributes - same for all vars - ds.crs.attrs['grid_mapping_name'] = 'latitude_longitude' - ds.crs.attrs['longitude_of_prime_meridian'] = 0.0 - ds.crs.attrs['semi_major_axis'] = 6378137.0 - ds.crs.attrs['inverse_flattening'] = 298.257223563 - ds.crs.attrs['proj4text'] = '+proj=longlat +datum=WGS84 +no_defs' - ds.crs.attrs['crs_wkt'] = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' - + ds.crs.attrs["grid_mapping_name"] = "latitude_longitude" + ds.crs.attrs["longitude_of_prime_meridian"] = 0.0 + ds.crs.attrs["semi_major_axis"] = 6378137.0 + ds.crs.attrs["inverse_flattening"] = 298.257223563 + ds.crs.attrs["proj4text"] = "+proj=longlat +datum=WGS84 +no_defs" + ds.crs.attrs["crs_wkt"] = ( + 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]' + ) + # time attributes - different for monthly v annual - ds.time.attrs['long_name'] = 'time' - if 'annual' in var: - ds.time.attrs['range'] = str(year_values[0]) + ' - ' + str(year_values[-1]) - ds.time.attrs['comment'] = 'years referring to the start of each year' - elif 'monthly' in var: - ds.time.attrs['range'] = str(time_values[0]) + ' - ' + str(time_values[-1]) - ds.time.attrs['comment'] = 'start of the month' - - ds.RGIId.attrs['long_name'] = 'Randolph Glacier Inventory Id' - ds.RGIId.attrs['comment'] = 'RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)' - ds.RGIId.attrs['cf_role'] = 'timeseries_id' + ds.time.attrs["long_name"] = "time" + if "annual" in var: + ds.time.attrs["range"] = ( + str(year_values[0]) + " - " + str(year_values[-1]) + ) + ds.time.attrs["comment"] = ( + "years referring to the start of each year" + ) + elif "monthly" in var: + ds.time.attrs["range"] = ( + str(time_values[0]) + " - " + str(time_values[-1]) + ) + ds.time.attrs["comment"] = "start of the month" + + ds.RGIId.attrs["long_name"] = "Randolph Glacier Inventory Id" + ds.RGIId.attrs["comment"] = ( + "RGIv6.0 (https://nsidc.org/data/nsidc-0770/versions/6)" + ) + ds.RGIId.attrs["cf_role"] = "timeseries_id" if realizations[0]: - ds.Climate_Model.attrs['long_name'] = f'{gcms[0]} realization' + ds.Climate_Model.attrs["long_name"] = f"{gcms[0]} realization" else: - ds.Climate_Model.attrs['long_name'] = 'General Circulation Model' - - ds.lon.attrs['standard_name'] = 'longitude' - ds.lon.attrs['long_name'] = 'longitude of glacier center' - ds.lon.attrs['units'] = 'degrees_east' - - ds.lat.attrs['standard_name'] = 'latitude' - ds.lat.attrs['long_name'] = 'latitude of glacier center' - ds.lat.attrs['units'] = 'degrees_north' - + ds.Climate_Model.attrs["long_name"] = ( + "General Circulation Model" + ) + + ds.lon.attrs["standard_name"] = "longitude" + ds.lon.attrs["long_name"] = "longitude of glacier center" + ds.lon.attrs["units"] = "degrees_east" + + ds.lat.attrs["standard_name"] = "latitude" + ds.lat.attrs["long_name"] = "latitude of glacier center" + ds.lat.attrs["units"] = "degrees_north" + # save batch - vn_fp = f'{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/' + vn_fp = f"{comppath}/glacier_stats/{var}/{str(reg).zfill(2)}/" if not os.path.exists(vn_fp): os.makedirs(vn_fp, exist_ok=True) - + if realizations[0]: - ds_fn = f'R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + ds_fn = f"R{str(reg).zfill(2)}_{var}_{gcms[0]}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) else: - ds_fn = f'R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_') + ds_fn = f"R{str(reg).zfill(2)}_{var}_{scenario}_Batch-{str(batch_start)}-{str(batch_end)}_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) ds.to_netcdf(vn_fp + ds_fn) loop_end = time.time() - print(f'Batch {nbatch} runtime:\t{np.round(loop_end - loop_start,2)} seconds') - - + print( + f"Batch {nbatch} runtime:\t{np.round(loop_end - loop_start, 2)} seconds" + ) + ### MERGE BATCHES FOR ANNUAL VARS ### - vns = ['glac_mass_annual', 'glac_area_annual'] + vns = ["glac_mass_annual", "glac_area_annual"] for vn in vns: if vn in vars: - vn_fp = f'{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/' + vn_fp = f"{comppath}glacier_stats/{vn}/{str(reg).zfill(2)}/" fn_merge_list_start = [] if realizations[0]: - fn_merge_list = glob.glob(f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{gcms[0]}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) + fn_merge_list = glob.glob( + f"{vn_fp}/R{str(reg).zfill(2)}_{vn}_{gcms[0]}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) else: - fn_merge_list = glob.glob(f'{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc'.replace('__','_')) - fn_merge_list_start = [int(f.split('-')[-2]) for f in fn_merge_list] - + fn_merge_list = glob.glob( + f"{vn_fp}/R{str(reg).zfill(2)}_{vn}_{scenario}_Batch-*_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc".replace( + "__", "_" + ) + ) + fn_merge_list_start = [ + int(f.split("-")[-2]) for f in fn_merge_list + ] + if len(fn_merge_list) > 0: - fn_merge_list = [x for _,x in sorted(zip(fn_merge_list_start,fn_merge_list))] - + fn_merge_list = [ + x + for _, x in sorted(zip(fn_merge_list_start, fn_merge_list)) + ] + ds = None for fn in fn_merge_list: ds_batch = xr.open_dataset(fn) - + if ds is None: ds = ds_batch else: ds = xr.concat([ds, ds_batch], dim="glacier") # save - ds_fn = fn.split('Batch')[0][:-1] + f'_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc' + ds_fn = ( + fn.split("Batch")[0][:-1] + + f"_{calibration}_ba{bias_adj}_{nsets}_{gcm_startyear}_{gcm_endyear}_all.nc" + ) ds.to_netcdf(ds_fn) - + ds_batch.close() - + for fn in fn_merge_list: os.remove(fn) @@ -617,32 +970,102 @@ def main(): # Set up CLI parser = argparse.ArgumentParser( - description="""description: program for compiling regional stats from the python glacier evolution model (PyGEM)\nnote, this script is not embarrassingly parallel\nit is currently set up to be parallelized by splitting into n jobs based on the number of regions and scenarios scecified\nfor example, the call below could be parallelized into 4 jobs (2 regions x 2 scenarios)\n\nexample call: $python compile_simulations -rgi_region 01 02 -scenario ssp345 ssp585 -gcm_startyear2000 -gcm_endyear 2100 -ncores 4 -vars glac_mass_annual glac_area_annual""", - formatter_class=argparse.RawTextHelpFormatter) - requiredNamed = parser.add_argument_group('required named arguments') - requiredNamed.add_argument('-rgi_region01', type=int, default=None, required=True, nargs='+', - help='Randoph Glacier Inventory region (can take multiple, e.g. "1 2 3")') - requiredNamed.add_argument('-gcm_name', type=str, default=None, required=True, nargs='+', - help='GCM name for which to compile simulations (can take multiple, ex. "ERA5" or "CESM2")') - parser.add_argument('-scenario', action='store', type=str, default=None, nargs='+', - help='rcp or ssp scenario used for model run (can take multiple, ex. "ssp245 ssp585")') - parser.add_argument('-realization', action='store', type=str, default=None, nargs='+', - help='realization from large ensemble used for model run (cant take multiple, ex. "r1i1p1f1 r2i1p1f1 r3i1p1f1")') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-sim_path', type=str, default=pygem_prms['root'] + '/Output/simulations/', - help='PyGEM simulations filepath') - parser.add_argument('-option_calibration', action='store', type=str, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: 0, "1", "2", "3".\n0: no adjustment\n1: new prec scheme and temp building on HH2015\n2: HH2015 methods\n3: quantile delta mapping)') - parser.add_argument('-vars',type=str, help='comm delimited list of PyGEM variables to compile (can take multiple, ex. "monthly_mass annual_area")', - choices=['glac_runoff_monthly','offglac_runoff_monthly','glac_acc_monthly','glac_melt_monthly','glac_refreeze_monthly','glac_frontalablation_monthly','glac_massbaltotal_monthly','glac_prec_monthly','glac_mass_monthly','glac_mass_annual','glac_area_annual'], - nargs='+') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization') + description="""description: program for compiling regional stats from the python glacier evolution model (PyGEM)\nnote, this script is not embarrassingly parallel\nit is currently set up to be parallelized by splitting into n jobs based on the number of regions and scenarios scecified\nfor example, the call below could be parallelized into 4 jobs (2 regions x 2 scenarios)\n\nexample call: $python compile_simulations -rgi_region 01 02 -scenario ssp345 ssp585 -gcm_startyear2000 -gcm_endyear 2100 -ncores 4 -vars glac_mass_annual glac_area_annual""", + formatter_class=argparse.RawTextHelpFormatter, + ) + requiredNamed = parser.add_argument_group("required named arguments") + requiredNamed.add_argument( + "-rgi_region01", + type=int, + default=None, + required=True, + nargs="+", + help='Randoph Glacier Inventory region (can take multiple, e.g. "1 2 3")', + ) + requiredNamed.add_argument( + "-gcm_name", + type=str, + default=None, + required=True, + nargs="+", + help='GCM name for which to compile simulations (can take multiple, ex. "ERA5" or "CESM2")', + ) + parser.add_argument( + "-scenario", + action="store", + type=str, + default=None, + nargs="+", + help='rcp or ssp scenario used for model run (can take multiple, ex. "ssp245 ssp585")', + ) + parser.add_argument( + "-realization", + action="store", + type=str, + default=None, + nargs="+", + help='realization from large ensemble used for model run (cant take multiple, ex. "r1i1p1f1 r2i1p1f1 r3i1p1f1")', + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-sim_path", + type=str, + default=pygem_prms["root"] + "/Output/simulations/", + help="PyGEM simulations filepath", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help='Bias adjustment option (options: 0, "1", "2", "3".\n0: no adjustment\n1: new prec scheme and temp building on HH2015\n2: HH2015 methods\n3: quantile delta mapping)', + ) + parser.add_argument( + "-vars", + type=str, + help='comm delimited list of PyGEM variables to compile (can take multiple, ex. "monthly_mass annual_area")', + choices=[ + "glac_runoff_monthly", + "offglac_runoff_monthly", + "glac_acc_monthly", + "glac_melt_monthly", + "glac_refreeze_monthly", + "glac_frontalablation_monthly", + "glac_massbaltotal_monthly", + "glac_prec_monthly", + "glac_mass_monthly", + "glac_mass_annual", + "glac_area_annual", + ], + nargs="+", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + ) args = parser.parse_args() simpath = args.sim_path @@ -657,10 +1080,10 @@ def main(): vars = args.vars if not simpath: - simpath = pygem_prms['root'] + '/Output/simulations/' + simpath = pygem_prms["root"] + "/Output/simulations/" - if not os.path.exists(simpath + 'compile/'): - os.makedirs(simpath + 'compile/') + if not os.path.exists(simpath + "compile/"): + os.makedirs(simpath + "compile/") if not isinstance(region, list): region = [region] @@ -671,23 +1094,41 @@ def main(): if scenarios: if not isinstance(scenarios, list): scenarios = [scenarios] - if set(['ERA5', 'ERA-Interim', 'COAWST']) & set(gcms): - raise ValueError(f'Cannot compile present-day and future data simulataneously. A scenario was specified, which does not exist for one of the specified GCMs.\nGCMs: {gcms}\nScenarios: {scenarios}') - else: - scenarios = [''] - if set(gcms) - set(['ERA5', 'ERA-Interim', 'COAWST']): - raise ValueError(f'Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}') + if set(["ERA5", "ERA-Interim", "COAWST"]) & set(gcms): + raise ValueError( + f"Cannot compile present-day and future data simulataneously. A scenario was specified, which does not exist for one of the specified GCMs.\nGCMs: {gcms}\nScenarios: {scenarios}" + ) + else: + scenarios = [""] + if set(gcms) - set(["ERA5", "ERA-Interim", "COAWST"]): + raise ValueError( + f"Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}" + ) if realizations is None: - realizations = [''] + realizations = [""] else: if not isinstance(realizations, list): realizations = [realizations] if len(gcms) > 1: - raise ValueError(f'Script not set up to aggregate multiple GCMs and realizations simultaneously - if aggregating multiple realizations, specify a single GCM at a time\nGCMs: {gcms}\nrealizations: {realizations}') + raise ValueError( + f"Script not set up to aggregate multiple GCMs and realizations simultaneously - if aggregating multiple realizations, specify a single GCM at a time\nGCMs: {gcms}\nrealizations: {realizations}" + ) if not vars: - vars = ['glac_runoff_monthly','offglac_runoff_monthly','glac_acc_monthly','glac_melt_monthly','glac_refreeze_monthly','glac_frontalablation_monthly','glac_massbaltotal_monthly','glac_prec_monthly','glac_mass_monthly','glac_mass_annual','glac_area_annual'] + vars = [ + "glac_runoff_monthly", + "offglac_runoff_monthly", + "glac_acc_monthly", + "glac_melt_monthly", + "glac_refreeze_monthly", + "glac_frontalablation_monthly", + "glac_massbaltotal_monthly", + "glac_prec_monthly", + "glac_mass_monthly", + "glac_mass_annual", + "glac_area_annual", + ] # get number of jobs and split into desired number of cores njobs = int(len(region) * len(scenarios)) @@ -699,22 +1140,53 @@ def main(): # pack variables for multiprocessing list_packed_vars = [] - kwargs=['region', 'simpath', 'gcms', 'realizations', 'scenario', 'calib', 'bias_adj', 'gcm_startyear', 'gcm_endyear', 'vars'] - i=0 + kwargs = [ + "region", + "simpath", + "gcms", + "realizations", + "scenario", + "calib", + "bias_adj", + "gcm_startyear", + "gcm_endyear", + "vars", + ] + i = 0 # if realizations specified, aggregate all realizations for each gcm and scenario by region for sce in scenarios: for reg in region: - list_packed_vars.append([reg, simpath, gcms, realizations, sce, calib, bias_adj, gcm_startyear, gcm_endyear, vars]) - print(f'job {i}:', [f'{name}={val}' for name, val in zip(kwargs,list_packed_vars[-1])]) - i+=1 + list_packed_vars.append( + [ + reg, + simpath, + gcms, + realizations, + sce, + calib, + bias_adj, + gcm_startyear, + gcm_endyear, + vars, + ] + ) + print( + f"job {i}:", + [ + f"{name}={val}" + for name, val in zip(kwargs, list_packed_vars[-1]) + ], + ) + i += 1 # parallel processing - print('Processing with ' + str(num_cores) + ' cores...') + print("Processing with " + str(num_cores) + " cores...") with multiprocessing.Pool(num_cores) as p: - p.map(run, list_packed_vars) + p.map(run, list_packed_vars) end = time.time() - print(f'Total runtime: {np.round(end - start,2)} seconds') + print(f"Total runtime: {np.round(end - start, 2)} seconds") + -if __name__=='__main__': - main() \ No newline at end of file +if __name__ == "__main__": + main() diff --git a/pygem/bin/postproc/postproc_distribute_ice.py b/pygem/bin/postproc/postproc_distribute_ice.py index 892c335a..d32f4fc0 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -5,46 +5,61 @@ Distrubted under the MIT lisence """ + # Built-in libraries import argparse -import collections -import copy -import inspect import multiprocessing import os -import glob -import sys import time from functools import partial + import matplotlib.pyplot as plt + # External libraries import numpy as np import xarray as xr + # oggm -from oggm import workflow, tasks, cfg +from oggm import tasks, workflow from oggm.sandbox import distribute_2d + # pygem imports import pygem.setup.config as config + # read config pygem_prms = config.read_config() -import pygem import pygem.pygem_modelsetup as modelsetup -from pygem.oggm_compat import single_flowline_glacier_directory -from pygem.oggm_compat import single_flowline_glacier_directory_with_calving +from pygem.oggm_compat import ( + single_flowline_glacier_directory, + single_flowline_glacier_directory_with_calving, +) def getparser(): """ Use argparse to add arguments from the command line """ - parser = argparse.ArgumentParser(description="distrube PyGEM simulated ice thickness to a 2D grid") + parser = argparse.ArgumentParser( + description="distrube PyGEM simulated ice thickness to a 2D grid" + ) # add arguments - parser.add_argument('-simpath', action='store', type=str, nargs='+', - help='path to PyGEM binned simulation (can take multiple)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-simpath", + action="store", + type=str, + nargs="+", + help="path to PyGEM binned simulation (can take multiple)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument( + "-v", "--debug", action="store_true", help="Flag for debugging" + ) return parser @@ -52,8 +67,8 @@ def getparser(): def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): """ take PyGEM model output and temporarily store it in a way that OGGM distribute_2d expects - this will be a netcdf file named fl_diagnostics.nc within the glacier directory - which contains - the following coordinates: + this will be a netcdf file named fl_diagnostics.nc within the glacier directory - which contains + the following coordinates: dis_along_flowline (dis_along_flowline): float64, along-flowline distance in m time (time): float64, model time in years and the following data variables: @@ -61,32 +76,34 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): area_m2(time, dis_along_flowline): float64 thickness_m (time, dis_along_flowline): float64 """ - yr0,yr1 = pygem_simpath.split('_')[-3:-1] + yr0, yr1 = pygem_simpath.split("_")[-3:-1] pygem_ds = xr.open_dataset(pygem_simpath).sel(year=slice(yr0, yr1)) - time = pygem_ds.coords['year'].values.flatten().astype(float) - distance_along_flowline = pygem_ds['bin_distance'].values.flatten().astype(float) - area = pygem_ds['bin_area_annual'].values[0].astype(float).T - thick = pygem_ds['bin_thick_annual'].values[0].astype(float).T + time = pygem_ds.coords["year"].values.flatten().astype(float) + distance_along_flowline = ( + pygem_ds["bin_distance"].values.flatten().astype(float) + ) + area = pygem_ds["bin_area_annual"].values[0].astype(float).T + thick = pygem_ds["bin_thick_annual"].values[0].astype(float).T vol = area * thick diag_ds = xr.Dataset() - diag_ds.coords['time'] = time - diag_ds.coords['dis_along_flowline'] = distance_along_flowline - diag_ds['area_m2'] = (('time', 'dis_along_flowline'), area) - diag_ds['area_m2'].attrs['description'] = 'Section area' - diag_ds['area_m2'].attrs['unit'] = 'm 2' - diag_ds['thickness_m'] = (('time', 'dis_along_flowline'), thick * np.nan) - diag_ds['thickness_m'].attrs['description'] = 'Section thickness' - diag_ds['thickness_m'].attrs['unit'] = 'm' - diag_ds['volume_m3'] = (('time', 'dis_along_flowline'), vol) - diag_ds['volume_m3'].attrs['description'] = 'Section volume' - diag_ds['volume_m3'].attrs['unit'] = 'm 3' + diag_ds.coords["time"] = time + diag_ds.coords["dis_along_flowline"] = distance_along_flowline + diag_ds["area_m2"] = (("time", "dis_along_flowline"), area) + diag_ds["area_m2"].attrs["description"] = "Section area" + diag_ds["area_m2"].attrs["unit"] = "m 2" + diag_ds["thickness_m"] = (("time", "dis_along_flowline"), thick * np.nan) + diag_ds["thickness_m"].attrs["description"] = "Section thickness" + diag_ds["thickness_m"].attrs["unit"] = "m" + diag_ds["volume_m3"] = (("time", "dis_along_flowline"), vol) + diag_ds["volume_m3"].attrs["description"] = "Section volume" + diag_ds["volume_m3"].attrs["unit"] = "m 3" # diag_ds.to_netcdf(oggm_diag, 'w', group='fl_0') if debug: # plot volume - vol = diag_ds.sum(dim=['dis_along_flowline'])['volume_m3'] - f,ax = plt.subplots(1,figsize=(5,5)) - (vol/vol[0]).plot(ax=ax) + vol = diag_ds.sum(dim=["dis_along_flowline"])["volume_m3"] + f, ax = plt.subplots(1, figsize=(5, 5)) + (vol / vol[0]).plot(ax=ax) plt.show() return diag_ds @@ -94,57 +111,87 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): def plot_distributed_thickness(ds): f, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4)) - vmax = round(np.nanmax(ds.simulated_thickness.sel(time=ds.coords['time'].values[0]))/25) * 25 - ds.simulated_thickness.sel(time=ds.coords['time'].values[0]).plot(ax=ax1, vmin=0, vmax=vmax,add_colorbar=False) - ds.simulated_thickness.sel(time=ds.coords['time'].values[-1]).plot(ax=ax2, vmin=0, vmax=vmax) - ax1.axis('equal'); ax2.axis('equal') + vmax = ( + round( + np.nanmax( + ds.simulated_thickness.sel(time=ds.coords["time"].values[0]) + ) + / 25 + ) + * 25 + ) + ds.simulated_thickness.sel(time=ds.coords["time"].values[0]).plot( + ax=ax1, vmin=0, vmax=vmax, add_colorbar=False + ) + ds.simulated_thickness.sel(time=ds.coords["time"].values[-1]).plot( + ax=ax2, vmin=0, vmax=vmax + ) + ax1.axis("equal") + ax2.axis("equal") plt.tight_layout() plt.show() def run(simpath, debug=False): - if os.path.isfile(simpath): pygem_path, pygem_fn = os.path.split(simpath) - pygem_fn_split = pygem_fn.split('_') - f_suffix = '_'.join(pygem_fn_split[1:])[:-3] + pygem_fn_split = pygem_fn.split("_") + f_suffix = "_".join(pygem_fn_split[1:])[:-3] glac_no = pygem_fn_split[0] - glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[0, :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) - # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== + glacier_rgi_table = modelsetup.selectglaciersrgitable( + glac_no=[glac_no] + ).loc[0, :] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_calving']: + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_calving"] + ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False else: # set reset=True to overwrite non-calving directory that may already exist - gdir = single_flowline_glacier_directory_with_calving(glacier_str) + gdir = single_flowline_glacier_directory_with_calving( + glacier_str + ) gdir.is_tidewater = True except Exception as err: print(err) # create OGGM formatted flowline diagnostic dataset from PyGEM simulation - pygem_fl_diag = pygem_to_oggm(os.path.join(pygem_path,pygem_fn),debug=debug) + pygem_fl_diag = pygem_to_oggm( + os.path.join(pygem_path, pygem_fn), debug=debug + ) ### ### OGGM preprocessing steps before redistributing ice thickness form simulation ### # This is to add a new topography to the file (smoothed differently) - workflow.execute_entity_task(distribute_2d.add_smoothed_glacier_topo, gdir) + workflow.execute_entity_task( + distribute_2d.add_smoothed_glacier_topo, gdir + ) # This is to get the bed map at the start of the simulation - workflow.execute_entity_task(tasks.distribute_thickness_per_altitude, gdir) + workflow.execute_entity_task( + tasks.distribute_thickness_per_altitude, gdir + ) # This is to prepare the glacier directory for the interpolation (needs to be done only once) workflow.execute_entity_task(distribute_2d.assign_points_to_band, gdir) ### # distribute simulation to 2d ds = workflow.execute_entity_task( - distribute_2d.distribute_thickness_from_simulation, - gdir, - fl_diag=pygem_fl_diag, - concat_input_filesuffix='_spinup_historical', # concatenate with the historical spinup - output_filesuffix=f'_pygem_{f_suffix}', # filesuffix added to the output filename gridded_simulation.nc, if empty input_filesuffix is used + distribute_2d.distribute_thickness_from_simulation, + gdir, + fl_diag=pygem_fl_diag, + concat_input_filesuffix="_spinup_historical", # concatenate with the historical spinup + output_filesuffix=f"_pygem_{f_suffix}", # filesuffix added to the output filename gridded_simulation.nc, if empty input_filesuffix is used )[0] - print('2D simulated ice thickness created: ', gdir.get_filepath('gridded_simulation',filesuffix=f'_pygem_{f_suffix}')) + print( + "2D simulated ice thickness created: ", + gdir.get_filepath( + "gridded_simulation", filesuffix=f"_pygem_{f_suffix}" + ), + ) if debug: plot_distributed_thickness(ds) @@ -164,11 +211,12 @@ def main(): # set up partial function with debug argument run_with_debug = partial(run, debug=args.debug) # parallel processing - print('Processing with ' + str(ncores) + ' cores...') + print("Processing with " + str(ncores) + " cores...") with multiprocessing.Pool(ncores) as p: p.map(run_with_debug, args.simpath) - print('Total processing time:', time.time()-time_start, 's') - + print("Total processing time:", time.time() - time_start, "s") + + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/postproc/postproc_monthly_mass.py b/pygem/bin/postproc/postproc_monthly_mass.py index ea18f641..7abc1a8a 100644 --- a/pygem/bin/postproc/postproc_monthly_mass.py +++ b/pygem/bin/postproc/postproc_monthly_mass.py @@ -7,28 +7,25 @@ derive monthly glacierwide mass for PyGEM simulation using annual glacier mass and monthly total mass balance """ + # Built-in libraries import argparse import collections -import copy -import inspect +import glob import multiprocessing import os -import glob -import sys import time -import json -# External libraries -import pandas as pd -import pickle + import numpy as np + +# External libraries import xarray as xr + # pygem imports -import pygem import pygem.setup.config as config + # read config pygem_prms = config.read_config() -import pygem.pygem_modelsetup as modelsetup # ----- FUNCTIONS ----- @@ -36,14 +33,31 @@ def getparser(): """ Use argparse to add arguments from the command line """ - parser = argparse.ArgumentParser(description="process monthly glacierwide mass from annual mass and total monthly mass balance") + parser = argparse.ArgumentParser( + description="process monthly glacierwide mass from annual mass and total monthly mass balance" + ) # add arguments - parser.add_argument('-simpath', action='store', type=str, nargs='+', - help='path to PyGEM simulation (can take multiple)') - parser.add_argument('-simdir', action='store', type=str, default=None, - help='directory with glacierwide simulation outputs for which to process monthly mass') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') + parser.add_argument( + "-simpath", + action="store", + type=str, + nargs="+", + help="path to PyGEM simulation (can take multiple)", + ) + parser.add_argument( + "-simdir", + action="store", + type=str, + default=None, + help="directory with glacierwide simulation outputs for which to process monthly mass", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) return parser @@ -74,10 +88,14 @@ def get_monthly_mass(glac_mass_annual, glac_massbaltotal_monthly): """ # get running total monthly mass balance - reshape into subarrays of all values for a given year, then take cumulative sum oshape = glac_massbaltotal_monthly.shape - running_glac_massbaltotal_monthly = np.reshape(glac_massbaltotal_monthly, (-1,12), order='C').cumsum(axis=-1).reshape(oshape) + running_glac_massbaltotal_monthly = ( + np.reshape(glac_massbaltotal_monthly, (-1, 12), order="C") + .cumsum(axis=-1) + .reshape(oshape) + ) # tile annual mass to then superimpose atop running glacier mass balance (trim off final year from annual mass) - glac_mass_monthly = np.repeat(glac_mass_annual[:,:-1], 12, axis=-1) + glac_mass_monthly = np.repeat(glac_mass_annual[:, :-1], 12, axis=-1) # add annual mass values to running glacier mass balance glac_mass_monthly += running_glac_massbaltotal_monthly @@ -93,7 +111,7 @@ def update_xrdataset(input_ds, glac_mass_monthly): ---------- xrdataset : xarray Dataset existing xarray dataset - newdata : ndarray + newdata : ndarray new data array description: str describing new data field @@ -108,25 +126,33 @@ def update_xrdataset(input_ds, glac_mass_monthly): time_values = input_ds.time.values output_coords_dict = collections.OrderedDict() - output_coords_dict['glac_mass_monthly'] = ( - collections.OrderedDict([('glac', glac_values), ('time', time_values)])) + output_coords_dict["glac_mass_monthly"] = collections.OrderedDict( + [("glac", glac_values), ("time", time_values)] + ) # Attributes dictionary output_attrs_dict = {} - output_attrs_dict['glac_mass_monthly'] = { - 'long_name': 'glacier mass', - 'units': 'kg', - 'temporal_resolution': 'monthly', - 'comment': 'monthly glacier mass'} - + output_attrs_dict["glac_mass_monthly"] = { + "long_name": "glacier mass", + "units": "kg", + "temporal_resolution": "monthly", + "comment": "monthly glacier mass", + } # Add variables to empty dataset and merge together count_vn = 0 encoding = {} for vn in output_coords_dict.keys(): - empty_holder = np.zeros([len(output_coords_dict[vn][i]) for i in list(output_coords_dict[vn].keys())]) - output_ds = xr.Dataset({vn: (list(output_coords_dict[vn].keys()), empty_holder)}, - coords=output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(output_coords_dict[vn][i]) + for i in list(output_coords_dict[vn].keys()) + ] + ) + output_ds = xr.Dataset( + {vn: (list(output_coords_dict[vn].keys()), empty_holder)}, + coords=output_coords_dict[vn], + ) count_vn += 1 # Merge datasets of stats into one output if count_vn == 1: @@ -140,14 +166,9 @@ def update_xrdataset(input_ds, glac_mass_monthly): except: pass # Encoding (specify _FillValue, offsets, etc.) - encoding[vn] = {'_FillValue': None, - 'zlib':True, - 'complevel':9 - } + encoding[vn] = {"_FillValue": None, "zlib": True, "complevel": 9} - output_ds_all['glac_mass_monthly'].values = ( - glac_mass_monthly - ) + output_ds_all["glac_mass_monthly"].values = glac_mass_monthly return output_ds_all, encoding @@ -167,27 +188,32 @@ def run(simpath): # calculate monthly mass - pygem glac_massbaltotal_monthly is in units of m3, so convert to mass using density of ice glac_mass_monthly = get_monthly_mass( - statsds.glac_mass_annual.values, - statsds.glac_massbaltotal_monthly.values * pygem_prms['constants']['density_ice'], - ) + statsds.glac_mass_annual.values, + statsds.glac_massbaltotal_monthly.values + * pygem_prms["constants"]["density_ice"], + ) statsds.close() # update dataset to add monthly mass change - output_ds_stats, encoding = update_xrdataset(statsds, glac_mass_monthly) + output_ds_stats, encoding = update_xrdataset( + statsds, glac_mass_monthly + ) # close input ds before write statsds.close() # append to existing stats netcdf - output_ds_stats.to_netcdf(simpath, mode='a', encoding=encoding, engine='netcdf4') + output_ds_stats.to_netcdf( + simpath, mode="a", encoding=encoding, engine="netcdf4" + ) # close datasets output_ds_stats.close() - + except: pass else: - print('Simulation not found: ',simpath) + print("Simulation not found: ", simpath) return @@ -199,7 +225,7 @@ def main(): simpath = None if args.simdir: # get list of sims - simpath = glob.glob(args.simdir+'*.nc') + simpath = glob.glob(args.simdir + "*.nc") else: if args.simpath: simpath = args.simpath @@ -212,11 +238,12 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(args.ncores) + ' cores...') + print("Processing with " + str(args.ncores) + " cores...") with multiprocessing.Pool(args.ncores) as p: - p.map(run,simpath) + p.map(run, simpath) + + print("Total processing time:", time.time() - time_start, "s") + - print('Total processing time:', time.time()-time_start, 's') - if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/preproc/preproc_fetch_mbdata.py b/pygem/bin/preproc/preproc_fetch_mbdata.py index d25cf9ef..cb534b9a 100644 --- a/pygem/bin/preproc/preproc_fetch_mbdata.py +++ b/pygem/bin/preproc/preproc_fetch_mbdata.py @@ -7,27 +7,25 @@ Fetch filled Hugonnet reference mass balance data """ + # Built-in libraries import argparse import os + # External libraries -import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.ticker import MultipleLocator -from scipy.stats import median_abs_deviation # oggm from oggm import utils + # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() -import pygem.pygem_modelsetup as modelsetup -def run(fp='', debug=False, overwrite=False): +def run(fp="", debug=False, overwrite=False): """ pull geodetic mass balance data from OGGM The original 'raw' were acquired and combined from https://doi.org/10.6096/13 (time series/dh__rgi60_pergla_rates) @@ -42,43 +40,58 @@ def run(fp='', debug=False, overwrite=False): """ mbdf = utils.get_geodetic_mb_dataframe() if debug: - print('MB data loaded from OGGM:') + print("MB data loaded from OGGM:") print(mbdf.head()) # pull only 2000-2020 period - mbdf_subset = mbdf[mbdf.period=='2000-01-01_2020-01-01'] + mbdf_subset = mbdf[mbdf.period == "2000-01-01_2020-01-01"] # reset the index mbdf_subset = mbdf_subset.reset_index() # sort by the rgiid column - mbdf_subset = mbdf_subset.sort_values(by='rgiid') + mbdf_subset = mbdf_subset.sort_values(by="rgiid") # rename some keys to work with what other scripts/functions expect - mbdf_subset= mbdf_subset.rename(columns={'dmdtda':'mb_mwea', - 'err_dmdtda':'mb_mwea_err'}) + mbdf_subset = mbdf_subset.rename( + columns={"dmdtda": "mb_mwea", "err_dmdtda": "mb_mwea_err"} + ) - if fp[-4:] != '.csv': - fp += '.csv' + if fp[-4:] != ".csv": + fp += ".csv" if os.path.isfile(fp) and not overwrite: - raise FileExistsError(f'The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}') - + raise FileExistsError( + f"The filled global geodetic mass balance file already exists, pass `-o` to overwrite, or pass a different file name: {fp}" + ) + mbdf_subset.to_csv(fp, index=False) if debug: - print(f'Filled global geodetic mass balance data saved to: {fp}') + print(f"Filled global geodetic mass balance data saved to: {fp}") print(mbdf_subset.head()) def main(): - parser = argparse.ArgumentParser(description="grab filled Hugonnet et al. 2021 geodetic mass balance data from OGGM and converts to a format PyGEM utilizes") + parser = argparse.ArgumentParser( + description="grab filled Hugonnet et al. 2021 geodetic mass balance data from OGGM and converts to a format PyGEM utilizes" + ) # add arguments - parser.add_argument('-fname', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", - help='Reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing geodetic mass balance data') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-fname", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", + help="Reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-o", + "--overwrite", + action="store_true", + help="Flag to overwrite existing geodetic mass balance data", + ) + parser.add_argument( + "-v", "--debug", action="store_true", help="Flag for debugging" + ) args = parser.parse_args() # hugonnet filepath @@ -88,4 +101,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/preproc/preproc_wgms_estimate_kp.py b/pygem/bin/preproc/preproc_wgms_estimate_kp.py index 684258a7..97c5f6fb 100644 --- a/pygem/bin/preproc/preproc_wgms_estimate_kp.py +++ b/pygem/bin/preproc/preproc_wgms_estimate_kp.py @@ -10,19 +10,23 @@ This is somewhat of a legacy script, since it is hardcoded and relies on outdated RGI and WGMS data """ + # Built-in libraries import argparse import os import sys + +import numpy as np + # External libraries import pandas as pd -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.ticker import MultipleLocator from scipy.stats import median_abs_deviation + +import pygem.setup.config as config + # pygem imports from pygem import class_climate -import pygem.setup.config as config + # check for config config.ensure_config() # read the config @@ -30,294 +34,448 @@ import pygem.pygem_modelsetup as modelsetup -def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', wgms_ee_winter_fp='', wgms_ee_winter_fp_subset='', subset_time_value=20000000): +def subset_winter( + wgms_eee_fp="", + wgms_ee_fp="", + wgms_e_fp="", + wgms_id_fp="", + wgms_ee_winter_fp="", + wgms_ee_winter_fp_subset="", + subset_time_value=20000000, +): """ subset winter mass balance data from WGMS """ - # Load data - wgms_e_df = pd.read_csv(wgms_e_fp, encoding='unicode_escape') - wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding='unicode_escape') - wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding='unicode_escape') - wgms_id_df = pd.read_csv(wgms_id_fp, encoding='unicode_escape') - + # Load data + wgms_e_df = pd.read_csv(wgms_e_fp, encoding="unicode_escape") + wgms_ee_df_raw = pd.read_csv(wgms_ee_fp, encoding="unicode_escape") + wgms_eee_df_raw = pd.read_csv(wgms_eee_fp, encoding="unicode_escape") + wgms_id_df = pd.read_csv(wgms_id_fp, encoding="unicode_escape") + # Map dictionary wgms_id_dict = dict(zip(wgms_id_df.WGMS_ID, wgms_id_df.RGI_ID)) - wgms_ee_df_raw['rgiid_raw'] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=['rgiid_raw']) - wgms_eee_df_raw['rgiid_raw'] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) - wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=['rgiid_raw']) - + wgms_ee_df_raw["rgiid_raw"] = wgms_ee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_ee_df_raw = wgms_ee_df_raw.dropna(subset=["rgiid_raw"]) + wgms_eee_df_raw["rgiid_raw"] = wgms_eee_df_raw.WGMS_ID.map(wgms_id_dict) + wgms_eee_df_raw = wgms_eee_df_raw.dropna(subset=["rgiid_raw"]) + # Link RGIv5.0 with RGIv6.0 - rgi60_fp = pygem_prms['root'] + '/RGI/rgi60/00_rgi60_attribs/' - rgi50_fp = pygem_prms['root'] + '/RGI/00_rgi50_attribs/' - + rgi60_fp = pygem_prms["root"] + "/RGI/rgi60/00_rgi60_attribs/" + rgi50_fp = pygem_prms["root"] + "/RGI/00_rgi50_attribs/" + # Process each region - regions_str = ['01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','18'] + regions_str = [ + "01", + "02", + "03", + "04", + "05", + "06", + "07", + "08", + "09", + "10", + "11", + "12", + "13", + "14", + "15", + "16", + "17", + "18", + ] rgi60_df = None rgi50_df = None for reg_str in regions_str: # RGI60 data for i in os.listdir(rgi60_fp): - if i.startswith(reg_str) and i.endswith('.csv'): - rgi60_df_reg = pd.read_csv(rgi60_fp + i, encoding='unicode_escape') + if i.startswith(reg_str) and i.endswith(".csv"): + rgi60_df_reg = pd.read_csv( + rgi60_fp + i, encoding="unicode_escape" + ) # append datasets if rgi60_df is None: rgi60_df = rgi60_df_reg else: rgi60_df = pd.concat([rgi60_df, rgi60_df_reg], axis=0) - + # RGI50 data for i in os.listdir(rgi50_fp): - if i.startswith(reg_str) and i.endswith('.csv'): - rgi50_df_reg = pd.read_csv(rgi50_fp + i, encoding='unicode_escape') + if i.startswith(reg_str) and i.endswith(".csv"): + rgi50_df_reg = pd.read_csv( + rgi50_fp + i, encoding="unicode_escape" + ) # append datasets if rgi50_df is None: rgi50_df = rgi50_df_reg else: rgi50_df = pd.concat([rgi50_df, rgi50_df_reg], axis=0) - + # Merge based on GLIMSID glims_rgi50_dict = dict(zip(rgi50_df.GLIMSId, rgi50_df.RGIId)) - rgi60_df['RGIId_50'] = rgi60_df.GLIMSId.map(glims_rgi50_dict) - rgi60_df_4dict = rgi60_df.dropna(subset=['RGIId_50']) + rgi60_df["RGIId_50"] = rgi60_df.GLIMSId.map(glims_rgi50_dict) + rgi60_df_4dict = rgi60_df.dropna(subset=["RGIId_50"]) rgi50_rgi60_dict = dict(zip(rgi60_df_4dict.RGIId_50, rgi60_df_4dict.RGIId)) rgi60_self_dict = dict(zip(rgi60_df.RGIId, rgi60_df.RGIId)) rgi50_rgi60_dict.update(rgi60_self_dict) - + # Add RGIId for version 6 to WGMS - wgms_ee_df_raw['rgiid'] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) - wgms_eee_df_raw['rgiid'] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) - + wgms_ee_df_raw["rgiid"] = wgms_ee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + wgms_eee_df_raw["rgiid"] = wgms_eee_df_raw.rgiid_raw.map(rgi50_rgi60_dict) + # Drop points without data - wgms_ee_df = wgms_ee_df_raw.dropna(subset=['rgiid']) - wgms_eee_df = wgms_eee_df_raw.dropna(subset=['rgiid']) - + wgms_ee_df = wgms_ee_df_raw.dropna(subset=["rgiid"]) + wgms_eee_df = wgms_eee_df_raw.dropna(subset=["rgiid"]) + # Winter balances only - wgms_ee_df_winter = wgms_ee_df.dropna(subset=['WINTER_BALANCE']) - wgms_ee_df_winter = wgms_ee_df_winter.sort_values('rgiid') + wgms_ee_df_winter = wgms_ee_df.dropna(subset=["WINTER_BALANCE"]) + wgms_ee_df_winter = wgms_ee_df_winter.sort_values("rgiid") wgms_ee_df_winter.reset_index(inplace=True, drop=True) - + # Add the winter time period using the E-MASS-BALANCE-OVERVIEW file wgms_e_cns2add = [] for cn in wgms_e_df.columns: if cn not in wgms_ee_df_winter.columns: wgms_e_cns2add.append(cn) wgms_ee_df_winter[cn] = np.nan - + for nrow in np.arange(wgms_ee_df_winter.shape[0]): - if nrow%500 == 0: - print(nrow, 'of', wgms_ee_df_winter.shape[0]) - name = wgms_ee_df_winter.loc[nrow,'NAME'] - wgmsid = wgms_ee_df_winter.loc[nrow,'WGMS_ID'] - year = wgms_ee_df_winter.loc[nrow,'YEAR'] - + if nrow % 500 == 0: + print(nrow, "of", wgms_ee_df_winter.shape[0]) + name = wgms_ee_df_winter.loc[nrow, "NAME"] + wgmsid = wgms_ee_df_winter.loc[nrow, "WGMS_ID"] + year = wgms_ee_df_winter.loc[nrow, "YEAR"] + try: - e_idx = np.where((wgms_e_df['NAME'] == name) & - (wgms_e_df['WGMS_ID'] == wgmsid) & - (wgms_e_df['Year'] == year))[0][0] + e_idx = np.where( + (wgms_e_df["NAME"] == name) + & (wgms_e_df["WGMS_ID"] == wgmsid) + & (wgms_e_df["Year"] == year) + )[0][0] except: e_idx = None - + if e_idx is not None: - wgms_ee_df_winter.loc[nrow,wgms_e_cns2add] = wgms_e_df.loc[e_idx,wgms_e_cns2add] - + wgms_ee_df_winter.loc[nrow, wgms_e_cns2add] = wgms_e_df.loc[ + e_idx, wgms_e_cns2add + ] + wgms_ee_df_winter.to_csv(wgms_ee_winter_fp, index=False) - + # Export subset of data - wgms_ee_df_winter_subset = wgms_ee_df_winter.loc[wgms_ee_df_winter['BEGIN_PERIOD'] > subset_time_value] - wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna(subset=['END_WINTER']) + wgms_ee_df_winter_subset = wgms_ee_df_winter.loc[ + wgms_ee_df_winter["BEGIN_PERIOD"] > subset_time_value + ] + wgms_ee_df_winter_subset = wgms_ee_df_winter_subset.dropna( + subset=["END_WINTER"] + ) wgms_ee_df_winter_subset.to_csv(wgms_ee_winter_fp_subset, index=False) -def est_kp(wgms_ee_winter_fp_subset='', wgms_ee_winter_fp_kp='', wgms_reg_kp_stats_fp=''): +def est_kp( + wgms_ee_winter_fp_subset="", + wgms_ee_winter_fp_kp="", + wgms_reg_kp_stats_fp="", +): """ This is used to estimate the precipitation factor for the bounds of HH2015_mod """ # Load data - assert os.path.exists(wgms_ee_winter_fp_subset), 'wgms_ee_winter_fn_subset does not exist!' - wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding='unicode_escape') - + assert os.path.exists(wgms_ee_winter_fp_subset), ( + "wgms_ee_winter_fn_subset does not exist!" + ) + wgms_df = pd.read_csv(wgms_ee_winter_fp_subset, encoding="unicode_escape") + # Process dates - wgms_df.loc[:,'BEGIN_PERIOD'] = wgms_df.loc[:,'BEGIN_PERIOD'].values.astype(int).astype(str) - wgms_df['BEGIN_YEAR'] = [int(x[0:4]) for x in wgms_df.loc[:,'BEGIN_PERIOD']] - wgms_df['BEGIN_MONTH'] = [int(x[4:6]) for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df['BEGIN_DAY'] = [int(x[6:]) for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df['BEGIN_YEARMONTH'] = [x[0:6] for x in list(wgms_df.loc[:,'BEGIN_PERIOD'])] - wgms_df.loc[:,'END_WINTER'] = wgms_df.loc[:,'END_WINTER'].values.astype(int).astype(str) - wgms_df['END_YEAR'] = [int(x[0:4]) for x in wgms_df.loc[:,'END_WINTER']] - wgms_df['END_MONTH'] = [int(x[4:6]) for x in list(wgms_df.loc[:,'END_WINTER'])] - wgms_df['END_DAY'] = [int(x[6:]) for x in list(wgms_df.loc[:,'END_WINTER'])] - wgms_df['END_YEARMONTH'] = [x[0:6] for x in list(wgms_df.loc[:,'END_WINTER'])] - + wgms_df.loc[:, "BEGIN_PERIOD"] = ( + wgms_df.loc[:, "BEGIN_PERIOD"].values.astype(int).astype(str) + ) + wgms_df["BEGIN_YEAR"] = [ + int(x[0:4]) for x in wgms_df.loc[:, "BEGIN_PERIOD"] + ] + wgms_df["BEGIN_MONTH"] = [ + int(x[4:6]) for x in list(wgms_df.loc[:, "BEGIN_PERIOD"]) + ] + wgms_df["BEGIN_DAY"] = [ + int(x[6:]) for x in list(wgms_df.loc[:, "BEGIN_PERIOD"]) + ] + wgms_df["BEGIN_YEARMONTH"] = [ + x[0:6] for x in list(wgms_df.loc[:, "BEGIN_PERIOD"]) + ] + wgms_df.loc[:, "END_WINTER"] = ( + wgms_df.loc[:, "END_WINTER"].values.astype(int).astype(str) + ) + wgms_df["END_YEAR"] = [int(x[0:4]) for x in wgms_df.loc[:, "END_WINTER"]] + wgms_df["END_MONTH"] = [ + int(x[4:6]) for x in list(wgms_df.loc[:, "END_WINTER"]) + ] + wgms_df["END_DAY"] = [ + int(x[6:]) for x in list(wgms_df.loc[:, "END_WINTER"]) + ] + wgms_df["END_YEARMONTH"] = [ + x[0:6] for x in list(wgms_df.loc[:, "END_WINTER"]) + ] + # ===== PROCESS UNIQUE GLACIERS ===== - rgiids_unique = list(wgms_df['rgiid'].unique()) - glac_no = [x.split('-')[1] for x in rgiids_unique] - + rgiids_unique = list(wgms_df["rgiid"].unique()) + glac_no = [x.split("-")[1] for x in rgiids_unique] + main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) - + # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=pygem_prms['climate']['ref_startyear'], endyear=pygem_prms['climate']['ref_endyear'], spinupyears=0, - option_wateryear=pygem_prms['climate']['ref_wateryear']) - dates_table_yearmo = [str(dates_table.loc[x,'year']) + str(dates_table.loc[x,'month']).zfill(2) - for x in range(dates_table.shape[0])] - + startyear=pygem_prms["climate"]["ref_startyear"], + endyear=pygem_prms["climate"]["ref_endyear"], + spinupyears=0, + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) + dates_table_yearmo = [ + str(dates_table.loc[x, "year"]) + + str(dates_table.loc[x, "month"]).zfill(2) + for x in range(dates_table.shape[0]) + ] + # ===== LOAD CLIMATE DATA ===== # Climate class - gcm = class_climate.GCM(name=pygem_prms['climate']['ref_gcm_name']) - + gcm = class_climate.GCM(name=pygem_prms["climate"]["ref_gcm_name"]) + # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, - dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table + ) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, - dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) # Lapse rate - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) - + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) + # ===== PROCESS THE OBSERVATIONS ====== - prec_cn = pygem_prms['climate']['ref_gcm_name'] + '_prec' + prec_cn = pygem_prms["climate"]["ref_gcm_name"] + "_prec" wgms_df[prec_cn] = np.nan - wgms_df['kp'] = np.nan - wgms_df['ndays'] = np.nan + wgms_df["kp"] = np.nan + wgms_df["ndays"] = np.nan for glac in range(main_glac_rgi.shape[0]): - print(glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId']) + print( + glac, main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"] + ) # Select subsets of data - glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_rgi_table = main_glac_rgi.loc[ + main_glac_rgi.index.values[glac], : + ] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) rgiid = glacier_rgi_table.RGIId - - wgms_df_single = (wgms_df.loc[wgms_df['rgiid'] == rgiid]).copy() + + wgms_df_single = (wgms_df.loc[wgms_df["rgiid"] == rgiid]).copy() glac_idx = wgms_df_single.index.values wgms_df_single.reset_index(inplace=True, drop=True) - + wgms_df_single[prec_cn] = np.nan for nobs in range(wgms_df_single.shape[0]): - # Only process good data # - dates are provided and real # - spans more than one month # - positive winter balance (since we don't account for melt) - if ((wgms_df_single.loc[nobs,'BEGIN_MONTH'] >= 1 and wgms_df_single.loc[nobs,'BEGIN_MONTH'] <= 12) and - (wgms_df_single.loc[nobs,'BEGIN_DAY'] >= 1 and wgms_df_single.loc[nobs,'BEGIN_DAY'] <= 31) and - (wgms_df_single.loc[nobs,'END_MONTH'] >= 1 and wgms_df_single.loc[nobs,'END_MONTH'] <= 12) and - (wgms_df_single.loc[nobs,'END_DAY'] >= 1 and wgms_df_single.loc[nobs,'END_DAY'] <= 31) and - (wgms_df_single.loc[nobs,'BEGIN_PERIOD'] < wgms_df_single.loc[nobs,'END_WINTER']) and - (wgms_df_single.loc[nobs,'BEGIN_YEARMONTH'] != wgms_df_single.loc[nobs,'END_YEARMONTH']) and - (wgms_df_single.loc[nobs,'WINTER_BALANCE'] > 0) - ): + if ( + ( + wgms_df_single.loc[nobs, "BEGIN_MONTH"] >= 1 + and wgms_df_single.loc[nobs, "BEGIN_MONTH"] <= 12 + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_DAY"] >= 1 + and wgms_df_single.loc[nobs, "BEGIN_DAY"] <= 31 + ) + and ( + wgms_df_single.loc[nobs, "END_MONTH"] >= 1 + and wgms_df_single.loc[nobs, "END_MONTH"] <= 12 + ) + and ( + wgms_df_single.loc[nobs, "END_DAY"] >= 1 + and wgms_df_single.loc[nobs, "END_DAY"] <= 31 + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_PERIOD"] + < wgms_df_single.loc[nobs, "END_WINTER"] + ) + and ( + wgms_df_single.loc[nobs, "BEGIN_YEARMONTH"] + != wgms_df_single.loc[nobs, "END_YEARMONTH"] + ) + and (wgms_df_single.loc[nobs, "WINTER_BALANCE"] > 0) + ): # Begin index - idx_begin = dates_table_yearmo.index(wgms_df_single.loc[nobs,'BEGIN_YEARMONTH']) - idx_end = dates_table_yearmo.index(wgms_df_single.loc[nobs,'END_YEARMONTH']) - + idx_begin = dates_table_yearmo.index( + wgms_df_single.loc[nobs, "BEGIN_YEARMONTH"] + ) + idx_end = dates_table_yearmo.index( + wgms_df_single.loc[nobs, "END_YEARMONTH"] + ) + # Fraction of the months to remove - remove_prec_begin = (gcm_prec[glac,idx_begin] * - wgms_df_single.loc[nobs,'BEGIN_DAY'] / dates_table.loc[idx_begin,'daysinmonth']) - remove_prec_end = (gcm_prec[glac,idx_end] * - (1 - wgms_df_single.loc[nobs,'END_DAY'] / dates_table.loc[idx_end,'daysinmonth'])) - + remove_prec_begin = ( + gcm_prec[glac, idx_begin] + * wgms_df_single.loc[nobs, "BEGIN_DAY"] + / dates_table.loc[idx_begin, "daysinmonth"] + ) + remove_prec_end = gcm_prec[glac, idx_end] * ( + 1 + - wgms_df_single.loc[nobs, "END_DAY"] + / dates_table.loc[idx_end, "daysinmonth"] + ) + # Winter Precipitation - gcm_prec_winter = gcm_prec[glac,idx_begin:idx_end+1].sum() - remove_prec_begin - remove_prec_end - wgms_df_single.loc[nobs,prec_cn] = gcm_prec_winter - + gcm_prec_winter = ( + gcm_prec[glac, idx_begin : idx_end + 1].sum() + - remove_prec_begin + - remove_prec_end + ) + wgms_df_single.loc[nobs, prec_cn] = gcm_prec_winter + # Number of days - ndays = (dates_table.loc[idx_begin:idx_end,'daysinmonth'].sum() - wgms_df_single.loc[nobs,'BEGIN_DAY'] - - (dates_table.loc[idx_end,'daysinmonth'] - wgms_df_single.loc[nobs,'END_DAY'])) - wgms_df_single.loc[nobs,'ndays'] = ndays - + ndays = ( + dates_table.loc[idx_begin:idx_end, "daysinmonth"].sum() + - wgms_df_single.loc[nobs, "BEGIN_DAY"] + - ( + dates_table.loc[idx_end, "daysinmonth"] + - wgms_df_single.loc[nobs, "END_DAY"] + ) + ) + wgms_df_single.loc[nobs, "ndays"] = ndays + # Estimate precipitation factors # - assumes no melt and all snow (hence a convservative/underestimated estimate) - wgms_df_single['kp'] = wgms_df_single['WINTER_BALANCE'] / 1000 / wgms_df_single[prec_cn] - + wgms_df_single["kp"] = ( + wgms_df_single["WINTER_BALANCE"] / 1000 / wgms_df_single[prec_cn] + ) + # Record precipitation, precipitation factors, and number of days in main dataframe - wgms_df.loc[glac_idx,prec_cn] = wgms_df_single[prec_cn].values - wgms_df.loc[glac_idx,'kp'] = wgms_df_single['kp'].values - wgms_df.loc[glac_idx,'ndays'] = wgms_df_single['ndays'].values - + wgms_df.loc[glac_idx, prec_cn] = wgms_df_single[prec_cn].values + wgms_df.loc[glac_idx, "kp"] = wgms_df_single["kp"].values + wgms_df.loc[glac_idx, "ndays"] = wgms_df_single["ndays"].values + # Drop nan values - wgms_df_wkp = wgms_df.dropna(subset=['kp']).copy() + wgms_df_wkp = wgms_df.dropna(subset=["kp"]).copy() wgms_df_wkp.reset_index(inplace=True, drop=True) - wgms_df_wkp.to_csv(wgms_ee_winter_fp_kp, index=False) # Calculate stats for all and each region - wgms_df_wkp['reg'] = [x.split('-')[1].split('.')[0] for x in wgms_df_wkp['rgiid'].values] - reg_unique = list(wgms_df_wkp['reg'].unique()) - + wgms_df_wkp["reg"] = [ + x.split("-")[1].split(".")[0] for x in wgms_df_wkp["rgiid"].values + ] + reg_unique = list(wgms_df_wkp["reg"].unique()) + # Output dataframe - reg_kp_cns = ['region', 'count_obs', 'count_glaciers', 'kp_mean', 'kp_std', 'kp_med', 'kp_nmad', 'kp_min', 'kp_max'] - reg_kp_df = pd.DataFrame(np.zeros((len(reg_unique)+1,len(reg_kp_cns))), columns=reg_kp_cns) - + reg_kp_cns = [ + "region", + "count_obs", + "count_glaciers", + "kp_mean", + "kp_std", + "kp_med", + "kp_nmad", + "kp_min", + "kp_max", + ] + reg_kp_df = pd.DataFrame( + np.zeros((len(reg_unique) + 1, len(reg_kp_cns))), columns=reg_kp_cns + ) + # Only those with at least 1 month of data - wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp['ndays'] >= 30] - + wgms_df_wkp = wgms_df_wkp.loc[wgms_df_wkp["ndays"] >= 30] + # All stats - reg_kp_df.loc[0,'region'] = 'all' - reg_kp_df.loc[0,'count_obs'] = wgms_df_wkp.shape[0] - reg_kp_df.loc[0,'count_glaciers'] = len(wgms_df_wkp['rgiid'].unique()) - reg_kp_df.loc[0,'kp_mean'] = np.mean(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_std'] = np.std(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_med'] = np.median(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_nmad'] = median_abs_deviation(wgms_df_wkp.kp.values, scale='normal') - reg_kp_df.loc[0,'kp_min'] = np.min(wgms_df_wkp.kp.values) - reg_kp_df.loc[0,'kp_max'] = np.max(wgms_df_wkp.kp.values) - + reg_kp_df.loc[0, "region"] = "all" + reg_kp_df.loc[0, "count_obs"] = wgms_df_wkp.shape[0] + reg_kp_df.loc[0, "count_glaciers"] = len(wgms_df_wkp["rgiid"].unique()) + reg_kp_df.loc[0, "kp_mean"] = np.mean(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_std"] = np.std(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_med"] = np.median(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_nmad"] = median_abs_deviation( + wgms_df_wkp.kp.values, scale="normal" + ) + reg_kp_df.loc[0, "kp_min"] = np.min(wgms_df_wkp.kp.values) + reg_kp_df.loc[0, "kp_max"] = np.max(wgms_df_wkp.kp.values) + # Regional stats for nreg, reg in enumerate(reg_unique): - wgms_df_wkp_reg = wgms_df_wkp.loc[wgms_df_wkp['reg'] == reg] - - reg_kp_df.loc[nreg+1,'region'] = reg - reg_kp_df.loc[nreg+1,'count_obs'] = wgms_df_wkp_reg.shape[0] - reg_kp_df.loc[nreg+1,'count_glaciers'] = len(wgms_df_wkp_reg['rgiid'].unique()) - reg_kp_df.loc[nreg+1,'kp_mean'] = np.mean(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_std'] = np.std(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_med'] = np.median(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_nmad'] = median_abs_deviation(wgms_df_wkp_reg.kp.values, scale='normal') - reg_kp_df.loc[nreg+1,'kp_min'] = np.min(wgms_df_wkp_reg.kp.values) - reg_kp_df.loc[nreg+1,'kp_max'] = np.max(wgms_df_wkp_reg.kp.values) - - - print('region', reg) - print(' count:', wgms_df_wkp_reg.shape[0]) - print(' glaciers:', len(wgms_df_wkp_reg['rgiid'].unique())) - print(' mean:', np.mean(wgms_df_wkp_reg.kp.values)) - print(' std :', np.std(wgms_df_wkp_reg.kp.values)) - print(' med :', np.median(wgms_df_wkp_reg.kp.values)) - print(' nmad:', median_abs_deviation(wgms_df_wkp_reg.kp.values, scale='normal')) - print(' min :', np.min(wgms_df_wkp_reg.kp.values)) - print(' max :', np.max(wgms_df_wkp_reg.kp.values)) - + wgms_df_wkp_reg = wgms_df_wkp.loc[wgms_df_wkp["reg"] == reg] + + reg_kp_df.loc[nreg + 1, "region"] = reg + reg_kp_df.loc[nreg + 1, "count_obs"] = wgms_df_wkp_reg.shape[0] + reg_kp_df.loc[nreg + 1, "count_glaciers"] = len( + wgms_df_wkp_reg["rgiid"].unique() + ) + reg_kp_df.loc[nreg + 1, "kp_mean"] = np.mean(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_std"] = np.std(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_med"] = np.median( + wgms_df_wkp_reg.kp.values + ) + reg_kp_df.loc[nreg + 1, "kp_nmad"] = median_abs_deviation( + wgms_df_wkp_reg.kp.values, scale="normal" + ) + reg_kp_df.loc[nreg + 1, "kp_min"] = np.min(wgms_df_wkp_reg.kp.values) + reg_kp_df.loc[nreg + 1, "kp_max"] = np.max(wgms_df_wkp_reg.kp.values) + + print("region", reg) + print(" count:", wgms_df_wkp_reg.shape[0]) + print(" glaciers:", len(wgms_df_wkp_reg["rgiid"].unique())) + print(" mean:", np.mean(wgms_df_wkp_reg.kp.values)) + print(" std :", np.std(wgms_df_wkp_reg.kp.values)) + print(" med :", np.median(wgms_df_wkp_reg.kp.values)) + print( + " nmad:", + median_abs_deviation(wgms_df_wkp_reg.kp.values, scale="normal"), + ) + print(" min :", np.min(wgms_df_wkp_reg.kp.values)) + print(" max :", np.max(wgms_df_wkp_reg.kp.values)) + reg_kp_df.to_csv(wgms_reg_kp_stats_fp, index=False) def main(): - parser = argparse.ArgumentParser(description="estimate precipitation factors from WGMS winter mass balance data") - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing data') + parser = argparse.ArgumentParser( + description="estimate precipitation factors from WGMS winter mass balance data" + ) + parser.add_argument( + "-o", + "--overwrite", + action="store_true", + help="Flag to overwrite existing data", + ) args = parser.parse_args() # ===== WGMS DATA ===== # these are hardcoded for the format downloaded from WGMS for their 2020-08 dataset, would need to be updated for newer data wgms_fp = f"{pygem_prms['root']}/WGMS/" # inputs - wgms_dsn = 'DOI-WGMS-FoG-2020-08/' - wgms_eee_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-EEE-MASS-BALANCE-POINT.csv' - wgms_ee_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-EE-MASS-BALANCE.csv' - wgms_e_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-E-MASS-BALANCE-OVERVIEW.csv' - wgms_id_fp = wgms_fp+wgms_dsn+ 'WGMS-FoG-2020-08-AA-GLACIER_ID_LUT.csv' + wgms_dsn = "DOI-WGMS-FoG-2020-08/" + wgms_eee_fp = ( + wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-EEE-MASS-BALANCE-POINT.csv" + ) + wgms_ee_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-EE-MASS-BALANCE.csv" + wgms_e_fp = ( + wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-E-MASS-BALANCE-OVERVIEW.csv" + ) + wgms_id_fp = wgms_fp + wgms_dsn + "WGMS-FoG-2020-08-AA-GLACIER_ID_LUT.csv" in_fps = [x for x in [wgms_eee_fp, wgms_ee_fp, wgms_e_fp, wgms_id_fp]] # outputs - wgms_ee_winter_fp = wgms_fp+ 'WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv' - wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace('.csv', '-subset.csv') - wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace('.csv', '-subset-kp.csv') - wgms_reg_kp_stats_fp = wgms_fp+ 'WGMS-FoG-2019-12-reg_kp_summary.csv' + wgms_ee_winter_fp = ( + wgms_fp + "WGMS-FoG-2019-12-EE-MASS-BALANCE-winter_processed.csv" + ) + wgms_ee_winter_fp_subset = wgms_ee_winter_fp.replace(".csv", "-subset.csv") + wgms_ee_winter_fp_kp = wgms_ee_winter_fp.replace(".csv", "-subset-kp.csv") + wgms_reg_kp_stats_fp = wgms_fp + "WGMS-FoG-2019-12-reg_kp_summary.csv" out_subset_fps = [wgms_ee_winter_fp, wgms_ee_winter_fp_subset] - output_kp_fps = [wgms_ee_winter_fp_kp,wgms_reg_kp_stats_fp] - + output_kp_fps = [wgms_ee_winter_fp_kp, wgms_reg_kp_stats_fp] + subset_time_value = 20000000 # if not all outputs already exist, subset the input data and create the necessary outputs @@ -325,24 +483,31 @@ def main(): missing = False for fp in in_fps: if not os.path.isfile(fp): - print(f'Missing required WGMS datafile: {fp}') + print(f"Missing required WGMS datafile: {fp}") missing = True if missing: sys.exit(1) - subset_winter(wgms_eee_fp=wgms_eee_fp, - wgms_ee_fp=wgms_ee_fp, - wgms_e_fp=wgms_e_fp, - wgms_id_fp=wgms_id_fp, - wgms_ee_winter_fp=wgms_ee_winter_fp, - wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, - subset_time_value=subset_time_value) - - if not all(os.path.exists(filepath) for filepath in output_kp_fps) or args.overwrite: - est_kp(wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, - wgms_ee_winter_fp_kp=wgms_ee_winter_fp_kp, - wgms_reg_kp_stats_fp=wgms_reg_kp_stats_fp) + subset_winter( + wgms_eee_fp=wgms_eee_fp, + wgms_ee_fp=wgms_ee_fp, + wgms_e_fp=wgms_e_fp, + wgms_id_fp=wgms_id_fp, + wgms_ee_winter_fp=wgms_ee_winter_fp, + wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, + subset_time_value=subset_time_value, + ) + + if ( + not all(os.path.exists(filepath) for filepath in output_kp_fps) + or args.overwrite + ): + est_kp( + wgms_ee_winter_fp_subset=wgms_ee_winter_fp_subset, + wgms_ee_winter_fp_kp=wgms_ee_winter_fp_kp, + wgms_reg_kp_stats_fp=wgms_reg_kp_stats_fp, + ) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/__init__.py b/pygem/bin/run/__init__.py index 58bc9316..e7b24f6e 100755 --- a/pygem/bin/run/__init__.py +++ b/pygem/bin/run/__init__.py @@ -4,4 +4,4 @@ copyright © 2018 David Rounce 0): - - modelprms = {'kp': pygem_prms['sim']['params']['kp'], - 'tbias': pygem_prms['sim']['params']['tbias'], - 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], - 'ddfice': pygem_prms['sim']['params']['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'], - 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], - 'precgrad': pygem_prms['sim']['params']['precgrad']} - - #%% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== + if ( + (fls is not None) + and (gdir.mbdata is not None) + and (glacier_area.sum() > 0) + ): + modelprms = { + "kp": pygem_prms["sim"]["params"]["kp"], + "tbias": pygem_prms["sim"]["params"]["tbias"], + "ddfsnow": pygem_prms["sim"]["params"]["ddfsnow"], + "ddfice": pygem_prms["sim"]["params"]["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"], + "tsnow_threshold": pygem_prms["sim"]["params"][ + "tsnow_threshold" + ], + "precgrad": pygem_prms["sim"]["params"]["precgrad"], + } + + # %% ===== EMULATOR TO SETUP MCMC ANALYSIS AND/OR RUN HH2015 WITH EMULATOR ===== # - precipitation factor, temperature bias, degree-day factor of snow - if args.option_calibration == 'emulator': - tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] - tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] - kp_init = pygem_prms['calib']['emulator_params']['kp_init'] - ddfsnow_init = pygem_prms['calib']['emulator_params']['ddfsnow_init'] - + if args.option_calibration == "emulator": + tbias_step = pygem_prms["calib"]["emulator_params"][ + "tbias_step" + ] + tbias_init = pygem_prms["calib"]["emulator_params"][ + "tbias_init" + ] + kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] + ddfsnow_init = pygem_prms["calib"]["emulator_params"][ + "ddfsnow_init" + ] + # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) - nsims = pygem_prms['calib']['emulator_params']['emulator_sims'] - - # Load sims df - sims_fp = pygem_prms['root'] + '/Output/emulator/sims/' + glacier_str.split('.')[0].zfill(2) + '/' - sims_fn = glacier_str + '-' + str(nsims) + '_emulator_sims.csv' + nsims = pygem_prms["calib"]["emulator_params"]["emulator_sims"] - if not os.path.exists(sims_fp + sims_fn) or pygem_prms['calib']['emulator_params']['overwrite_em_sims']: + # Load sims df + sims_fp = ( + pygem_prms["root"] + + "/Output/emulator/sims/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + sims_fn = glacier_str + "-" + str(nsims) + "_emulator_sims.csv" + + if ( + not os.path.exists(sims_fp + sims_fn) + or pygem_prms["calib"]["emulator_params"][ + "overwrite_em_sims" + ] + ): # ----- Temperature bias bounds (ensure reasonable values) ----- # Tbias lower bound based on some bins having negative climatic mass balance - tbias_maxacc = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) - modelprms['tbias'] = tbias_maxacc - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + tbias_maxacc = ( + -1 + * ( + gdir.historical_climate["temp"] + + gdir.historical_climate["lr"] + * ( + fls[0].surface_h.min() + - gdir.historical_climate["elev"] + ) + ).max() + ) + modelprms["tbias"] = tbias_maxacc + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) while nbinyears_negmbclim < 10 or mb_mwea > mb_obs_mwea: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3), - 'nbinyears_negmbclim:', nbinyears_negmbclim) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + "nbinyears_negmbclim:", + nbinyears_negmbclim, + ) tbias_stepsmall = 0.05 while nbinyears_negmbclim > 10: - modelprms['tbias'] = modelprms['tbias'] - tbias_stepsmall - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) + modelprms["tbias"] = ( + modelprms["tbias"] - tbias_stepsmall + ) + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3), - 'nbinyears_negmbclim:', nbinyears_negmbclim) - # Tbias lower bound - tbias_bndlow = modelprms['tbias'] + tbias_stepsmall - modelprms['tbias'] = tbias_bndlow - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_all = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) - + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + "nbinyears_negmbclim:", + nbinyears_negmbclim, + ) + # Tbias lower bound + tbias_bndlow = modelprms["tbias"] + tbias_stepsmall + modelprms["tbias"] = tbias_bndlow + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_all = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) + # Tbias lower bound & high precipitation factor - modelprms['kp'] = stats.gamma.ppf(0.99, pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1/pygem_prms['calib']['emulator_params']['kp_gamma_beta']) - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["kp"] = stats.gamma.ppf( + 0.99, + pygem_prms["calib"]["emulator_params"][ + "kp_gamma_alpha" + ], + scale=1 + / pygem_prms["calib"]["emulator_params"][ + "kp_gamma_beta" + ], + ) + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - + if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) # Tbias 'mid-point' - modelprms['kp'] = pygem_prms['calib']['emulator_params']['kp_init'] + modelprms["kp"] = pygem_prms["calib"]["emulator_params"][ + "kp_init" + ] ncount_tbias = 0 tbias_bndhigh = 10 tbias_middle = tbias_bndlow + tbias_step - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 50: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 50: + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - tbias_middle = modelprms['tbias'] - tbias_step / 2 + tbias_middle = modelprms["tbias"] - tbias_step / 2 ncount_tbias += 1 if debug: - print(ncount_tbias, - 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) - + print( + ncount_tbias, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) + # Tbias upper bound (run for equal amount of steps above the midpoint) while ncount_tbias > 0: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["tbias"] = modelprms["tbias"] + tbias_step + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - tbias_bndhigh = modelprms['tbias'] + tbias_bndhigh = modelprms["tbias"] ncount_tbias -= 1 if debug: - print(ncount_tbias, - 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) - + print( + ncount_tbias, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) + # ------ RANDOM RUNS ------- # Temperature bias - if pygem_prms['calib']['emulator_params']['tbias_disttype'] == 'uniform': - tbias_random = np.random.uniform(low=tbias_bndlow, high=tbias_bndhigh, - size=nsims) - elif pygem_prms['calib']['emulator_params']['tbias_disttype'] == 'truncnormal': - tbias_zlow = (tbias_bndlow - tbias_middle) / pygem_prms['calib']['emulator_params']['tbias_sigma'] - tbias_zhigh = (tbias_bndhigh - tbias_middle) / pygem_prms['calib']['emulator_params']['tbias_sigma'] - tbias_random = stats.truncnorm.rvs(a=tbias_zlow, b=tbias_zhigh, loc=tbias_middle, - scale=pygem_prms['calib']['emulator_params']['tbias_sigma'], size=nsims) + if ( + pygem_prms["calib"]["emulator_params"][ + "tbias_disttype" + ] + == "uniform" + ): + tbias_random = np.random.uniform( + low=tbias_bndlow, high=tbias_bndhigh, size=nsims + ) + elif ( + pygem_prms["calib"]["emulator_params"][ + "tbias_disttype" + ] + == "truncnormal" + ): + tbias_zlow = ( + tbias_bndlow - tbias_middle + ) / pygem_prms["calib"]["emulator_params"][ + "tbias_sigma" + ] + tbias_zhigh = ( + tbias_bndhigh - tbias_middle + ) / pygem_prms["calib"]["emulator_params"][ + "tbias_sigma" + ] + tbias_random = stats.truncnorm.rvs( + a=tbias_zlow, + b=tbias_zhigh, + loc=tbias_middle, + scale=pygem_prms["calib"]["emulator_params"][ + "tbias_sigma" + ], + size=nsims, + ) if debug: - print('\ntbias random:', tbias_random.mean(), tbias_random.std()) - + print( + "\ntbias random:", + tbias_random.mean(), + tbias_random.std(), + ) + # Precipitation factor - kp_random = stats.gamma.rvs(pygem_prms['calib']['emulator_params']['kp_gamma_alpha'], scale=1/pygem_prms['calib']['emulator_params']['kp_gamma_beta'], - size=nsims) + kp_random = stats.gamma.rvs( + pygem_prms["calib"]["emulator_params"][ + "kp_gamma_alpha" + ], + scale=1 + / pygem_prms["calib"]["emulator_params"][ + "kp_gamma_beta" + ], + size=nsims, + ) if debug: - print('kp random:', kp_random.mean(), kp_random.std()) - + print("kp random:", kp_random.mean(), kp_random.std()) + # Degree-day factor of snow - ddfsnow_zlow = (pygem_prms['calib']['emulator_params']['ddfsnow_bndlow'] - pygem_prms['calib']['emulator_params']['ddfsnow_mu']) / pygem_prms['calib']['emulator_params']['ddfsnow_sigma'] - ddfsnow_zhigh = (pygem_prms['calib']['emulator_params']['ddfsnow_bndhigh'] - pygem_prms['calib']['emulator_params']['ddfsnow_mu']) / pygem_prms['calib']['emulator_params']['ddfsnow_sigma'] - ddfsnow_random = stats.truncnorm.rvs(a=ddfsnow_zlow, b=ddfsnow_zhigh, loc=pygem_prms['calib']['emulator_params']['ddfsnow_mu'], - scale=pygem_prms['calib']['emulator_params']['ddfsnow_sigma'], size=nsims) - if debug: - print('ddfsnow random:', ddfsnow_random.mean(), ddfsnow_random.std(),'\n') - + ddfsnow_zlow = ( + pygem_prms["calib"]["emulator_params"][ + "ddfsnow_bndlow" + ] + - pygem_prms["calib"]["emulator_params"]["ddfsnow_mu"] + ) / pygem_prms["calib"]["emulator_params"]["ddfsnow_sigma"] + ddfsnow_zhigh = ( + pygem_prms["calib"]["emulator_params"][ + "ddfsnow_bndhigh" + ] + - pygem_prms["calib"]["emulator_params"]["ddfsnow_mu"] + ) / pygem_prms["calib"]["emulator_params"]["ddfsnow_sigma"] + ddfsnow_random = stats.truncnorm.rvs( + a=ddfsnow_zlow, + b=ddfsnow_zhigh, + loc=pygem_prms["calib"]["emulator_params"][ + "ddfsnow_mu" + ], + scale=pygem_prms["calib"]["emulator_params"][ + "ddfsnow_sigma" + ], + size=nsims, + ) + if debug: + print( + "ddfsnow random:", + ddfsnow_random.mean(), + ddfsnow_random.std(), + "\n", + ) + # Run through random values for nsim in range(nsims): - modelprms['tbias'] = tbias_random[nsim] - modelprms['kp'] = kp_random[nsim] - modelprms['ddfsnow'] = ddfsnow_random[nsim] - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - nbinyears_negmbclim, mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt_wmb=True) - - - output_single = np.array([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow'], - mb_mwea, nbinyears_negmbclim]) + modelprms["tbias"] = tbias_random[nsim] + modelprms["kp"] = kp_random[nsim] + modelprms["ddfsnow"] = ddfsnow_random[nsim] + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + nbinyears_negmbclim, mb_mwea = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt_wmb=True, + ) + + output_single = np.array( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + mb_mwea, + nbinyears_negmbclim, + ] + ) output_all = np.vstack((output_all, output_single)) - if debug and nsim%500 == 0: - print(nsim, 'tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'ddfsnow:', np.round(modelprms['ddfsnow'],4), 'mb_mwea:', np.round(mb_mwea,3)) - + if debug and nsim % 500 == 0: + print( + nsim, + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "ddfsnow:", + np.round(modelprms["ddfsnow"], 4), + "mb_mwea:", + np.round(mb_mwea, 3), + ) + # ----- Export results ----- - sims_df = pd.DataFrame(output_all, columns=['tbias', 'kp', 'ddfsnow', 'mb_mwea', - 'nbinyrs_negmbclim']) + sims_df = pd.DataFrame( + output_all, + columns=[ + "tbias", + "kp", + "ddfsnow", + "mb_mwea", + "nbinyrs_negmbclim", + ], + ) if os.path.exists(sims_fp) == False: os.makedirs(sims_fp, exist_ok=True) sims_df.to_csv(sims_fp + sims_fn, index=False) - + else: # Load simulations sims_df = pd.read_csv(sims_fp + sims_fn) # ----- EMULATOR: Mass balance ----- - em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' - em_mod_fp = pygem_prms['root'] + '/Output/emulator/models/' + glacier_str.split('.')[0].zfill(2) + '/' - if not os.path.exists(em_mod_fp + em_mod_fn) or pygem_prms['calib']['emulator_params']['overwrite_em_sims']: - mbEmulator = create_emulator(glacier_str, sims_df, y_cn='mb_mwea', debug=debug) + em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fp = ( + pygem_prms["root"] + + "/Output/emulator/models/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + if ( + not os.path.exists(em_mod_fp + em_mod_fn) + or pygem_prms["calib"]["emulator_params"][ + "overwrite_em_sims" + ] + ): + mbEmulator = create_emulator( + glacier_str, sims_df, y_cn="mb_mwea", debug=debug + ) else: - mbEmulator = massbalEmulator.load(em_mod_path = em_mod_fp + em_mod_fn) + mbEmulator = massbalEmulator.load( + em_mod_path=em_mod_fp + em_mod_fn + ) # ===== HH2015 MODIFIED CALIBRATION USING EMULATOR ===== - if pygem_prms['calib']['emulator_params']['opt_hh2015_mod']: - tbias_init = pygem_prms['calib']['emulator_params']['tbias_init'] - tbias_step = pygem_prms['calib']['emulator_params']['tbias_step'] - kp_init = pygem_prms['calib']['emulator_params']['kp_init'] - kp_bndlow = pygem_prms['calib']['emulator_params']['kp_bndlow'] - kp_bndhigh = pygem_prms['calib']['emulator_params']['kp_bndhigh'] - ddfsnow_init = pygem_prms['calib']['emulator_params']['ddfsnow_init'] + if pygem_prms["calib"]["emulator_params"]["opt_hh2015_mod"]: + tbias_init = pygem_prms["calib"]["emulator_params"][ + "tbias_init" + ] + tbias_step = pygem_prms["calib"]["emulator_params"][ + "tbias_step" + ] + kp_init = pygem_prms["calib"]["emulator_params"]["kp_init"] + kp_bndlow = pygem_prms["calib"]["emulator_params"][ + "kp_bndlow" + ] + kp_bndhigh = pygem_prms["calib"]["emulator_params"][ + "kp_bndhigh" + ] + ddfsnow_init = pygem_prms["calib"]["emulator_params"][ + "ddfsnow_init" + ] # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- - def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid, - debug=False): - """ Update bounds for various parameters for the single_param_optimizer """ + def update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=False, + ): + """Update bounds for various parameters for the single_param_optimizer""" # If mass balance less than observation, reduce tbias - if prm2opt == 'kp': + if prm2opt == "kp": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid - elif prm2opt == 'ddfsnow': + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) + elif prm2opt == "ddfsnow": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high - elif prm2opt == 'tbias': + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) + elif prm2opt == "tbias": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high - + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) + prm_mid_new = (prm_bndlow_new + prm_bndhigh_new) / 2 modelprms[prm2opt] = prm_mid_new - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid_new = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid_new = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) + if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow_new,2), - 'mb_mwea_low:', np.round(mb_mwea_low_new,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh_new,2), - 'mb_mwea_high:', np.round(mb_mwea_high_new,2)) - print(prm2opt + '_mid:', np.round(prm_mid_new,2), - 'mb_mwea_mid:', np.round(mb_mwea_mid_new,3)) - - return (prm_bndlow_new, prm_bndhigh_new, prm_mid_new, - mb_mwea_low_new, mb_mwea_high_new, mb_mwea_mid_new) - - - def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, - kp_bnds=None, tbias_bnds=None, ddfsnow_bnds=None, - mb_mwea_threshold=0.005, debug=False): - """ Single parameter optimizer based on a mid-point approach - + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow_new, 2), + "mb_mwea_low:", + np.round(mb_mwea_low_new, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh_new, 2), + "mb_mwea_high:", + np.round(mb_mwea_high_new, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid_new, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid_new, 3), + ) + + return ( + prm_bndlow_new, + prm_bndhigh_new, + prm_mid_new, + mb_mwea_low_new, + mb_mwea_high_new, + mb_mwea_mid_new, + ) + + def single_param_optimizer( + modelprms_subset, + mb_obs_mwea, + prm2opt=None, + kp_bnds=None, + tbias_bnds=None, + ddfsnow_bnds=None, + mb_mwea_threshold=0.005, + debug=False, + ): + """Single parameter optimizer based on a mid-point approach + Computationally more robust and sometimes faster than scipy minimize """ - assert prm2opt is not None, 'For single_param_optimizer you must specify parameter to optimize' - - if prm2opt == 'kp': + assert prm2opt is not None, ( + "For single_param_optimizer you must specify parameter to optimize" + ) + + if prm2opt == "kp": prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms['tbias'] = modelprms_subset['tbias'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] - elif prm2opt == 'ddfsnow': + modelprms["tbias"] = modelprms_subset["tbias"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + elif prm2opt == "ddfsnow": prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['tbias'] = modelprms_subset['tbias'] - elif prm2opt == 'tbias': + modelprms["kp"] = modelprms_subset["kp"] + modelprms["tbias"] = modelprms_subset["tbias"] + elif prm2opt == "tbias": prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] - + modelprms["kp"] = modelprms_subset["kp"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_low = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_low = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) # Upper bound modelprms[prm2opt] = prm_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_high = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_high = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) # Middle bound prm_mid = (prm_bndlow + prm_bndhigh) / 2 modelprms[prm2opt] = prm_mid - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) + if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow,2), 'mb_mwea_low:', np.round(mb_mwea_low,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh,2), 'mb_mwea_high:', np.round(mb_mwea_high,2)) - print(prm2opt + '_mid:', np.round(prm_mid,2), 'mb_mwea_mid:', np.round(mb_mwea_mid,3)) - + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow, 2), + "mb_mwea_low:", + np.round(mb_mwea_low, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh, 2), + "mb_mwea_high:", + np.round(mb_mwea_high, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid, 3), + ) + # Optimize the model parameter - if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: + if ( + np.absolute(mb_mwea_low - mb_obs_mwea) + <= mb_mwea_threshold + ): modelprms[prm2opt] = prm_bndlow mb_mwea_mid = mb_mwea_low - elif np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: + elif ( + np.absolute(mb_mwea_low - mb_obs_mwea) + <= mb_mwea_threshold + ): modelprms[prm2opt] = prm_bndhigh mb_mwea_mid = mb_mwea_high else: ncount = 0 - while (np.absolute(mb_mwea_mid - mb_obs_mwea) > mb_mwea_threshold and - np.absolute(mb_mwea_low - mb_mwea_high) > mb_mwea_threshold): + while ( + np.absolute(mb_mwea_mid - mb_obs_mwea) + > mb_mwea_threshold + and np.absolute(mb_mwea_low - mb_mwea_high) + > mb_mwea_threshold + ): if debug: - print('\n ncount:', ncount) - (prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid) = ( - update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, - mb_mwea_low, mb_mwea_high, mb_mwea_mid, debug=debug)) + print("\n ncount:", ncount) + ( + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + ) = update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=debug, + ) ncount += 1 - - return modelprms, mb_mwea_mid + return modelprms, mb_mwea_mid # ===== SET THINGS UP ====== if debug: - sims_df['mb_em'] = np.nan + sims_df["mb_em"] = np.nan for nidx in sims_df.index.values: - modelprms['tbias'] = sims_df.loc[nidx,'tbias'] - modelprms['kp'] = sims_df.loc[nidx,'kp'] - modelprms['ddfsnow'] = sims_df.loc[nidx,'ddfsnow'] - sims_df.loc[nidx,'mb_em'] = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - sims_df['mb_em_dif'] = sims_df['mb_em'] - sims_df['mb_mwea'] - + modelprms["tbias"] = sims_df.loc[nidx, "tbias"] + modelprms["kp"] = sims_df.loc[nidx, "kp"] + modelprms["ddfsnow"] = sims_df.loc[nidx, "ddfsnow"] + sims_df.loc[nidx, "mb_em"] = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) + sims_df["mb_em_dif"] = ( + sims_df["mb_em"] - sims_df["mb_mwea"] + ) + # ----- TEMPERATURE BIAS BOUNDS ----- # Selects from emulator sims dataframe - sims_df_subset = sims_df.loc[sims_df['kp']==1, :] - tbias_bndhigh = float(sims_df_subset['tbias'].max()) - tbias_bndlow = float(sims_df_subset['tbias'].min()) - + sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] + tbias_bndhigh = float(sims_df_subset["tbias"].max()) + tbias_bndlow = float(sims_df_subset["tbias"].min()) + # Adjust tbias_init based on bounds if tbias_init > tbias_bndhigh: tbias_init = tbias_bndhigh elif tbias_init < tbias_bndlow: tbias_init = tbias_bndlow - + # ----- Mass balance bounds ----- # Upper bound - modelprms['kp'] = kp_bndhigh - modelprms['tbias'] = tbias_bndlow - modelprms['ddfsnow'] = ddfsnow_init - mb_mwea_bndhigh = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndhigh + modelprms["tbias"] = tbias_bndlow + modelprms["ddfsnow"] = ddfsnow_init + mb_mwea_bndhigh = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) # Lower bound - modelprms['kp'] = kp_bndlow - modelprms['tbias'] = tbias_bndhigh - modelprms['ddfsnow'] = ddfsnow_init - mb_mwea_bndlow = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + modelprms["tbias"] = tbias_bndhigh + modelprms["ddfsnow"] = ddfsnow_init + mb_mwea_bndlow = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('mb_mwea_max:', np.round(mb_mwea_bndhigh,2), - 'mb_mwea_min:', np.round(mb_mwea_bndlow,2)) + print( + "mb_mwea_max:", + np.round(mb_mwea_bndhigh, 2), + "mb_mwea_min:", + np.round(mb_mwea_bndlow, 2), + ) if mb_obs_mwea > mb_mwea_bndhigh: continue_param_search = False tbias_opt = tbias_bndlow - kp_opt= kp_bndhigh - troubleshoot_fp = (pygem_prms['root'] + '/Output/errors/' + - args.option_calibration + '/' + - glacier_str.split('.')[0].zfill(2) + '/') + kp_opt = kp_bndhigh + troubleshoot_fp = ( + pygem_prms["root"] + + "/Output/errors/" + + args.option_calibration + + "/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(troubleshoot_fp): os.makedirs(troubleshoot_fp, exist_ok=True) - txt_fn_extrapfail = glacier_str + "-mbs_obs_outside_bnds.txt" - with open(troubleshoot_fp + txt_fn_extrapfail, "w") as text_file: - text_file.write(glacier_str + ' observed mass balance exceeds max accumulation ' + - 'with value of ' + str(np.round(mb_obs_mwea,2)) + ' mwea') - + txt_fn_extrapfail = ( + glacier_str + "-mbs_obs_outside_bnds.txt" + ) + with open( + troubleshoot_fp + txt_fn_extrapfail, "w" + ) as text_file: + text_file.write( + glacier_str + + " observed mass balance exceeds max accumulation " + + "with value of " + + str(np.round(mb_obs_mwea, 2)) + + " mwea" + ) + elif mb_obs_mwea < mb_mwea_bndlow: continue_param_search = False tbias_opt = tbias_bndhigh - kp_opt= kp_bndlow - troubleshoot_fp = (pygem_prms['root'] + '/Output/errors/' + - args.option_calibration + '/' + - glacier_str.split('.')[0].zfill(2) + '/') + kp_opt = kp_bndlow + troubleshoot_fp = ( + pygem_prms["root"] + + "/Output/errors/" + + args.option_calibration + + "/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(troubleshoot_fp): os.makedirs(troubleshoot_fp, exist_ok=True) - txt_fn_extrapfail = glacier_str + "-mbs_obs_outside_bnds.txt" - with open(troubleshoot_fp + txt_fn_extrapfail, "w") as text_file: - text_file.write(glacier_str + ' observed mass balance below max loss ' + - 'with value of ' + str(np.round(mb_obs_mwea,2)) + ' mwea') + txt_fn_extrapfail = ( + glacier_str + "-mbs_obs_outside_bnds.txt" + ) + with open( + troubleshoot_fp + txt_fn_extrapfail, "w" + ) as text_file: + text_file.write( + glacier_str + + " observed mass balance below max loss " + + "with value of " + + str(np.round(mb_obs_mwea, 2)) + + " mwea" + ) else: continue_param_search = True - + # ===== ADJUST LOWER AND UPPER BOUNDS TO SET UP OPTIMIZATION ====== # Initialize model parameters - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + test_count = 0 test_count_acc = 0 - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if mb_mwea > mb_obs_mwea: if debug: - print('increase tbias, decrease kp') + print("increase tbias, decrease kp") kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms['kp'] = kp_bndlow - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) while mb_mwea > mb_obs_mwea and test_count < 100: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] + tbias_step + modelprms["tbias"] = ( + modelprms["tbias"] + tbias_step + ) # Update bounds - tbias_bndhigh_opt = modelprms['tbias'] - tbias_bndlow_opt = modelprms['tbias'] - tbias_step + tbias_bndhigh_opt = modelprms["tbias"] + tbias_bndlow_opt = ( + modelprms["tbias"] - tbias_step + ) # Compute mass balance - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 else: if debug: - print('decrease tbias, increase kp') + print("decrease tbias, increase kp") kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms['kp'] = kp_bndhigh - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndhigh + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) while mb_obs_mwea > mb_mwea and test_count < 100: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] - tbias_step + modelprms["tbias"] = ( + modelprms["tbias"] - tbias_step + ) # If temperature bias is at lower limit, then increase precipitation factor - if modelprms['tbias'] <= tbias_bndlow: - modelprms['tbias'] = tbias_bndlow + if modelprms["tbias"] <= tbias_bndlow: + modelprms["tbias"] = tbias_bndlow if test_count_acc > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms['kp'] = kp_bndhigh + modelprms["kp"] = kp_bndhigh test_count_acc += 1 # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms['tbias'] - tbias_bndhigh_opt = modelprms['tbias'] + tbias_step + tbias_bndlow_opt = modelprms["tbias"] + tbias_bndhigh_opt = ( + modelprms["tbias"] + tbias_step + ) # Compute mass balance - mb_mwea = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + mb_mwea = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 - # ===== ROUND 1: PRECIPITATION FACTOR ====== + # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print('Round 1:') - print(glacier_str + ' kp: ' + str(np.round(modelprms['kp'],2)) + - ' ddfsnow: ' + str(np.round(modelprms['ddfsnow'],4)) + - ' tbias: ' + str(np.round(modelprms['tbias'],2))) - + print("Round 1:") + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) + # Reset parameters - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + # Lower bound - modelprms['kp'] = kp_bndlow - mb_mwea_kp_low = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + modelprms["kp"] = kp_bndlow + mb_mwea_kp_low = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) # Upper bound - modelprms['kp'] = kp_bndhigh - mb_mwea_kp_high = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) - + modelprms["kp"] = kp_bndhigh + mb_mwea_kp_high = mbEmulator.eval( + [ + modelprms["tbias"], + modelprms["kp"], + modelprms["ddfsnow"], + ] + ) + # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: kp_opt = kp_bndlow @@ -1000,145 +1733,250 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_kp_high else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_init, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_init, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } kp_bnds = (kp_bndlow, kp_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='kp', kp_bnds=kp_bnds, debug=debug) - kp_opt = modelprms_opt['kp'] + modelprms_subset, + mb_obs_mwea, + prm2opt="kp", + kp_bnds=kp_bnds, + debug=debug, + ) + kp_opt = modelprms_opt["kp"] continue_param_search = False - + # Update parameter values - modelprms['kp'] = kp_opt + modelprms["kp"] = kp_opt if debug: - print(' kp:', np.round(kp_opt,2), 'mb_mwea:', np.round(mb_mwea,3), - 'obs_mwea:', np.round(mb_obs_mwea,3)) + print( + " kp:", + np.round(kp_opt, 2), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + ) # ===== ROUND 2: TEMPERATURE BIAS ====== if continue_param_search: if debug: - print('Round 2:') + print("Round 2:") # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, 'ddfsnow': ddfsnow_init, - 'tbias': np.mean([tbias_bndlow_opt, tbias_bndhigh_opt])} - tbias_bnds = (tbias_bndlow_opt, tbias_bndhigh_opt) + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_init, + "tbias": np.mean( + [tbias_bndlow_opt, tbias_bndhigh_opt] + ), + } + tbias_bnds = (tbias_bndlow_opt, tbias_bndhigh_opt) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug) - + modelprms_subset, + mb_obs_mwea, + prm2opt="tbias", + tbias_bnds=tbias_bnds, + debug=debug, + ) + # Update parameter values - tbias_opt = modelprms_opt['tbias'] - modelprms['tbias'] = tbias_opt + tbias_opt = modelprms_opt["tbias"] + modelprms["tbias"] = tbias_opt if debug: - print(' tbias:', np.round(tbias_opt,3), 'mb_mwea:', np.round(mb_mwea,3), - 'obs_mwea:', np.round(mb_obs_mwea,3)) - + print( + " tbias:", + np.round(tbias_opt, 3), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + ) + else: - tbias_opt = modelprms['tbias'] - - + tbias_opt = modelprms["tbias"] + if debug: - print('\n\ntbias:', np.round(tbias_opt,2), 'kp:', np.round(kp_opt,2), - 'mb_mwea:', np.round(mb_mwea,3), 'obs_mwea:', np.round(mb_obs_mwea,3), '\n\n') - + print( + "\n\ntbias:", + np.round(tbias_opt, 2), + "kp:", + np.round(kp_opt, 2), + "mb_mwea:", + np.round(mb_mwea, 3), + "obs_mwea:", + np.round(mb_obs_mwea, 3), + "\n\n", + ) + modelparams_opt = modelprms - modelparams_opt['kp'] = kp_opt - modelparams_opt['tbias'] = tbias_opt - + modelparams_opt["kp"] = kp_opt + modelparams_opt["tbias"] = tbias_opt + # Export model parameters modelprms = modelparams_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [float(mb_mwea)] - modelprms['mb_obs_mwea'] = [float(mb_obs_mwea)] - modelprms['mb_obs_mwea_err'] = [float(mb_obs_mwea_err)] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [float(mb_mwea)] + modelprms["mb_obs_mwea"] = [float(mb_obs_mwea)] + modelprms["mb_obs_mwea_err"] = [float(mb_obs_mwea_err)] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) - - #%% ===== MCMC CALIBRATION ====== + + # %% ===== MCMC CALIBRATION ====== # use MCMC method to determine posterior probability distributions of the three parameters tbias, # ddfsnow and kp. Then create an ensemble of parameter sets evenly sampled from these # distributions, and output these sets of parameters and their corresponding mass balances to be # used in the simulations. - elif args.option_calibration == 'MCMC': - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: + elif args.option_calibration == "MCMC": + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: # load emulator - em_mod_fn = glacier_str + '-emulator-mb_mwea.pth' - em_mod_fp = pygem_prms['root'] + '/Output/emulator/models/' + glacier_str.split('.')[0].zfill(2) + '/' - assert os.path.exists(em_mod_fp + em_mod_fn), f'emulator output does not exist : {em_mod_fp + em_mod_fn}' - mbEmulator = massbalEmulator.load(em_mod_path = em_mod_fp + em_mod_fn) - outpath_sfix = '' # output file path suffix if using emulator + em_mod_fn = glacier_str + "-emulator-mb_mwea.pth" + em_mod_fp = ( + pygem_prms["root"] + + "/Output/emulator/models/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + assert os.path.exists(em_mod_fp + em_mod_fn), ( + f"emulator output does not exist : {em_mod_fp + em_mod_fn}" + ) + mbEmulator = massbalEmulator.load( + em_mod_path=em_mod_fp + em_mod_fn + ) + outpath_sfix = ( + "" # output file path suffix if using emulator + ) else: - outpath_sfix = '-fullsim' # output file path suffix if not using emulator + outpath_sfix = "-fullsim" # output file path suffix if not using emulator - # --------------------------------- - # ----- FUNCTION DECLARATIONS ----- - # --------------------------------- + # --------------------------------- + # ----- FUNCTION DECLARATIONS ----- + # --------------------------------- # Rough estimate of minimum elevation mass balance function def calc_mb_total_minelev(modelprms): - """ Approximate estimate of the mass balance at minimum elevation """ + """Approximate estimate of the mass balance at minimum elevation""" fl = fls[0] min_elev = fl.surface_h.min() - glacier_gcm_temp = gdir.historical_climate['temp'] - glacier_gcm_prec = gdir.historical_climate['prec'] - glacier_gcm_lr = gdir.historical_climate['lr'] - glacier_gcm_elev = gdir.historical_climate['elev'] + glacier_gcm_temp = gdir.historical_climate["temp"] + glacier_gcm_prec = gdir.historical_climate["prec"] + glacier_gcm_lr = gdir.historical_climate["lr"] + glacier_gcm_elev = gdir.historical_climate["elev"] # Temperature using gcm and glacier lapse rates # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange - T_minelev = (glacier_gcm_temp + glacier_gcm_lr * - (glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']] - glacier_gcm_elev) + - glacier_gcm_lr * - (min_elev - glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]) + - modelprms['tbias']) + T_minelev = ( + glacier_gcm_temp + + glacier_gcm_lr + * ( + glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + - glacier_gcm_elev + ) + + glacier_gcm_lr + * ( + min_elev + - glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + ) + + modelprms["tbias"] + ) # Precipitation using precipitation factor and precipitation gradient # P_bin = P_gcm * prec_factor * (1 + prec_grad * (z_bin - z_ref)) - P_minelev = (glacier_gcm_prec * modelprms['kp'] * (1 + modelprms['precgrad'] * (min_elev - - glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]))) + P_minelev = ( + glacier_gcm_prec + * modelprms["kp"] + * ( + 1 + + modelprms["precgrad"] + * ( + min_elev + - glacier_rgi_table.loc[ + pygem_prms["mb"][ + "option_elev_ref_downscale" + ] + ] + ) + ) + ) # Accumulation using tsnow_threshold Acc_minelev = np.zeros(P_minelev.shape) - Acc_minelev[T_minelev <= modelprms['tsnow_threshold']] = ( - P_minelev[T_minelev <= modelprms['tsnow_threshold']]) + Acc_minelev[T_minelev <= modelprms["tsnow_threshold"]] = ( + P_minelev[T_minelev <= modelprms["tsnow_threshold"]] + ) # Melt # energy available for melt [degC day] - melt_energy_available = T_minelev * dates_table['daysinmonth'].values + melt_energy_available = ( + T_minelev * dates_table["daysinmonth"].values + ) melt_energy_available[melt_energy_available < 0] = 0 # assume all snow melt because anything more would melt underlying ice in lowermost bin # SNOW MELT [m w.e.] - Melt_minelev = modelprms['ddfsnow'] * melt_energy_available + Melt_minelev = modelprms["ddfsnow"] * melt_energy_available # Total mass balance over entire period at minimum elvation mb_total_minelev = (Acc_minelev - Melt_minelev).sum() - + return mb_total_minelev def get_priors(priors): - # define distribution based on priors + # define distribution based on priors dists = [] - for param in ['tbias','kp','ddfsnow']: - if priors[param]['type'] == 'normal': - dist = stats.norm(loc=priors[param]['mu'], scale=priors[param]['sigma']) - elif priors[param]['type'] == 'uniform': - dist = stats.uniform(low=priors[param]['low'], high=priors[param]['high']) - elif priors[param]['type'] == 'gamma': - dist = stats.gamma(a=priors[param]['alpha'], scale=1/priors[param]['beta']) - elif priors[param]['type'] == 'truncnormal': - dist = stats.truncnorm(a=(priors[param]['low']-priors[param]['mu'])/priors[param]['sigma'], - b=(priors[param]['high']-priors[param]['mu'])/priors[param]['sigma'], - loc=priors[param]['mu'], scale=priors[param]['sigma']) + for param in ["tbias", "kp", "ddfsnow"]: + if priors[param]["type"] == "normal": + dist = stats.norm( + loc=priors[param]["mu"], + scale=priors[param]["sigma"], + ) + elif priors[param]["type"] == "uniform": + dist = stats.uniform( + low=priors[param]["low"], + high=priors[param]["high"], + ) + elif priors[param]["type"] == "gamma": + dist = stats.gamma( + a=priors[param]["alpha"], + scale=1 / priors[param]["beta"], + ) + elif priors[param]["type"] == "truncnormal": + dist = stats.truncnorm( + a=(priors[param]["low"] - priors[param]["mu"]) + / priors[param]["sigma"], + b=(priors[param]["high"] - priors[param]["mu"]) + / priors[param]["sigma"], + loc=priors[param]["mu"], + scale=priors[param]["sigma"], + ) dists.append(dist) return dists - def get_initials(dists, threshold=.01): + def get_initials(dists, threshold=0.01): # sample priors - ensure that probability of each sample > .01 initials = None while initials is None: @@ -1151,74 +1989,144 @@ def get_initials(dists, threshold=.01): if all(p > threshold for p in ps): initials = xs return initials - + def mb_max(*args, **kwargs): - """ Model parameters cannot completely melt the glacier (psuedo-likelihood fxn) """ - if kwargs['massbal'] < mb_max_loss: + """Model parameters cannot completely melt the glacier (psuedo-likelihood fxn)""" + if kwargs["massbal"] < mb_max_loss: return -np.inf else: return 0 def must_melt(kp, tbias, ddfsnow, **kwargs): - """ Likelihood function for mass balance [mwea] based on model parametersr (psuedo-likelihood fxn) """ + """Likelihood function for mass balance [mwea] based on model parametersr (psuedo-likelihood fxn)""" modelprms_copy = modelprms.copy() - modelprms_copy['tbias'] = float(tbias) - modelprms_copy['kp'] = float(kp) - modelprms_copy['ddfsnow'] = float(ddfsnow) - modelprms_copy['ddfice'] = modelprms_copy['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms_copy["tbias"] = float(tbias) + modelprms_copy["kp"] = float(kp) + modelprms_copy["ddfsnow"] = float(ddfsnow) + modelprms_copy["ddfice"] = ( + modelprms_copy["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) mb_total_minelev = calc_mb_total_minelev(modelprms_copy) if mb_total_minelev < 0: return 0 else: return -np.inf - # --------------------------------- + + # --------------------------------- # --------------------------------- # ----- MASS BALANCE MAX LOSS ----- # --------------------------------- # Maximum mass loss [mwea] (based on consensus ice thickness estimate) # consensus_mass has units of kg - if os.path.exists(gdir.get_filepath('consensus_mass')): - with open(gdir.get_filepath('consensus_mass'), 'rb') as f: + if os.path.exists(gdir.get_filepath("consensus_mass")): + with open(gdir.get_filepath("consensus_mass"), "rb") as f: consensus_mass = pickle.load(f) else: # Mean global ice thickness from Farinotti et al. (2019) used for missing consensus glaciers ice_thickness_constant = 224 - consensus_mass = glacier_rgi_table.Area * 1e6 * ice_thickness_constant * pygem_prms['constants']['density_ice'] - - mb_max_loss = (-1 * consensus_mass / pygem_prms['constants']['density_water'] / gdir.rgi_area_m2 / - (gdir.dates_table.shape[0] / 12)) + consensus_mass = ( + glacier_rgi_table.Area + * 1e6 + * ice_thickness_constant + * pygem_prms["constants"]["density_ice"] + ) + + mb_max_loss = ( + -1 + * consensus_mass + / pygem_prms["constants"]["density_water"] + / gdir.rgi_area_m2 + / (gdir.dates_table.shape[0] / 12) + ) # --------------------------------- # ------------------ # ----- PRIORS ----- # ------------------ # Prior distributions (specified or informed by regions) - if pygem_prms['calib']['priors_reg_fn'] is not None: + if pygem_prms["calib"]["priors_reg_fn"] is not None: # Load priors - priors_df = pd.read_csv(pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn']) - priors_idx = np.where((priors_df.O1Region == glacier_rgi_table['O1Region']) & - (priors_df.O2Region == glacier_rgi_table['O2Region']))[0][0] + priors_df = pd.read_csv( + pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"] + ) + priors_idx = np.where( + (priors_df.O1Region == glacier_rgi_table["O1Region"]) + & (priors_df.O2Region == glacier_rgi_table["O2Region"]) + )[0][0] # Precipitation factor priors - kp_gamma_alpha = float(priors_df.loc[priors_idx, 'kp_alpha']) - kp_gamma_beta = float(priors_df.loc[priors_idx, 'kp_beta']) + kp_gamma_alpha = float( + priors_df.loc[priors_idx, "kp_alpha"] + ) + kp_gamma_beta = float(priors_df.loc[priors_idx, "kp_beta"]) # Temperature bias priors - tbias_mu = float(priors_df.loc[priors_idx, 'tbias_mean']) - tbias_sigma = float(priors_df.loc[priors_idx, 'tbias_std']) + tbias_mu = float(priors_df.loc[priors_idx, "tbias_mean"]) + tbias_sigma = float(priors_df.loc[priors_idx, "tbias_std"]) else: # Precipitation factor priors - kp_gamma_alpha = pygem_prms['calib']['MCMC_params']['kp_gamma_alpha'] - kp_gamma_beta = pygem_prms['calib']['MCMC_params']['kp_gamma_beta'] + kp_gamma_alpha = pygem_prms["calib"]["MCMC_params"][ + "kp_gamma_alpha" + ] + kp_gamma_beta = pygem_prms["calib"]["MCMC_params"][ + "kp_gamma_beta" + ] # Temperature bias priors - tbias_mu = pygem_prms['calib']['MCMC_params']['tbias_mu'] - tbias_sigma = pygem_prms['calib']['MCMC_params']['tbias_sigma'] + tbias_mu = pygem_prms["calib"]["MCMC_params"]["tbias_mu"] + tbias_sigma = pygem_prms["calib"]["MCMC_params"][ + "tbias_sigma" + ] # put all priors together into a dictionary - priors = { - 'tbias': {'type':pygem_prms['calib']['MCMC_params']['tbias_disttype'], 'mu':float(tbias_mu) , 'sigma':float(tbias_sigma), 'low':safe_float(getattr(pygem_prms,'tbias_bndlow',None)), 'high':safe_float(getattr(pygem_prms,'tbias_bndhigh',None))}, - 'kp': {'type':pygem_prms['calib']['MCMC_params']['kp_disttype'], 'alpha':float(kp_gamma_alpha), 'beta':float(kp_gamma_beta), 'low':safe_float(getattr(pygem_prms,'kp_bndlow',None)), 'high':safe_float(getattr(pygem_prms,'kp_bndhigh',None))}, - 'ddfsnow': {'type':pygem_prms['calib']['MCMC_params']['ddfsnow_disttype'], 'mu':pygem_prms['calib']['MCMC_params']['ddfsnow_mu'], 'sigma':pygem_prms['calib']['MCMC_params']['ddfsnow_sigma'] ,'low':float(pygem_prms['calib']['MCMC_params']['ddfsnow_bndlow']), 'high':float(pygem_prms['calib']['MCMC_params']['ddfsnow_bndhigh'])}, - } + priors = { + "tbias": { + "type": pygem_prms["calib"]["MCMC_params"][ + "tbias_disttype" + ], + "mu": float(tbias_mu), + "sigma": float(tbias_sigma), + "low": safe_float( + getattr(pygem_prms, "tbias_bndlow", None) + ), + "high": safe_float( + getattr(pygem_prms, "tbias_bndhigh", None) + ), + }, + "kp": { + "type": pygem_prms["calib"]["MCMC_params"][ + "kp_disttype" + ], + "alpha": float(kp_gamma_alpha), + "beta": float(kp_gamma_beta), + "low": safe_float( + getattr(pygem_prms, "kp_bndlow", None) + ), + "high": safe_float( + getattr(pygem_prms, "kp_bndhigh", None) + ), + }, + "ddfsnow": { + "type": pygem_prms["calib"]["MCMC_params"][ + "ddfsnow_disttype" + ], + "mu": pygem_prms["calib"]["MCMC_params"]["ddfsnow_mu"], + "sigma": pygem_prms["calib"]["MCMC_params"][ + "ddfsnow_sigma" + ], + "low": float( + pygem_prms["calib"]["MCMC_params"][ + "ddfsnow_bndlow" + ] + ), + "high": float( + pygem_prms["calib"]["MCMC_params"][ + "ddfsnow_bndhigh" + ] + ), + }, + } # define distributions from priors for sampling initials prior_dists = get_priors(priors) # ------------------ @@ -1227,299 +2135,602 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # ----- TEMPERATURE BIAS BOUNDS ----- # ----------------------------------- # note, temperature bias bounds will remain constant across chains if using emulator - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: # Selects from emulator sims dataframe - sims_fp = pygem_prms['root'] + '/Output/emulator/sims/' + glacier_str.split('.')[0].zfill(2) + '/' - sims_fn = glacier_str + '-' + str(pygem_prms['calib']['MCMC_params']['emulator_sims']) + '_emulator_sims.csv' + sims_fp = ( + pygem_prms["root"] + + "/Output/emulator/sims/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + sims_fn = ( + glacier_str + + "-" + + str( + pygem_prms["calib"]["MCMC_params"]["emulator_sims"] + ) + + "_emulator_sims.csv" + ) sims_df = pd.read_csv(sims_fp + sims_fn) - sims_df_subset = sims_df.loc[sims_df['kp']==1, :] - tbias_bndhigh = float(sims_df_subset['tbias'].max()) - tbias_bndlow = float(sims_df_subset['tbias'].min()) + sims_df_subset = sims_df.loc[sims_df["kp"] == 1, :] + tbias_bndhigh = float(sims_df_subset["tbias"].max()) + tbias_bndlow = float(sims_df_subset["tbias"].min()) # ----------------------------------- # ------------------- # --- set up MCMC --- # ------------------- # mass balance observation and standard deviation - obs = [(torch.tensor([mb_obs_mwea]),torch.tensor([mb_obs_mwea_err]))] + obs = [ + ( + torch.tensor([mb_obs_mwea]), + torch.tensor([mb_obs_mwea_err]), + ) + ] # if there are more observations to calibrate against, simply append a tuple of (obs, variance) to obs list # e.g. obs.append((torch.tensor(dmda_array),torch.tensor(dmda_err_array))) - if pygem_prms['calib']['MCMC_params']['option_use_emulator']: - mbfxn = mbEmulator.eval # returns (mb_mwea) - mbargs = None # no additional arguments for mbEmulator.eval() + if pygem_prms["calib"]["MCMC_params"]["option_use_emulator"]: + mbfxn = mbEmulator.eval # returns (mb_mwea) + mbargs = ( + None # no additional arguments for mbEmulator.eval() + ) else: - mbfxn = mb_mwea_calc # returns (mb_mwea) - mbargs = (gdir, modelprms, glacier_rgi_table, fls) # arguments for mb_mwea_calc() + mbfxn = mb_mwea_calc # returns (mb_mwea) + mbargs = ( + gdir, + modelprms, + glacier_rgi_table, + fls, + ) # arguments for mb_mwea_calc() # instantiate mbPosterior given priors, and observed values # note, mbEmulator.eval expects the modelprms to be ordered like so: [tbias, kp, ddfsnow], so priors and initial guesses must also be ordered as such) - priors = {key: priors[key] for key in ['tbias','kp','ddfsnow'] if key in priors} - mb = mcmc.mbPosterior(obs, priors, mb_func=mbfxn, mb_args=mbargs, potential_fxns=[mb_max, must_melt]) + priors = { + key: priors[key] + for key in ["tbias", "kp", "ddfsnow"] + if key in priors + } + mb = mcmc.mbPosterior( + obs, + priors, + mb_func=mbfxn, + mb_args=mbargs, + potential_fxns=[mb_max, must_melt], + ) # prepare export modelprms dictionary modelprms_export = {} - for k in ['tbias','kp','ddfsnow','ddfice','mb_mwea','ar']: + for k in ["tbias", "kp", "ddfsnow", "ddfice", "mb_mwea", "ar"]: modelprms_export[k] = {} # ------------------- # -------------------- # ----- run MCMC ----- - # -------------------- + # -------------------- try: ### loop over chains, adjust initial guesses accordingly. done in a while loop as to repeat a chain up to one time if it remained stuck throughout ### - n_chain=0 - repeat=False + n_chain = 0 + repeat = False while n_chain < args.nchains: # compile initial guesses and standardize by standard deviations # for 0th chain, take mean from regional priors if n_chain == 0: - initial_guesses = torch.tensor((tbias_mu, kp_gamma_alpha / kp_gamma_beta, pygem_prms['calib']['MCMC_params']['ddfsnow_mu'])) + initial_guesses = torch.tensor( + ( + tbias_mu, + kp_gamma_alpha / kp_gamma_beta, + pygem_prms["calib"]["MCMC_params"][ + "ddfsnow_mu" + ], + ) + ) # for all chains > 0, randomly sample from regional priors else: - initial_guesses = torch.tensor(get_initials(prior_dists)) + initial_guesses = torch.tensor( + get_initials(prior_dists) + ) if debug: - print(f"{glacier_str} chain {n_chain} initials:\ttbias: {initial_guesses[0]:.2f}, kp: {initial_guesses[1]:.2f}, ddfsnow: {initial_guesses[2]:.4f}") - initial_guesses_z = mcmc.z_normalize(initial_guesses, mb.means, mb.stds) + print( + f"{glacier_str} chain {n_chain} initials:\ttbias: {initial_guesses[0]:.2f}, kp: {initial_guesses[1]:.2f}, ddfsnow: {initial_guesses[2]:.4f}" + ) + initial_guesses_z = mcmc.z_normalize( + initial_guesses, mb.means, mb.stds + ) # instantiate sampler sampler = mcmc.Metropolis(mb.means, mb.stds) # draw samples - m_chain_z, pred_chain, m_primes_z, pred_primes, _, ar = sampler.sample(initial_guesses_z, - mb.log_posterior, - n_samples=args.chain_length, - h=pygem_prms['calib']['MCMC_params']['mcmc_step'], - burnin=int(args.burn_pct/100*args.chain_length), - thin_factor=pygem_prms['calib']['MCMC_params']['thin_interval'], - progress_bar=args.progress_bar) + ( + m_chain_z, + pred_chain, + m_primes_z, + pred_primes, + _, + ar, + ) = sampler.sample( + initial_guesses_z, + mb.log_posterior, + n_samples=args.chain_length, + h=pygem_prms["calib"]["MCMC_params"]["mcmc_step"], + burnin=int( + args.burn_pct / 100 * args.chain_length + ), + thin_factor=pygem_prms["calib"]["MCMC_params"][ + "thin_interval" + ], + progress_bar=args.progress_bar, + ) # Check condition at the end if (m_chain_z[:, 0] == m_chain_z[0, 0]).all(): - if not repeat and n_chain!=0: + if not repeat and n_chain != 0: repeat = True continue # inverse z-normalize the samples to original parameter space - m_chain = mcmc.inverse_z_normalize(m_chain_z, mb.means, mb.stds) - m_primes = mcmc.inverse_z_normalize(m_primes_z, mb.means, mb.stds) + m_chain = mcmc.inverse_z_normalize( + m_chain_z, mb.means, mb.stds + ) + m_primes = mcmc.inverse_z_normalize( + m_primes_z, mb.means, mb.stds + ) # concatenate mass balance - m_chain = torch.cat((m_chain, torch.tensor(pred_chain[0]).reshape(-1,1)), dim=1) - m_primes = torch.cat((m_primes, torch.tensor(pred_primes[0]).reshape(-1,1)), dim=1) + m_chain = torch.cat( + ( + m_chain, + torch.tensor(pred_chain[0]).reshape(-1, 1), + ), + dim=1, + ) + m_primes = torch.cat( + ( + m_primes, + torch.tensor(pred_primes[0]).reshape(-1, 1), + ), + dim=1, + ) if debug: # print('\nacceptance ratio:', model.step_method_dict[next(iter(model.stochastics))][0].ratio) - print('mb_mwea_mean:', np.round(torch.mean(m_chain[:,-1]).item(),3), - 'mb_mwea_std:', np.round(torch.std(m_chain[:,-1]).item(),3), - '\nmb_obs_mean:', np.round(mb_obs_mwea,3), 'mb_obs_std:', np.round(mb_obs_mwea_err,3)) + print( + "mb_mwea_mean:", + np.round(torch.mean(m_chain[:, -1]).item(), 3), + "mb_mwea_std:", + np.round(torch.std(m_chain[:, -1]).item(), 3), + "\nmb_obs_mean:", + np.round(mb_obs_mwea, 3), + "mb_obs_std:", + np.round(mb_obs_mwea_err, 3), + ) # plot chain - fp = (pygem_prms['root'] + f'/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/fig/') + fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/fig/" + ) os.makedirs(fp, exist_ok=True) if args.ncores > 1: - show=False + show = False else: - show=True - mcmc.plot_chain(m_primes, m_chain, obs[0], ar, glacier_str, show=show, fpath=f'{fp}/{glacier_str}-chain{n_chain}.png') + show = True + mcmc.plot_chain( + m_primes, + m_chain, + obs[0], + ar, + glacier_str, + show=show, + fpath=f"{fp}/{glacier_str}-chain{n_chain}.png", + ) for i in pred_chain.keys(): - mcmc.plot_resid_hist(obs[i], pred_chain[i], glacier_str, show=show, fpath=f'{fp}/{glacier_str}-chain{n_chain}-residuals-{i}.png') + mcmc.plot_resid_hist( + obs[i], + pred_chain[i], + glacier_str, + show=show, + fpath=f"{fp}/{glacier_str}-chain{n_chain}-residuals-{i}.png", + ) # Store data from model to be exported - chain_str = 'chain_' + str(n_chain) - modelprms_export['tbias'][chain_str] = m_chain[:,0].tolist() - modelprms_export['kp'][chain_str] = m_chain[:,1].tolist() - modelprms_export['ddfsnow'][chain_str] = m_chain[:,2].tolist() - modelprms_export['ddfice'][chain_str] = (m_chain[:,2] / - pygem_prms['sim']['params']['ddfsnow_iceratio']).tolist() - modelprms_export['mb_mwea'][chain_str] = m_chain[:,3].tolist() - modelprms_export['ar'][chain_str] = ar + chain_str = "chain_" + str(n_chain) + modelprms_export["tbias"][chain_str] = m_chain[ + :, 0 + ].tolist() + modelprms_export["kp"][chain_str] = m_chain[ + :, 1 + ].tolist() + modelprms_export["ddfsnow"][chain_str] = m_chain[ + :, 2 + ].tolist() + modelprms_export["ddfice"][chain_str] = ( + m_chain[:, 2] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ).tolist() + modelprms_export["mb_mwea"][chain_str] = m_chain[ + :, 3 + ].tolist() + modelprms_export["ar"][chain_str] = ar # increment n_chain only if the current iteration was a repeat n_chain += 1 # Export model parameters - modelprms_export['precgrad'] = [pygem_prms['sim']['params']['precgrad']] - modelprms_export['tsnow_threshold'] = [pygem_prms['sim']['params']['tsnow_threshold']] - modelprms_export['mb_obs_mwea'] = [float(mb_obs_mwea)] - modelprms_export['mb_obs_mwea_err'] = [float(mb_obs_mwea_err)] - modelprms_export['priors'] = priors - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = [(pygem_prms['root'] + f'/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/')] + modelprms_export["precgrad"] = [ + pygem_prms["sim"]["params"]["precgrad"] + ] + modelprms_export["tsnow_threshold"] = [ + pygem_prms["sim"]["params"]["tsnow_threshold"] + ] + modelprms_export["mb_obs_mwea"] = [float(mb_obs_mwea)] + modelprms_export["mb_obs_mwea_err"] = [ + float(mb_obs_mwea_err) + ] + modelprms_export["priors"] = priors + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = [ + ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + ] # if not using emulator (running full model), save output in ./calibration/ and ./calibration-fullsim/ - if not pygem_prms['calib']['MCMC_params']['option_use_emulator']: - modelprms_fp.append(pygem_prms['root'] + f'/Output/calibration{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) - + '/') + if not pygem_prms["calib"]["MCMC_params"][ + "option_use_emulator" + ]: + modelprms_fp.append( + pygem_prms["root"] + + f"/Output/calibration{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) for fp in modelprms_fp: if not os.path.exists(fp): os.makedirs(fp, exist_ok=True) modelprms_fullfn = fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) - modelprms_dict[args.option_calibration] = modelprms_export + modelprms_dict[args.option_calibration] = ( + modelprms_export + ) else: - modelprms_dict = {args.option_calibration: modelprms_export} - with open(modelprms_fullfn, 'w') as f: + modelprms_dict = { + args.option_calibration: modelprms_export + } + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) - + # MCMC LOG SUCCESS - mcmc_good_fp = pygem_prms['root'] + f'/Output/mcmc_success{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) + '/' + mcmc_good_fp = ( + pygem_prms["root"] + + f"/Output/mcmc_success{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(mcmc_good_fp): os.makedirs(mcmc_good_fp, exist_ok=True) txt_fn_good = glacier_str + "-mcmc_success.txt" with open(mcmc_good_fp + txt_fn_good, "w") as text_file: - text_file.write(glacier_str + ' successfully exported mcmc results') - + text_file.write( + glacier_str + " successfully exported mcmc results" + ) + except Exception as err: # MCMC LOG FAILURE - mcmc_fail_fp = pygem_prms['root'] + f'/Output/mcmc_fail{outpath_sfix}/' + glacier_str.split('.')[0].zfill(2) + '/' + mcmc_fail_fp = ( + pygem_prms["root"] + + f"/Output/mcmc_fail{outpath_sfix}/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(mcmc_fail_fp): os.makedirs(mcmc_fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-mcmc_fail.txt" with open(mcmc_fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + f' failed to complete MCMC: {err}') + text_file.write( + glacier_str + f" failed to complete MCMC: {err}" + ) # -------------------- - - #%% ===== HUSS AND HOCK (2015) CALIBRATION ===== - elif args.option_calibration == 'HH2015': - tbias_init = float(pygem_prms['calib']['HH2015_params']['tbias_init']) - tbias_step = float(pygem_prms['calib']['HH2015_params']['tbias_step']) - kp_init = float(pygem_prms['calib']['HH2015_params']['kp_init']) - kp_bndlow = float(pygem_prms['calib']['HH2015_params']['kp_bndlow']) - kp_bndhigh = float(pygem_prms['calib']['HH2015_params']['kp_bndhigh']) - ddfsnow_init = float(pygem_prms['calib']['HH2015_params']['ddfsnow_init']) - ddfsnow_bndlow = float(pygem_prms['calib']['HH2015_params']['ddfsnow_bndlow']) - ddfsnow_bndhigh = float(pygem_prms['calib']['HH2015_params']['ddfsnow_bndhigh']) + # %% ===== HUSS AND HOCK (2015) CALIBRATION ===== + elif args.option_calibration == "HH2015": + tbias_init = float( + pygem_prms["calib"]["HH2015_params"]["tbias_init"] + ) + tbias_step = float( + pygem_prms["calib"]["HH2015_params"]["tbias_step"] + ) + kp_init = float( + pygem_prms["calib"]["HH2015_params"]["kp_init"] + ) + kp_bndlow = float( + pygem_prms["calib"]["HH2015_params"]["kp_bndlow"] + ) + kp_bndhigh = float( + pygem_prms["calib"]["HH2015_params"]["kp_bndhigh"] + ) + ddfsnow_init = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_init"] + ) + ddfsnow_bndlow = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndlow"] + ) + ddfsnow_bndhigh = float( + pygem_prms["calib"]["HH2015_params"]["ddfsnow_bndhigh"] + ) # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) continue_param_search = True - + # ----- FUNCTIONS: COMPUTATIONALLY FASTER AND MORE ROBUST THAN SCIPY MINIMIZE ----- - def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid, - debug=False): + def update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=False, + ): # If mass balance less than observation, reduce tbias - if prm2opt == 'kp': + if prm2opt == "kp": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid - elif prm2opt == 'ddfsnow': + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) + elif prm2opt == "ddfsnow": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high - elif prm2opt == 'tbias': + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) + elif prm2opt == "tbias": if mb_mwea_mid < mb_obs_mwea: - prm_bndlow_new, mb_mwea_low_new = prm_bndlow, mb_mwea_low - prm_bndhigh_new, mb_mwea_high_new = prm_mid, mb_mwea_mid + prm_bndlow_new, mb_mwea_low_new = ( + prm_bndlow, + mb_mwea_low, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_mid, + mb_mwea_mid, + ) else: - prm_bndlow_new, mb_mwea_low_new = prm_mid, mb_mwea_mid - prm_bndhigh_new, mb_mwea_high_new = prm_bndhigh, mb_mwea_high - + prm_bndlow_new, mb_mwea_low_new = ( + prm_mid, + mb_mwea_mid, + ) + prm_bndhigh_new, mb_mwea_high_new = ( + prm_bndhigh, + mb_mwea_high, + ) + prm_mid_new = (prm_bndlow_new + prm_bndhigh_new) / 2 modelprms[prm2opt] = prm_mid_new - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid_new = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid_new = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow_new,2), - 'mb_mwea_low:', np.round(mb_mwea_low_new,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh_new,2), - 'mb_mwea_high:', np.round(mb_mwea_high_new,2)) - print(prm2opt + '_mid:', np.round(prm_mid_new,2), - 'mb_mwea_mid:', np.round(mb_mwea_mid_new,3)) - - return (prm_bndlow_new, prm_bndhigh_new, prm_mid_new, - mb_mwea_low_new, mb_mwea_high_new, mb_mwea_mid_new) - - - def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, - kp_bnds=None, tbias_bnds=None, ddfsnow_bnds=None, - mb_mwea_threshold=0.005, debug=False): - assert prm2opt is not None, 'For single_param_optimizer you must specify parameter to optimize' - - if prm2opt == 'kp': + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow_new, 2), + "mb_mwea_low:", + np.round(mb_mwea_low_new, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh_new, 2), + "mb_mwea_high:", + np.round(mb_mwea_high_new, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid_new, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid_new, 3), + ) + + return ( + prm_bndlow_new, + prm_bndhigh_new, + prm_mid_new, + mb_mwea_low_new, + mb_mwea_high_new, + mb_mwea_mid_new, + ) + + def single_param_optimizer( + modelprms_subset, + mb_obs_mwea, + prm2opt=None, + kp_bnds=None, + tbias_bnds=None, + ddfsnow_bnds=None, + mb_mwea_threshold=0.005, + debug=False, + ): + assert prm2opt is not None, ( + "For single_param_optimizer you must specify parameter to optimize" + ) + + if prm2opt == "kp": prm_bndlow = kp_bnds[0] prm_bndhigh = kp_bnds[1] - modelprms['tbias'] = modelprms_subset['tbias'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] - elif prm2opt == 'ddfsnow': + modelprms["tbias"] = modelprms_subset["tbias"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] + elif prm2opt == "ddfsnow": prm_bndlow = ddfsnow_bnds[0] prm_bndhigh = ddfsnow_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['tbias'] = modelprms_subset['tbias'] - elif prm2opt == 'tbias': + modelprms["kp"] = modelprms_subset["kp"] + modelprms["tbias"] = modelprms_subset["tbias"] + elif prm2opt == "tbias": prm_bndlow = tbias_bnds[0] prm_bndhigh = tbias_bnds[1] - modelprms['kp'] = modelprms_subset['kp'] - modelprms['ddfsnow'] = modelprms_subset['ddfsnow'] + modelprms["kp"] = modelprms_subset["kp"] + modelprms["ddfsnow"] = modelprms_subset["ddfsnow"] # Lower bound modelprms[prm2opt] = prm_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_low = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_low = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound modelprms[prm2opt] = prm_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_high = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_high = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Middle bound prm_mid = (prm_bndlow + prm_bndhigh) / 2 modelprms[prm2opt] = prm_mid - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_mid = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_mid = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) + if debug: - print(prm2opt + '_bndlow:', np.round(prm_bndlow,2), 'mb_mwea_low:', np.round(mb_mwea_low,2)) - print(prm2opt + '_bndhigh:', np.round(prm_bndhigh,2), 'mb_mwea_high:', np.round(mb_mwea_high,2)) - print(prm2opt + '_mid:', np.round(prm_mid,2), 'mb_mwea_mid:', np.round(mb_mwea_mid,3)) - + print( + prm2opt + "_bndlow:", + np.round(prm_bndlow, 2), + "mb_mwea_low:", + np.round(mb_mwea_low, 2), + ) + print( + prm2opt + "_bndhigh:", + np.round(prm_bndhigh, 2), + "mb_mwea_high:", + np.round(mb_mwea_high, 2), + ) + print( + prm2opt + "_mid:", + np.round(prm_mid, 2), + "mb_mwea_mid:", + np.round(mb_mwea_mid, 3), + ) + # Optimize the model parameter - if np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: + if ( + np.absolute(mb_mwea_low - mb_obs_mwea) + <= mb_mwea_threshold + ): modelprms[prm2opt] = prm_bndlow mb_mwea_mid = mb_mwea_low - elif np.absolute(mb_mwea_low - mb_obs_mwea) <= mb_mwea_threshold: + elif ( + np.absolute(mb_mwea_low - mb_obs_mwea) + <= mb_mwea_threshold + ): modelprms[prm2opt] = prm_bndhigh mb_mwea_mid = mb_mwea_high else: ncount = 0 - while np.absolute(mb_mwea_mid - mb_obs_mwea) > mb_mwea_threshold: + while ( + np.absolute(mb_mwea_mid - mb_obs_mwea) + > mb_mwea_threshold + ): if debug: - print('\n ncount:', ncount) - (prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_high, mb_mwea_mid) = ( - update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, - mb_mwea_low, mb_mwea_high, mb_mwea_mid, debug=debug)) + print("\n ncount:", ncount) + ( + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + ) = update_bnds( + prm2opt, + prm_bndlow, + prm_bndhigh, + prm_mid, + mb_mwea_low, + mb_mwea_high, + mb_mwea_mid, + debug=debug, + ) ncount += 1 - + return modelprms, mb_mwea_mid - - + # ===== ROUND 1: PRECIPITATION FACTOR ====== if debug: - print('Round 1:') - + print("Round 1:") + if debug: - print(glacier_str + ' kp: ' + str(np.round(modelprms['kp'],2)) + - ' ddfsnow: ' + str(np.round(modelprms['ddfsnow'],4)) + - ' tbias: ' + str(np.round(modelprms['tbias'],2))) - + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) + # Lower bound - modelprms['kp'] = kp_bndlow - mb_mwea_kp_low = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["kp"] = kp_bndlow + mb_mwea_kp_low = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound - modelprms['kp'] = kp_bndhigh - mb_mwea_kp_high = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - + modelprms["kp"] = kp_bndhigh + mb_mwea_kp_high = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) + # Optimal precipitation factor if mb_obs_mwea < mb_mwea_kp_low: kp_opt = kp_bndlow @@ -1529,30 +2740,54 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_kp_high else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_init, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_init, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } kp_bnds = (kp_bndlow, kp_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='kp', kp_bnds=kp_bnds, debug=debug) - kp_opt = modelprms_opt['kp'] + modelprms_subset, + mb_obs_mwea, + prm2opt="kp", + kp_bnds=kp_bnds, + debug=debug, + ) + kp_opt = modelprms_opt["kp"] continue_param_search = False - + # Update parameter values - modelprms['kp'] = kp_opt + modelprms["kp"] = kp_opt if debug: - print(' kp:', np.round(kp_opt,2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " kp:", + np.round(kp_opt, 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) # ===== ROUND 2: DEGREE-DAY FACTOR OF SNOW ====== if continue_param_search: if debug: - print('Round 2:') + print("Round 2:") # Lower bound - modelprms['ddfsnow'] = ddfsnow_bndlow - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_ddflow = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfsnow"] = ddfsnow_bndlow + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_ddflow = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Upper bound - modelprms['ddfsnow'] = ddfsnow_bndhigh - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - mb_mwea_ddfhigh = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["ddfsnow"] = ddfsnow_bndhigh + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + mb_mwea_ddfhigh = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Optimal degree-day factor of snow if mb_obs_mwea < mb_mwea_ddfhigh: ddfsnow_opt = ddfsnow_bndhigh @@ -1562,129 +2797,211 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, mb_mwea = mb_mwea_ddflow else: # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, 'ddfsnow': ddfsnow_init, 'tbias': tbias_init} + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_init, + "tbias": tbias_init, + } ddfsnow_bnds = (ddfsnow_bndlow, ddfsnow_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='ddfsnow', ddfsnow_bnds=ddfsnow_bnds, debug=debug) - ddfsnow_opt = modelprms_opt['ddfsnow'] + modelprms_subset, + mb_obs_mwea, + prm2opt="ddfsnow", + ddfsnow_bnds=ddfsnow_bnds, + debug=debug, + ) + ddfsnow_opt = modelprms_opt["ddfsnow"] continue_param_search = False # Update parameter values - modelprms['ddfsnow'] = ddfsnow_opt - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + modelprms["ddfsnow"] = ddfsnow_opt + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) if debug: - print(' ddfsnow:', np.round(ddfsnow_opt,4), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " ddfsnow:", + np.round(ddfsnow_opt, 4), + "mb_mwea:", + np.round(mb_mwea, 2), + ) else: - ddfsnow_opt = modelprms['ddfsnow'] - + ddfsnow_opt = modelprms["ddfsnow"] + # ===== ROUND 3: TEMPERATURE BIAS ====== if continue_param_search: if debug: - print('Round 3:') + print("Round 3:") # ----- TEMPBIAS: max accumulation ----- # Lower temperature bound based on no positive temperatures # Temperature at the lowest bin # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tbias - tbias_max_acc = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) + tbias_max_acc = ( + -1 + * ( + gdir.historical_climate["temp"] + + gdir.historical_climate["lr"] + * ( + fls[0].surface_h.min() + - gdir.historical_climate["elev"] + ) + ).max() + ) tbias_bndlow = tbias_max_acc - modelprms['tbias'] = tbias_bndlow - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["tbias"] = tbias_bndlow + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(' tbias_bndlow:', np.round(tbias_bndlow,2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " tbias_bndlow:", + np.round(tbias_bndlow, 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) # Upper bound - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 20: - modelprms['tbias'] = modelprms['tbias'] + tbias_step - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 20: + modelprms["tbias"] = modelprms["tbias"] + tbias_step + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(' tc:', np.round(modelprms['tbias'],2), 'mb_mwea:', np.round(mb_mwea,2)) - tbias_bndhigh = modelprms['tbias'] + print( + " tc:", + np.round(modelprms["tbias"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) + tbias_bndhigh = modelprms["tbias"] # Single parameter optimizer (computationally more efficient and less prone to fail) - modelprms_subset = {'kp':kp_opt, - 'ddfsnow': ddfsnow_opt, - 'tbias': modelprms['tbias'] - tbias_step/2} - tbias_bnds = (tbias_bndhigh-tbias_step, tbias_bndhigh) + modelprms_subset = { + "kp": kp_opt, + "ddfsnow": ddfsnow_opt, + "tbias": modelprms["tbias"] - tbias_step / 2, + } + tbias_bnds = (tbias_bndhigh - tbias_step, tbias_bndhigh) modelprms_opt, mb_mwea = single_param_optimizer( - modelprms_subset, mb_obs_mwea, prm2opt='tbias', tbias_bnds=tbias_bnds, debug=debug) - + modelprms_subset, + mb_obs_mwea, + prm2opt="tbias", + tbias_bnds=tbias_bnds, + debug=debug, + ) + # Update parameter values - tbias_opt = modelprms_opt['tbias'] - modelprms['tbias'] = tbias_opt + tbias_opt = modelprms_opt["tbias"] + modelprms["tbias"] = tbias_opt if debug: - print(' tbias:', np.round(tbias_opt,3), 'mb_mwea:', np.round(mb_mwea,3)) + print( + " tbias:", + np.round(tbias_opt, 3), + "mb_mwea:", + np.round(mb_mwea, 3), + ) else: - tbias_opt = modelprms['tbias'] - - + tbias_opt = modelprms["tbias"] + # Export model parameters modelprms = modelprms_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [mb_mwea] - modelprms['mb_obs_mwea'] = [mb_obs_mwea] - modelprms['mb_obs_mwea_err'] = [mb_obs_mwea_err] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [mb_mwea] + modelprms["mb_obs_mwea"] = [mb_obs_mwea] + modelprms["mb_obs_mwea_err"] = [mb_obs_mwea_err] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) - - - #%% ===== MODIFIED HUSS AND HOCK (2015) CALIBRATION ===== + + # %% ===== MODIFIED HUSS AND HOCK (2015) CALIBRATION ===== # used in Rounce et al. (2020; MCMC paper) # - precipitation factor, then temperature bias (no ddfsnow) # - ranges different - elif args.option_calibration == 'HH2015mod': - tbias_init = pygem_prms['calib']['HH2015mod_params']['tbias_init'] - tbias_step = pygem_prms['calib']['HH2015mod_params']['tbias_step'] - kp_init = pygem_prms['calib']['HH2015mod_params']['kp_init'] - kp_bndlow = pygem_prms['calib']['HH2015mod_params']['kp_bndlow'] - kp_bndhigh = pygem_prms['calib']['HH2015mod_params']['kp_bndhigh'] - ddfsnow_init = pygem_prms['calib']['HH2015mod_params']['ddfsnow_init'] - + elif args.option_calibration == "HH2015mod": + tbias_init = pygem_prms["calib"]["HH2015mod_params"][ + "tbias_init" + ] + tbias_step = pygem_prms["calib"]["HH2015mod_params"][ + "tbias_step" + ] + kp_init = pygem_prms["calib"]["HH2015mod_params"]["kp_init"] + kp_bndlow = pygem_prms["calib"]["HH2015mod_params"][ + "kp_bndlow" + ] + kp_bndhigh = pygem_prms["calib"]["HH2015mod_params"][ + "kp_bndhigh" + ] + ddfsnow_init = pygem_prms["calib"]["HH2015mod_params"][ + "ddfsnow_init" + ] + # ----- Initialize model parameters ----- - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init - modelprms['ddfsnow'] = ddfsnow_init - modelprms['ddfice'] = modelprms['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] - + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init + modelprms["ddfsnow"] = ddfsnow_init + modelprms["ddfice"] = ( + modelprms["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) + # ----- FUNCTIONS ----- def objective(modelprms_subset): - """ Objective function for mass balance data (mimize difference between model and observation). + """Objective function for mass balance data (mimize difference between model and observation). Parameters ---------- modelprms_subset : list of model parameters [kp, ddfsnow, tbias] """ # Subset of model parameters used to reduce number of constraints required - modelprms['kp'] = modelprms_subset[0] - modelprms['tbias'] = tbias_init + modelprms["kp"] = modelprms_subset[0] + modelprms["tbias"] = tbias_init if len(modelprms_subset) > 1: - modelprms['tbias'] = modelprms_subset[1] + modelprms["tbias"] = modelprms_subset[1] # Mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) # Difference with observation (mwea) mb_dif_mwea_abs = abs(mb_obs_mwea - mb_mwea) return mb_dif_mwea_abs - - def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, - run_opt=True, eps_opt=pygem_prms['calib']['HH2015mod_params']['eps_opt'], - ftol_opt=pygem_prms['calib']['HH2015mod_params']['ftol_opt']): - """ Run the optimization for the single glacier objective function. + def run_objective( + modelprms_init, + mb_obs_mwea, + modelprms_bnds=None, + run_opt=True, + eps_opt=pygem_prms["calib"]["HH2015mod_params"]["eps_opt"], + ftol_opt=pygem_prms["calib"]["HH2015mod_params"][ + "ftol_opt" + ], + ): + """Run the optimization for the single glacier objective function. Parameters ---------- @@ -1698,38 +3015,69 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, """ # Run the optimization if run_opt: - modelprms_opt = minimize(objective, modelprms_init, method=pygem_prms['calib']['HH2015mod_params']['method_opt'], - bounds=modelprms_bnds, options={'ftol':ftol_opt, 'eps':eps_opt}) + modelprms_opt = minimize( + objective, + modelprms_init, + method=pygem_prms["calib"]["HH2015mod_params"][ + "method_opt" + ], + bounds=modelprms_bnds, + options={"ftol": ftol_opt, "eps": eps_opt}, + ) # Record the optimized parameters modelprms_subset = modelprms_opt.x else: modelprms_subset = modelprms.copy() - modelprms['kp'] = modelprms_subset[0] + modelprms["kp"] = modelprms_subset[0] if len(modelprms_subset) == 2: - modelprms['tbias'] = modelprms_subset[1] + modelprms["tbias"] = modelprms_subset[1] # Re-run the optimized parameters in order to see the mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) return modelprms, mb_mwea - # ----- Temperature bias bounds ----- tbias_bndhigh = 0 # Tbias lower bound based on no positive temperatures - tbias_bndlow = (-1 * (gdir.historical_climate['temp'] + gdir.historical_climate['lr'] * - (fls[0].surface_h.min() - gdir.historical_climate['elev'])).max()) - modelprms['tbias'] = tbias_bndlow - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + tbias_bndlow = ( + -1 + * ( + gdir.historical_climate["temp"] + + gdir.historical_climate["lr"] + * ( + fls[0].surface_h.min() + - gdir.historical_climate["elev"] + ) + ).max() + ) + modelprms["tbias"] = tbias_bndlow + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(' tbias_bndlow:', np.round(tbias_bndlow,2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + " tbias_bndlow:", + np.round(tbias_bndlow, 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) # Tbias upper bound (based on kp_bndhigh) - modelprms['kp'] = kp_bndhigh - - while mb_mwea > mb_obs_mwea and modelprms['tbias'] < 20: - modelprms['tbias'] = modelprms['tbias'] + 1 - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["kp"] = kp_bndhigh + + while mb_mwea > mb_obs_mwea and modelprms["tbias"] < 20: + modelprms["tbias"] = modelprms["tbias"] + 1 + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print(' tc:', np.round(modelprms['tbias'],2), 'mb_mwea:', np.round(mb_mwea,2)) - tbias_bndhigh = modelprms['tbias'] + print( + " tc:", + np.round(modelprms["tbias"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + ) + tbias_bndhigh = modelprms["tbias"] # ===== ROUND 1: PRECIPITATION FACTOR ===== # Adjust bounds based on range of temperature bias @@ -1737,80 +3085,119 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, tbias_init = tbias_bndhigh elif tbias_init < tbias_bndlow: tbias_init = tbias_bndlow - modelprms['tbias'] = tbias_init - modelprms['kp'] = kp_init + modelprms["tbias"] = tbias_init + modelprms["kp"] = kp_init tbias_bndlow_opt = tbias_init tbias_bndhigh_opt = tbias_init # Constrain bounds of precipitation factor and temperature bias - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - nbinyears_negmbclim = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls, - return_tbias_mustmelt=True) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) + nbinyears_negmbclim = mb_mwea_calc( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + return_tbias_mustmelt=True, + ) if debug: - print('\ntbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "\ntbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) # Adjust lower or upper bound based on the observed mass balance test_count = 0 if mb_mwea > mb_obs_mwea: if debug: - print('increase tbias, decrease kp') + print("increase tbias, decrease kp") kp_bndhigh = 1 # Check if lower bound causes good agreement - modelprms['kp'] = kp_bndlow - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + modelprms["kp"] = kp_bndlow + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) while mb_mwea > mb_obs_mwea and test_count < 20: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] + tbias_step + modelprms["tbias"] = modelprms["tbias"] + tbias_step # Update bounds - tbias_bndhigh_opt = modelprms['tbias'] - tbias_bndlow_opt = modelprms['tbias'] - tbias_step + tbias_bndhigh_opt = modelprms["tbias"] + tbias_bndlow_opt = modelprms["tbias"] - tbias_step # Compute mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 else: if debug: - print('decrease tbias, increase kp') + print("decrease tbias, increase kp") kp_bndlow = 1 # Check if upper bound causes good agreement - modelprms['kp'] = kp_bndhigh - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) - + modelprms["kp"] = kp_bndhigh + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) + while mb_obs_mwea > mb_mwea and test_count < 20: # Update temperature bias - modelprms['tbias'] = modelprms['tbias'] - tbias_step + modelprms["tbias"] = modelprms["tbias"] - tbias_step # If temperature bias is at lower limit, then increase precipitation factor - if modelprms['tbias'] <= tbias_bndlow: - modelprms['tbias'] = tbias_bndlow + if modelprms["tbias"] <= tbias_bndlow: + modelprms["tbias"] = tbias_bndlow if test_count > 0: kp_bndhigh = kp_bndhigh + 1 - modelprms['kp'] = kp_bndhigh + modelprms["kp"] = kp_bndhigh # Update bounds (must do after potential correction for lower bound) - tbias_bndlow_opt = modelprms['tbias'] - tbias_bndhigh_opt = modelprms['tbias'] + tbias_step + tbias_bndlow_opt = modelprms["tbias"] + tbias_bndhigh_opt = modelprms["tbias"] + tbias_step # Compute mass balance - mb_mwea = mb_mwea_calc(gdir, modelprms, glacier_rgi_table, fls=fls) + mb_mwea = mb_mwea_calc( + gdir, modelprms, glacier_rgi_table, fls=fls + ) if debug: - print('tbias:', np.round(modelprms['tbias'],2), 'kp:', np.round(modelprms['kp'],2), - 'mb_mwea:', np.round(mb_mwea,2), 'obs_mwea:', np.round(mb_obs_mwea,2)) + print( + "tbias:", + np.round(modelprms["tbias"], 2), + "kp:", + np.round(modelprms["kp"], 2), + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mwea:", + np.round(mb_obs_mwea, 2), + ) test_count += 1 # ----- RUN OPTIMIZATION WITH CONSTRAINED BOUNDS ----- kp_bnds = (kp_bndlow, kp_bndhigh) kp_init = kp_init - + tbias_bnds = (tbias_bndlow_opt, tbias_bndhigh_opt) tbias_init = np.mean([tbias_bndlow_opt, tbias_bndhigh_opt]) if debug: - print('tbias bounds:', tbias_bnds) - print('kp bounds:', kp_bnds) - + print("tbias bounds:", tbias_bnds) + print("kp bounds:", kp_bnds) + # Set up optimization for only the precipitation factor if tbias_bndlow_opt == tbias_bndhigh_opt: modelprms_subset = [kp_init] @@ -1819,48 +3206,77 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, else: modelprms_subset = [kp_init, tbias_init] modelprms_bnds = (kp_bnds, tbias_bnds) - - # Run optimization - modelparams_opt, mb_mwea = run_objective(modelprms_subset, mb_obs_mwea, - modelprms_bnds=modelprms_bnds, ftol_opt=1e-3) - kp_opt = modelparams_opt['kp'] - tbias_opt = modelparams_opt['tbias'] + # Run optimization + modelparams_opt, mb_mwea = run_objective( + modelprms_subset, + mb_obs_mwea, + modelprms_bnds=modelprms_bnds, + ftol_opt=1e-3, + ) + + kp_opt = modelparams_opt["kp"] + tbias_opt = modelparams_opt["tbias"] if debug: - print('mb_mwea:', np.round(mb_mwea,2), 'obs_mb:', np.round(mb_obs_mwea,2), - 'kp:', np.round(kp_opt,2), 'tbias:', np.round(tbias_opt,2), '\n\n') + print( + "mb_mwea:", + np.round(mb_mwea, 2), + "obs_mb:", + np.round(mb_obs_mwea, 2), + "kp:", + np.round(kp_opt, 2), + "tbias:", + np.round(tbias_opt, 2), + "\n\n", + ) # Export model parameters modelprms = modelparams_opt - for vn in ['ddfice', 'ddfsnow', 'kp', 'precgrad', 'tbias', 'tsnow_threshold']: + for vn in [ + "ddfice", + "ddfsnow", + "kp", + "precgrad", + "tbias", + "tsnow_threshold", + ]: modelprms[vn] = [modelprms[vn]] - modelprms['mb_mwea'] = [mb_mwea] - modelprms['mb_obs_mwea'] = [mb_obs_mwea] - modelprms['mb_obs_mwea_err'] = [mb_obs_mwea_err] - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms["mb_mwea"] = [mb_mwea] + modelprms["mb_obs_mwea"] = [mb_obs_mwea] + modelprms["mb_obs_mwea_err"] = [mb_obs_mwea_err] + + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp): os.makedirs(modelprms_fp, exist_ok=True) modelprms_fullfn = modelprms_fp + modelprms_fn if os.path.exists(modelprms_fullfn): - with open(modelprms_fullfn, 'r') as f: + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) modelprms_dict[args.option_calibration] = modelprms else: modelprms_dict = {args.option_calibration: modelprms} - with open(modelprms_fullfn, 'w') as f: + with open(modelprms_fullfn, "w") as f: json.dump(modelprms_dict, f) else: # LOG FAILURE - fail_fp = pygem_prms['root'] + '/Outputcal_fail/' + glacier_str.split('.')[0].zfill(2) + '/' + fail_fp = ( + pygem_prms["root"] + + "/Outputcal_fail/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(fail_fp): os.makedirs(fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-cal_fail.txt" with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' had no flowlines or mb_data.') + text_file.write(glacier_str + " had no flowlines or mb_data.") # Global variables for Spyder development if args.ncores == 1: @@ -1868,7 +3284,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, main_vars = inspect.currentframe().f_locals -#%% PARALLEL PROCESSING +# %% PARALLEL PROCESSING def main(): time_start = time.time() parser = getparser() @@ -1881,14 +3297,18 @@ def main(): glac_no = [float(g) for g in glac_no] glac_no = [f"{g:.5f}" if g >= 10 else f"0{g:.5f}" for g in glac_no] elif args.rgi_glac_number_fn is not None: - with open(args.rgi_glac_number_fn, 'r') as f: + with open(args.rgi_glac_number_fn, "r") as f: glac_no = json.load(f) else: main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=args.rgi_region01, rgi_regionsO2=args.rgi_region02, - include_landterm=pygem_prms['setup']['include_landterm'], include_laketerm=pygem_prms['setup']['include_laketerm'], - include_tidewater=pygem_prms['setup']['include_tidewater'], min_glac_area_km2=pygem_prms['setup']['min_glac_area_km2']) - glac_no = list(main_glac_rgi_all['rgino_str'].values) + rgi_regionsO1=args.rgi_region01, + rgi_regionsO2=args.rgi_region02, + include_landterm=pygem_prms["setup"]["include_landterm"], + include_laketerm=pygem_prms["setup"]["include_laketerm"], + include_tidewater=pygem_prms["setup"]["include_tidewater"], + min_glac_area_km2=pygem_prms["setup"]["min_glac_area_km2"], + ) + glac_no = list(main_glac_rgi_all["rgino_str"].values) # Number of cores for parallel processing if args.ncores > 1: @@ -1897,28 +3317,31 @@ def main(): num_cores = 1 # Glacier number lists to pass for parallel processing - glac_no_lsts = modelsetup.split_list(glac_no, n=num_cores, option_ordered=args.option_ordered) + glac_no_lsts = modelsetup.split_list( + glac_no, n=num_cores, option_ordered=args.option_ordered + ) # Read GCM names from argument parser gcm_name = args.ref_gcm_name - print('Processing:', gcm_name) - + print("Processing:", gcm_name) + # Pack variables for multiprocessing list_packed_vars = [] for count, glac_no_lst in enumerate(glac_no_lsts): list_packed_vars.append([count, glac_no_lst, gcm_name]) # Parallel processing if num_cores > 1: - print('Processing in parallel with ' + str(num_cores) + ' cores...') + print("Processing in parallel with " + str(num_cores) + " cores...") with multiprocessing.Pool(num_cores) as p: - p.map(run,list_packed_vars) + p.map(run, list_packed_vars) # If not in parallel, then only should be one loop else: # Loop through the chunks and export bias adjustments for n in range(len(list_packed_vars)): run(list_packed_vars[n]) - print('Total processing time:', time.time()-time_start, 's') + print("Total processing time:", time.time() - time_start, "s") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_calibration_frontalablation.py b/pygem/bin/run/run_calibration_frontalablation.py index cd8b0c3d..21252b56 100644 --- a/pygem/bin/run/run_calibration_frontalablation.py +++ b/pygem/bin/run/run_calibration_frontalablation.py @@ -7,46 +7,45 @@ Calibrate frontal ablation parameters for tidewater glaciers """ + # Built-in libraries import argparse -import os -import pickle -import sys -import time -import json import glob -from functools import partial +import json + # External libraries import multiprocessing -import pandas as pd +import os +from functools import partial + import matplotlib.pyplot as plt import numpy as np +import pandas as pd from scipy.stats import linregress -import xarray as xr + # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() +from oggm import cfg, tasks, utils +from oggm.core.flowline import FluxBasedModel +from oggm.core.massbalance import apparent_mb_from_any_mb + import pygem.pygem_modelsetup as modelsetup -from pygem.massbalance import PyGEMMassBalance +from pygem import class_climate from pygem.glacierdynamics import MassRedistributionCurveModel +from pygem.massbalance import PyGEMMassBalance from pygem.oggm_compat import single_flowline_glacier_directory_with_calving -from pygem.shop import debris -from pygem import class_climate - -import oggm -from oggm import utils, cfg -from oggm import tasks -from oggm.core.flowline import FluxBasedModel -from oggm.core.massbalance import apparent_mb_from_any_mb +from pygem.shop import debris ############### ### globals ### ############### -frontal_ablation_Gta_cn = 'fa_gta_obs' -frontal_ablation_Gta_unc_cn = 'fa_gta_obs_unc' +frontal_ablation_Gta_cn = "fa_gta_obs" +frontal_ablation_Gta_unc_cn = "fa_gta_obs_unc" # Frontal ablation calibration parameter (yr-1) calving_k_init = 0.1 @@ -54,45 +53,57 @@ calving_k_bndhigh_gl = 5 calving_k_step_gl = 0.2 -perc_threshold_agreement = 0.05 # Threshold (%) at which to stop optimization and consider good agreement -fa_threshold = 1e-4 # Absolute threshold at which to stop optimization (Gta) - -rgi_reg_dict = {'all':'Global', - 'global':'Global', - 1:'Alaska', - 2:'W Canada/USA', - 3:'Arctic Canada North', - 4:'Arctic Canada South', - 5:'Greenland', - 6:'Iceland', - 7:'Svalbard', - 8:'Scandinavia', - 9:'Russian Arctic', - 10:'North Asia', - 11:'Central Europe', - 12:'Caucasus/Middle East', - 13:'Central Asia', - 14:'South Asia West', - 15:'South Asia East', - 16:'Low Latitudes', - 17:'Southern Andes', - 18:'New Zealand', - 19:'Antarctica/Subantarctic' - } +perc_threshold_agreement = 0.05 # Threshold (%) at which to stop optimization and consider good agreement +fa_threshold = 1e-4 # Absolute threshold at which to stop optimization (Gta) + +rgi_reg_dict = { + "all": "Global", + "global": "Global", + 1: "Alaska", + 2: "W Canada/USA", + 3: "Arctic Canada North", + 4: "Arctic Canada South", + 5: "Greenland", + 6: "Iceland", + 7: "Svalbard", + 8: "Scandinavia", + 9: "Russian Arctic", + 10: "North Asia", + 11: "Central Europe", + 12: "Caucasus/Middle East", + 13: "Central Asia", + 14: "South Asia West", + 15: "South Asia East", + 16: "Low Latitudes", + 17: "Southern Andes", + 18: "New Zealand", + 19: "Antarctica/Subantarctic", +} ############### + def mwea_to_gta(mwea, area_m2): - return mwea * pygem_prms['constants']['density_water'] * area_m2 / 1e12 + return mwea * pygem_prms["constants"]["density_water"] * area_m2 / 1e12 -def gta_to_mwea(gta, area_m2): - return gta * 1e12 / pygem_prms['constants']['density_water'] / area_m2 -def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, - ignore_nan=True, invert_standard=False, debug=False, - calc_mb_geo_correction=False, reset_gdir=True): +def gta_to_mwea(gta, area_m2): + return gta * 1e12 / pygem_prms["constants"]["density_water"] / area_m2 + + +def reg_calving_flux( + main_glac_rgi, + calving_k, + args, + fa_glac_data_reg=None, + ignore_nan=True, + invert_standard=False, + debug=False, + calc_mb_geo_correction=False, + reset_gdir=True, +): """ Compute the calving flux for a group of glaciers - + Parameters ---------- main_glac_rgi : pd.DataFrame @@ -108,429 +119,674 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, ------- output_df : pd.DataFrame Dataframe containing information pertaining to each glacier's calving flux - """ + """ # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=args.ref_endyear, spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) + startyear=args.ref_startyear, + endyear=args.ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) # ===== LOAD CLIMATE DATA ===== # Climate class - assert args.ref_gcm_name in ['ERA5', 'ERA-Interim'], ( - 'Error: Calibration not set up for ' + args.ref_gcm_name) + assert args.ref_gcm_name in ["ERA5", "ERA-Interim"], ( + "Error: Calibration not set up for " + args.ref_gcm_name + ) gcm = class_climate.GCM(name=args.ref_gcm_name) # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table) - if pygem_prms['mb']['option_ablation'] == 2 and args.ref_gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi, dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table + ) + if pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in [ + "ERA5" + ]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table + ) else: gcm_tempstd = np.zeros(gcm_temp.shape) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) # Lapse rate [degC m-1] - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) # ===== CALIBRATE ALL THE GLACIERS AT ONCE ===== - output_cns = ['RGIId', 'calving_k', 'calving_thick', 'calving_flux_Gta_inv', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics'] - output_df = pd.DataFrame(np.zeros((main_glac_rgi.shape[0],len(output_cns))), columns=output_cns) - output_df['RGIId'] = main_glac_rgi.RGIId - output_df['calving_k'] = calving_k - output_df['calving_thick'] = np.nan - output_df['calving_flux_Gta'] = np.nan - output_df['oggm_dynamics'] = 0 - output_df['mb_mwea_fa_asl_lost'] = 0. + output_cns = [ + "RGIId", + "calving_k", + "calving_thick", + "calving_flux_Gta_inv", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ] + output_df = pd.DataFrame( + np.zeros((main_glac_rgi.shape[0], len(output_cns))), columns=output_cns + ) + output_df["RGIId"] = main_glac_rgi.RGIId + output_df["calving_k"] = calving_k + output_df["calving_thick"] = np.nan + output_df["calving_flux_Gta"] = np.nan + output_df["oggm_dynamics"] = 0 + output_df["mb_mwea_fa_asl_lost"] = 0.0 for nglac in np.arange(main_glac_rgi.shape[0]): - - if args.verbose: print('\n',main_glac_rgi.loc[main_glac_rgi.index.values[nglac],'RGIId']) - + if args.verbose: + print( + "\n", + main_glac_rgi.loc[main_glac_rgi.index.values[nglac], "RGIId"], + ) + # Select subsets of data - glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[nglac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) - - gdir = single_flowline_glacier_directory_with_calving(glacier_str, - logging_level='CRITICAL', - reset=reset_gdir, - facorrected=False - ) + glacier_rgi_table = main_glac_rgi.loc[ + main_glac_rgi.index.values[nglac], : + ] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + + gdir = single_flowline_glacier_directory_with_calving( + glacier_str, + logging_level="CRITICAL", + reset=reset_gdir, + facorrected=False, + ) gdir.is_tidewater = True - cfg.PARAMS['use_kcalving_for_inversion'] = True - cfg.PARAMS['use_kcalving_for_run'] = True + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True try: - fls = gdir.read_pickle('inversion_flowlines') + fls = gdir.read_pickle("inversion_flowlines") glacier_area = fls[0].widths_m * fls[0].dx_meter - debris.debris_binned(gdir, fl_str='inversion_flowlines', ignore_debris=True) + debris.debris_binned( + gdir, fl_str="inversion_flowlines", ignore_debris=True + ) except: fls = None - + # Add climate data to glacier directory - gdir.historical_climate = {'elev': gcm_elev[nglac], - 'temp': gcm_temp[nglac,:], - 'tempstd': gcm_tempstd[nglac,:], - 'prec': gcm_prec[nglac,:], - 'lr': gcm_lr[nglac,:]} + gdir.historical_climate = { + "elev": gcm_elev[nglac], + "temp": gcm_temp[nglac, :], + "tempstd": gcm_tempstd[nglac, :], + "prec": gcm_prec[nglac, :], + "lr": gcm_lr[nglac, :], + } gdir.dates_table = dates_table - + # ----- Invert ice thickness and run simulation ------ if (fls is not None) and (glacier_area.sum() > 0): - # ----- Model parameters ----- # Use the calibrated model parameters (although they were calibrated without accounting for calving) if args.prms_from_glac_cal: - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) if not os.path.exists(modelprms_fp + modelprms_fn): # try using regional priors args.prms_from_reg_priors = True break - with open(modelprms_fp + modelprms_fn, 'r') as f: + with open(modelprms_fp + modelprms_fn, "r") as f: modelprms_dict = json.load(f) - modelprms_em = modelprms_dict['emulator'] - kp_value = modelprms_em['kp'][0] - tbias_value = modelprms_em['tbias'][0] + modelprms_em = modelprms_dict["emulator"] + kp_value = modelprms_em["kp"][0] + tbias_value = modelprms_em["tbias"][0] # Use most likely parameters from initial calibration to force the mass balance gradient for the inversion elif args.prms_from_reg_priors: - if pygem_prms['calib']['priors_reg_fn'] is not None: + if pygem_prms["calib"]["priors_reg_fn"] is not None: # Load priors - priors_df = pd.read_csv(pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn']) - priors_idx = np.where((priors_df.O1Region == glacier_rgi_table['O1Region']) & - (priors_df.O2Region == glacier_rgi_table['O2Region']))[0][0] - kp_value = priors_df.loc[priors_idx,'kp_med'] - tbias_value = priors_df.loc[priors_idx,'tbias_med'] + priors_df = pd.read_csv( + pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"] + ) + priors_idx = np.where( + (priors_df.O1Region == glacier_rgi_table["O1Region"]) + & (priors_df.O2Region == glacier_rgi_table["O2Region"]) + )[0][0] + kp_value = priors_df.loc[priors_idx, "kp_med"] + tbias_value = priors_df.loc[priors_idx, "tbias_med"] - # Set model parameters - modelprms = {'kp': kp_value, - 'tbias': tbias_value, - 'ddfsnow': pygem_prms['sim']['params']['ddfsnow'], - 'ddfice': pygem_prms['sim']['params']['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'], - 'tsnow_threshold': pygem_prms['sim']['params']['tsnow_threshold'], - 'precgrad': pygem_prms['sim']['params']['precgrad']} - + modelprms = { + "kp": kp_value, + "tbias": tbias_value, + "ddfsnow": pygem_prms["sim"]["params"]["ddfsnow"], + "ddfice": pygem_prms["sim"]["params"]["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"], + "tsnow_threshold": pygem_prms["sim"]["params"][ + "tsnow_threshold" + ], + "precgrad": pygem_prms["sim"]["params"]["precgrad"], + } + # Calving and dynamic parameters - cfg.PARAMS['calving_k'] = calving_k - cfg.PARAMS['inversion_calving_k'] = cfg.PARAMS['calving_k'] - - if pygem_prms['sim']['oggm_dynamics']['use_reg_glena']: - glena_df = pd.read_csv(pygem_prms['root'] + pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']) - glena_idx = np.where(glena_df.O1Region == glacier_rgi_table.O1Region)[0][0] - glen_a_multiplier = glena_df.loc[glena_idx,'glens_a_multiplier'] - fs = glena_df.loc[glena_idx,'fs'] + cfg.PARAMS["calving_k"] = calving_k + cfg.PARAMS["inversion_calving_k"] = cfg.PARAMS["calving_k"] + + if pygem_prms["sim"]["oggm_dynamics"]["use_reg_glena"]: + glena_df = pd.read_csv( + pygem_prms["root"] + + pygem_prms["sim"]["oggm_dynamics"]["glena_reg_relpath"] + ) + glena_idx = np.where( + glena_df.O1Region == glacier_rgi_table.O1Region + )[0][0] + glen_a_multiplier = glena_df.loc[ + glena_idx, "glens_a_multiplier" + ] + fs = glena_df.loc[glena_idx, "fs"] else: - fs = pygem_prms['sim']['oggm_dynamics']['fs'] - glen_a_multiplier = pygem_prms['sim']['oggm_dynamics']['glen_a_multiplier'] - + fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] + glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ + "glen_a_multiplier" + ] + # CFL number (may use different values for calving to prevent errors) - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number'] + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number" + ] else: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number_calving'] - + cfg.PARAMS["cfl_number"] = pygem_prms["sim"]["oggm_dynamics"][ + "cfl_number_calving" + ] + # ----- Mass balance model for ice thickness inversion using OGGM ----- - mbmod_inv = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=False, - inversion_filter=False) + mbmod_inv = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=False, + inversion_filter=False, + ) # ----- CALVING ----- # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' - mb_years=np.arange(nyears) - + assert True == False, "Adjust nyears for non-monthly timestep" + mb_years = np.arange(nyears) + # Perform inversion # - find_inversion_calving_from_any_mb will do the inversion with calving, but if it fails # then it will do the inversion assuming land-terminating if invert_standard: - apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears)) + apparent_mb_from_any_mb( + gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears) + ) tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + tasks.mass_conservation_inversion( + gdir, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + ) else: - tasks.find_inversion_calving_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=mb_years, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) - + tasks.find_inversion_calving_from_any_mb( + gdir, + mb_model=mbmod_inv, + mb_years=mb_years, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + ) + # ------ MODEL WITH EVOLVING AREA ------ - tasks.init_present_time_glacier(gdir) # adds bins below - debris.debris_binned(gdir, fl_str='model_flowlines') # add debris enhancement factors to flowlines - nfls = gdir.read_pickle('model_flowlines') + tasks.init_present_time_glacier(gdir) # adds bins below + debris.debris_binned( + gdir, fl_str="model_flowlines" + ) # add debris enhancement factors to flowlines + nfls = gdir.read_pickle("model_flowlines") # Mass balance model - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=nfls, option_areaconstant=True) + mbmod = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=nfls, + option_areaconstant=True, + ) # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle('inversion_input')[-1] - th = cls['hgt'][-1] - vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] + cls = gdir.read_pickle("inversion_input")[-1] + th = cls["hgt"][-1] + vmin, vmax = cfg.PARAMS["free_board_marine_terminating"] water_level = utils.clip_scalar(0, th - vmax, th - vmin) - - ev_model = FluxBasedModel(nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + + ev_model = FluxBasedModel( + nfls, + y0=0, + mb_model=mbmod, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) try: diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual[-1] = diag.volume_m3[-1] + ev_model.mb_model.glac_wide_volume_annual[-1] = diag.volume_m3[ + -1 + ] ev_model.mb_model.glac_wide_area_annual[-1] = diag.area_m2[-1] - + # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Glacier-wide frontal ablation (m3 w.e.) # - note: diag.calving_m3 is cumulative calving -# if debug: -# print('\n\ndiag.calving_m3:', diag.calving_m3.values) -# print('calving_m3_since_y0:', ev_model.calving_m3_since_y0) - calving_m3_annual = ((diag.calving_m3.values[1:] - diag.calving_m3.values[0:-1]) * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + # if debug: + # print('\n\ndiag.calving_m3:', diag.calving_m3.values) + # print('calving_m3_since_y0:', ev_model.calving_m3_since_y0) + calving_m3_annual = ( + ( + diag.calving_m3.values[1:] + - diag.calving_m3.values[0:-1] + ) + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) for n in np.arange(calving_m3_annual.shape[0]): - ev_model.mb_model.glac_wide_frontalablation[12*n+11] = calving_m3_annual[n] + ev_model.mb_model.glac_wide_frontalablation[ + 12 * n + 11 + ] = calving_m3_annual[n] # Glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) - -# if debug: -# print('avg calving_m3:', calving_m3_annual.sum() / nyears) -# print('avg frontal ablation [Gta]:', -# np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) -# print('avg frontal ablation [Gta]:', -# np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) - + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) + + # if debug: + # print('avg calving_m3:', calving_m3_annual.sum() / nyears) + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward['calving_flux'] = calving_m3_annual.sum() / nyears / 1e9 + out_calving_forward["calving_flux"] = ( + calving_m3_annual.sum() / nyears / 1e9 + ) # calving flux (Gt/yr) - calving_flux_Gta = out_calving_forward['calving_flux'] * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] - + calving_flux_Gta = ( + out_calving_forward["calving_flux"] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) + # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward['calving_front_thick'] = thick[last_idx] - + out_calving_forward["calving_front_thick"] = thick[ + last_idx + ] + # Record in dataframe - output_df.loc[nglac,'calving_flux_Gta'] = calving_flux_Gta - output_df.loc[nglac,'calving_thick'] = out_calving_forward['calving_front_thick'] - output_df.loc[nglac,'no_errors'] = 1 - output_df.loc[nglac,'oggm_dynamics'] = 1 - - if args.verbose or debug: - print('OGGM dynamics, calving_k:', np.round(calving_k,4), 'glen_a:', np.round(glen_a_multiplier,2)) - print(' calving front thickness [m]:', np.round(out_calving_forward['calving_front_thick'],1)) - print(' calving flux model [Gt/yr]:', np.round(calving_flux_Gta,5)) - + output_df.loc[nglac, "calving_flux_Gta"] = calving_flux_Gta + output_df.loc[nglac, "calving_thick"] = ( + out_calving_forward["calving_front_thick"] + ) + output_df.loc[nglac, "no_errors"] = 1 + output_df.loc[nglac, "oggm_dynamics"] = 1 + + if args.verbose or debug: + print( + "OGGM dynamics, calving_k:", + np.round(calving_k, 4), + "glen_a:", + np.round(glen_a_multiplier, 2), + ) + print( + " calving front thickness [m]:", + np.round( + out_calving_forward["calving_front_thick"], 1 + ), + ) + print( + " calving flux model [Gt/yr]:", + np.round(calving_flux_Gta, 5), + ) + except: if gdir.is_tidewater: if args.verbose: - print('OGGM dynamics failed, using mass redistribution curves') - # Mass redistribution curves glacier dynamics model + print( + "OGGM dynamics failed, using mass redistribution curves" + ) + # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) _, diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values - + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) + # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ( + ev_model.mb_model.glac_bin_frontalablation.sum(0) + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) - - calving_flux_km3a = (ev_model.mb_model.glac_wide_frontalablation.sum() * pygem_prms['constants']['density_water'] / - pygem_prms['constants']['density_ice'] / nyears / 1e9) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) + + calving_flux_km3a = ( + ev_model.mb_model.glac_wide_frontalablation.sum() + * pygem_prms["constants"]["density_water"] + / pygem_prms["constants"]["density_ice"] + / nyears + / 1e9 + ) + + # if debug: + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) + # print('avg frontal ablation [Gta]:', + # np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) -# if debug: -# print('avg frontal ablation [Gta]:', -# np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) -# print('avg frontal ablation [Gta]:', -# np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) - # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) - out_calving_forward['calving_flux'] = calving_flux_km3a + out_calving_forward["calving_flux"] = calving_flux_km3a # calving flux (Gt/yr) - calving_flux_Gta = out_calving_forward['calving_flux'] * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] + calving_flux_Gta = ( + out_calving_forward["calving_flux"] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # calving front thickness at start of simulation thick = nfls[0].thick last_idx = np.nonzero(thick)[0][-1] - out_calving_forward['calving_front_thick'] = thick[last_idx] - + out_calving_forward["calving_front_thick"] = thick[ + last_idx + ] + # Record in dataframe - output_df.loc[nglac,'calving_flux_Gta'] = calving_flux_Gta - output_df.loc[nglac,'calving_thick'] = out_calving_forward['calving_front_thick'] - output_df.loc[nglac,'no_errors'] = 1 - - if args.verbose or debug: - print('Mass Redistribution curve, calving_k:', np.round(calving_k,1), 'glen_a:', np.round(glen_a_multiplier,2)) - print(' calving front thickness [m]:', np.round(out_calving_forward['calving_front_thick'],0)) - print(' calving flux model [Gt/yr]:', np.round(calving_flux_Gta,5)) + output_df.loc[nglac, "calving_flux_Gta"] = calving_flux_Gta + output_df.loc[nglac, "calving_thick"] = ( + out_calving_forward["calving_front_thick"] + ) + output_df.loc[nglac, "no_errors"] = 1 + + if args.verbose or debug: + print( + "Mass Redistribution curve, calving_k:", + np.round(calving_k, 1), + "glen_a:", + np.round(glen_a_multiplier, 2), + ) + print( + " calving front thickness [m]:", + np.round( + out_calving_forward["calving_front_thick"], 0 + ), + ) + print( + " calving flux model [Gt/yr]:", + np.round(calving_flux_Gta, 5), + ) if calc_mb_geo_correction: - # Mass balance correction from mass loss above sea level due to calving retreat + # Mass balance correction from mass loss above sea level due to calving retreat # (i.e., what the geodetic signal should see) last_yr_idx = np.where(mbmod.glac_wide_area_annual > 0)[0][-1] - if last_yr_idx == mbmod.glac_bin_area_annual.shape[1]-1: + if last_yr_idx == mbmod.glac_bin_area_annual.shape[1] - 1: last_yr_idx = -2 - bin_last_idx = np.where(mbmod.glac_bin_area_annual[:,last_yr_idx] > 0)[0][-1] - bin_area_lost = mbmod.glac_bin_area_annual[bin_last_idx:,0] - mbmod.glac_bin_area_annual[bin_last_idx:,-2] + bin_last_idx = np.where( + mbmod.glac_bin_area_annual[:, last_yr_idx] > 0 + )[0][-1] + bin_area_lost = ( + mbmod.glac_bin_area_annual[bin_last_idx:, 0] + - mbmod.glac_bin_area_annual[bin_last_idx:, -2] + ) height_asl = mbmod.heights - water_level - height_asl[mbmod.heights<0] = 0 - mb_mwea_fa_asl_geo_correction = ((bin_area_lost * height_asl[bin_last_idx:]).sum() / - mbmod.glac_wide_area_annual[0] * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] / nyears) - mb_mwea_fa_asl_geo_correction_max = 0.3*gta_to_mwea(calving_flux_Gta, glacier_rgi_table['Area']*1e6) - if mb_mwea_fa_asl_geo_correction > mb_mwea_fa_asl_geo_correction_max: - mb_mwea_fa_asl_geo_correction = mb_mwea_fa_asl_geo_correction_max - + height_asl[mbmod.heights < 0] = 0 + mb_mwea_fa_asl_geo_correction = ( + (bin_area_lost * height_asl[bin_last_idx:]).sum() + / mbmod.glac_wide_area_annual[0] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + / nyears + ) + mb_mwea_fa_asl_geo_correction_max = 0.3 * gta_to_mwea( + calving_flux_Gta, glacier_rgi_table["Area"] * 1e6 + ) + if ( + mb_mwea_fa_asl_geo_correction + > mb_mwea_fa_asl_geo_correction_max + ): + mb_mwea_fa_asl_geo_correction = ( + mb_mwea_fa_asl_geo_correction_max + ) + # Below sea-level correction due to calving that geodetic mass balance doesn't see -# print('test:', mbmod.glac_bin_icethickness_annual.shape, height_asl.shape, bin_area_lost.shape) -# height_bsl = mbmod.glac_bin_icethickness_annual - height_asl - + # print('test:', mbmod.glac_bin_icethickness_annual.shape, height_asl.shape, bin_area_lost.shape) + # height_bsl = mbmod.glac_bin_icethickness_annual - height_asl + # Area for retreat if args.verbose or debug: -# print('\n----- area calcs -----') -# print(mbmod.glac_bin_area_annual[bin_last_idx:,0]) -# print(mbmod.glac_bin_icethickness_annual[bin_last_idx:,0]) -# print(mbmod.glac_bin_area_annual[bin_last_idx:,-2]) -# print(mbmod.glac_bin_icethickness_annual[bin_last_idx:,-2]) -# print(mbmod.heights.shape, mbmod.heights[bin_last_idx:]) - print(' mb_mwea_fa_asl_geo_correction:', np.round(mb_mwea_fa_asl_geo_correction,2)) -# print(' mb_mwea_fa_asl_geo_correction:', mb_mwea_fa_asl_geo_correction) -# print(glacier_rgi_table, glacier_rgi_table['Area']) - - - output_df.loc[nglac,'mb_mwea_fa_asl_lost'] = mb_mwea_fa_asl_geo_correction + # print('\n----- area calcs -----') + # print(mbmod.glac_bin_area_annual[bin_last_idx:,0]) + # print(mbmod.glac_bin_icethickness_annual[bin_last_idx:,0]) + # print(mbmod.glac_bin_area_annual[bin_last_idx:,-2]) + # print(mbmod.glac_bin_icethickness_annual[bin_last_idx:,-2]) + # print(mbmod.heights.shape, mbmod.heights[bin_last_idx:]) + print( + " mb_mwea_fa_asl_geo_correction:", + np.round(mb_mwea_fa_asl_geo_correction, 2), + ) + # print(' mb_mwea_fa_asl_geo_correction:', mb_mwea_fa_asl_geo_correction) + # print(glacier_rgi_table, glacier_rgi_table['Area']) + + output_df.loc[nglac, "mb_mwea_fa_asl_lost"] = ( + mb_mwea_fa_asl_geo_correction + ) if out_calving_forward is None: - output_df.loc[nglac,['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors']] = ( - np.nan, np.nan, np.nan, 0) - + output_df.loc[ + nglac, + [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + ], + ] = (np.nan, np.nan, np.nan, 0) + # Remove glaciers that failed to run if fa_glac_data_reg is None: reg_calving_gta_obs_good = None - output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) + output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() elif ignore_nan: - output_df_good = output_df.dropna(axis=0, subset=['calving_flux_Gta']) + output_df_good = output_df.dropna(axis=0, subset=["calving_flux_Gta"]) reg_calving_gta_mod_good = output_df_good.calving_flux_Gta.sum() rgiids_data = list(fa_glac_data_reg.RGIId.values) rgiids_mod = list(output_df_good.RGIId.values) fa_data_idx = [rgiids_data.index(x) for x in rgiids_mod] - reg_calving_gta_obs_good = fa_glac_data_reg.loc[fa_data_idx,frontal_ablation_Gta_cn].sum() + reg_calving_gta_obs_good = fa_glac_data_reg.loc[ + fa_data_idx, frontal_ablation_Gta_cn + ].sum() else: reg_calving_gta_mod_good = output_df.calving_flux_Gta.sum() - reg_calving_gta_obs_good = fa_glac_data_reg[frontal_ablation_Gta_cn].sum() - + reg_calving_gta_obs_good = fa_glac_data_reg[ + frontal_ablation_Gta_cn + ].sum() + return output_df, reg_calving_gta_mod_good, reg_calving_gta_obs_good -def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_bndhigh, fa_glac_data_ind, - calving_k_step=calving_k_step_gl, - ignore_nan=False, calc_mb_geo_correction=False, nround_max=5): +def run_opt_fa( + main_glac_rgi_ind, + args, + calving_k, + calving_k_bndlow, + calving_k_bndhigh, + fa_glac_data_ind, + calving_k_step=calving_k_step_gl, + ignore_nan=False, + calc_mb_geo_correction=False, + nround_max=5, +): """ Run the optimization of the frontal ablation for an individual glacier """ - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) - + output_df, reg_calving_gta_mod, reg_calving_gta_obs = reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + calving_k_bndlow_hold = np.copy(calving_k_bndlow) - + if args.verbose: - print(' fa_model_init [Gt/yr] :', np.round(reg_calving_gta_mod,4)) - + print(" fa_model_init [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + # ----- Rough optimizer using calving_k_step to loop through parameters within bounds ------ calving_k_last = calving_k reg_calving_gta_mod_last = reg_calving_gta_mod.copy() - + if reg_calving_gta_mod < reg_calving_gta_obs: - if args.verbose: - print('\nincrease calving_k') - -# print('reg_calving_gta_mod:', reg_calving_gta_mod) -# print('reg_calving_gta_obs:', reg_calving_gta_obs) -# print('calving_k:', calving_k) -# print('calving_k_bndhigh:', calving_k_bndhigh) -# print('calving_k_bndlow:', calving_k_bndlow) - - while ((reg_calving_gta_mod < reg_calving_gta_obs and np.round(calving_k,2) < calving_k_bndhigh - and calving_k > calving_k_bndlow - and (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement - and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold))): + print("\nincrease calving_k") + + # print('reg_calving_gta_mod:', reg_calving_gta_mod) + # print('reg_calving_gta_obs:', reg_calving_gta_obs) + # print('calving_k:', calving_k) + # print('calving_k_bndhigh:', calving_k_bndhigh) + # print('calving_k_bndlow:', calving_k_bndlow) + + while ( + reg_calving_gta_mod < reg_calving_gta_obs + and np.round(calving_k, 2) < calving_k_bndhigh + and calving_k > calving_k_bndlow + and ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + > fa_threshold + ) + ): # Record previous output calving_k_last = np.copy(calving_k) reg_calving_gta_mod_last = reg_calving_gta_mod.copy() - + if args.verbose: - print(' increase calving_k_step:', calving_k_step) - + print(" increase calving_k_step:", calving_k_step) + # Increase calving k calving_k += calving_k_step - + if calving_k > calving_k_bndhigh: calving_k = calving_k_bndhigh - + # Re-run the regional frontal ablation estimates output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + ) if args.verbose: - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod,4)) - + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + # Set lower bound calving_k_bndlow = calving_k_last reg_calving_gta_mod_bndlow = reg_calving_gta_mod_last # Set upper bound calving_k_bndhigh = calving_k reg_calving_gta_mod_bndhigh = reg_calving_gta_mod - + else: - if args.verbose: - print('\ndecrease calving_k') - print('-----') - print('reg_calving_gta_mod:', reg_calving_gta_mod) - print('reg_calving_gta_obs:', reg_calving_gta_obs) - print('calving_k:', calving_k) - print('calving_k_bndlow:', calving_k_bndlow) - print('fa perc:', (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs)) - print('fa thres:', np.abs(reg_calving_gta_mod - reg_calving_gta_obs)) - print('good values:', output_df.loc[0,'calving_flux_Gta']) - - while ((reg_calving_gta_mod > reg_calving_gta_obs and calving_k > calving_k_bndlow - and (np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement - and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold)) - and not np.isnan(output_df.loc[0,'calving_flux_Gta'])): + print("\ndecrease calving_k") + print("-----") + print("reg_calving_gta_mod:", reg_calving_gta_mod) + print("reg_calving_gta_obs:", reg_calving_gta_obs) + print("calving_k:", calving_k) + print("calving_k_bndlow:", calving_k_bndlow) + print( + "fa perc:", + ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + ), + ) + print( + "fa thres:", np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + ) + print("good values:", output_df.loc[0, "calving_flux_Gta"]) + + while ( + reg_calving_gta_mod > reg_calving_gta_obs + and calving_k > calving_k_bndlow + and ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + > fa_threshold + ) + ) and not np.isnan(output_df.loc[0, "calving_flux_Gta"]): # Record previous output calving_k_last = np.copy(calving_k) reg_calving_gta_mod_last = reg_calving_gta_mod.copy() - + # Decrease calving k calving_k -= calving_k_step - + if calving_k < calving_k_bndlow: calving_k = calving_k_bndlow - + # Re-run the regional frontal ablation estimates output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + ) if args.verbose: - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod,4)) + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) # Set lower bound calving_k_bndlow = calving_k @@ -538,76 +794,120 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b # Set upper bound calving_k_bndhigh = calving_k_last reg_calving_gta_mod_bndhigh = reg_calving_gta_mod_last - - if args.verbose: - print('bnds:', calving_k_bndlow, calving_k_bndhigh) - print('bnds gt/yr:', reg_calving_gta_mod_bndlow, reg_calving_gta_mod_bndhigh) + + if args.verbose: + print("bnds:", calving_k_bndlow, calving_k_bndhigh) + print( + "bnds gt/yr:", + reg_calving_gta_mod_bndlow, + reg_calving_gta_mod_bndhigh, + ) # ----- Optimize further using mid-point "bisection" method ----- # Consider replacing with scipy.optimize.brent - if not np.isnan(output_df.loc[0,'calving_flux_Gta']): - + if not np.isnan(output_df.loc[0, "calving_flux_Gta"]): # Check if upper bound causes good fit - if (np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) / reg_calving_gta_obs < perc_threshold_agreement - or np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) < fa_threshold): - + if ( + np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) + / reg_calving_gta_obs + < perc_threshold_agreement + or np.abs(reg_calving_gta_mod_bndhigh - reg_calving_gta_obs) + < fa_threshold + ): # If so, calving_k equals upper bound and re-run to get proper estimates for output calving_k = calving_k_bndhigh - + # Re-run the regional frontal ablation estimates output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + ) if args.verbose: - print('upper bound:') - print(' calving_k:', np.round(calving_k,4)) - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod,4)) - + print("upper bound:") + print(" calving_k:", np.round(calving_k, 4)) + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + # Check if lower bound causes good fit - elif (np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs < perc_threshold_agreement - or np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) < fa_threshold): - + elif ( + np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) + / reg_calving_gta_obs + < perc_threshold_agreement + or np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) + < fa_threshold + ): calving_k = calving_k_bndlow # Re-run the regional frontal ablation estimates output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + ) if args.verbose: - print('lower bound:') - print(' calving_k:', np.round(calving_k,4)) - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod,4)) - + print("lower bound:") + print(" calving_k:", np.round(calving_k, 4)) + print(" fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4)) + print(" fa_model [Gt/yr] :", np.round(reg_calving_gta_mod, 4)) + else: # Calibrate between limited range nround = 0 # Set initial calving_k calving_k = (calving_k_bndlow + calving_k_bndhigh) / 2 - -# print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) -# print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) -# print('calving_k_bndlow:', calving_k_bndlow) -# print('nround:', nround, 'nround_max:', nround_max) -# print('calving_k:', calving_k, 'calving_k_bndlow_set:', calving_k_bndlow_hold) - - while ((np.abs(reg_calving_gta_mod - reg_calving_gta_obs) / reg_calving_gta_obs > perc_threshold_agreement and - np.abs(reg_calving_gta_mod - reg_calving_gta_obs) > fa_threshold) and nround <= nround_max - and calving_k > calving_k_bndlow_hold): - + + # print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) + # print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) + # print('calving_k_bndlow:', calving_k_bndlow) + # print('nround:', nround, 'nround_max:', nround_max) + # print('calving_k:', calving_k, 'calving_k_bndlow_set:', calving_k_bndlow_hold) + + while ( + ( + np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + / reg_calving_gta_obs + > perc_threshold_agreement + and np.abs(reg_calving_gta_mod - reg_calving_gta_obs) + > fa_threshold + ) + and nround <= nround_max + and calving_k > calving_k_bndlow_hold + ): nround += 1 if args.verbose: - print('\nRound', nround) + print("\nRound", nround) # Update calving_k using midpoint calving_k = (calving_k_bndlow + calving_k_bndhigh) / 2 output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=calc_mb_geo_correction)) + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=calc_mb_geo_correction, + ) + ) if args.verbose: - print(' calving_k:', np.round(calving_k,4)) - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model [Gt/yr] :', np.round(reg_calving_gta_mod,4)) - + print(" calving_k:", np.round(calving_k, 4)) + print( + " fa_data [Gt/yr]:", np.round(reg_calving_gta_obs, 4) + ) + print( + " fa_model [Gt/yr] :", + np.round(reg_calving_gta_mod, 4), + ) + # Update bounds if reg_calving_gta_mod < reg_calving_gta_obs: # Update lower bound @@ -617,73 +917,98 @@ def run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_b # Update upper bound reg_calving_gta_mod_bndhigh = reg_calving_gta_mod calving_k_bndhigh = np.copy(calving_k) - -# if debug: -# print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) -# print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) -# print('calving_k_bndlow:', calving_k_bndlow) -# print('nround:', nround, 'nround_max:', nround_max) -# print(' calving_k:', calving_k) - + + # if debug: + # print('fa_perc:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs) / reg_calving_gta_obs) + # print('fa_dif:', np.abs(reg_calving_gta_mod_bndlow - reg_calving_gta_obs)) + # print('calving_k_bndlow:', calving_k_bndlow) + # print('nround:', nround, 'nround_max:', nround_max) + # print(' calving_k:', calving_k) + if calving_k < calving_k_bndlow: calving_k = calving_k_bndlow - + return output_df, calving_k -def merge_data(frontalablation_fp='', overwrite=False, verbose=False): - frontalablation_fn1 = 'Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv' - frontalablation_fn2 = 'frontalablation_glacier_data_minowa2021.csv' - frontalablation_fn3 = 'frontalablation_glacier_data_osmanoglu.csv' - out_fn = frontalablation_fn1.replace('.csv','-w17_19.csv') +def merge_data(frontalablation_fp="", overwrite=False, verbose=False): + frontalablation_fn1 = "Northern_hemisphere_calving_flux_Kochtitzky_et_al_for_David_Rounce_with_melt_v14-wromainMB.csv" + frontalablation_fn2 = "frontalablation_glacier_data_minowa2021.csv" + frontalablation_fn3 = "frontalablation_glacier_data_osmanoglu.csv" + out_fn = frontalablation_fn1.replace(".csv", "-w17_19.csv") if os.path.isfile(frontalablation_fp + out_fn) and not overwrite: if verbose: - print(f'Combined frontal ablation dataset already exists, pass `-o` to overwrite: {frontalablation_fp+out_fn}') + print( + f"Combined frontal ablation dataset already exists, pass `-o` to overwrite: {frontalablation_fp + out_fn}" + ) return out_fn - - fa_glac_data_cns_subset = ['RGIId','fa_gta_obs', 'fa_gta_obs_unc', - 'Romain_gta_mbtot', 'Romain_gta_mbclim','Romain_mwea_mbtot', 'Romain_mwea_mbclim', - 'thick_measured_yn', 'start_date', 'end_date', 'source'] - + + fa_glac_data_cns_subset = [ + "RGIId", + "fa_gta_obs", + "fa_gta_obs_unc", + "Romain_gta_mbtot", + "Romain_gta_mbclim", + "Romain_mwea_mbtot", + "Romain_mwea_mbclim", + "thick_measured_yn", + "start_date", + "end_date", + "source", + ] + # Load datasets fa_glac_data1 = pd.read_csv(frontalablation_fp + frontalablation_fn1) fa_glac_data2 = pd.read_csv(frontalablation_fp + frontalablation_fn2) fa_glac_data3 = pd.read_csv(frontalablation_fp + frontalablation_fn3) # Kochtitzky data - fa_data_df1 = pd.DataFrame(np.zeros((fa_glac_data1.shape[0],len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df1['RGIId'] = fa_glac_data1['RGIId'] - fa_data_df1['fa_gta_obs'] = fa_glac_data1['Frontal_ablation_2000_to_2020_gt_per_yr_mean'] - fa_data_df1['fa_gta_obs_unc'] = fa_glac_data1['Frontal_ablation_2000_to_2020_gt_per_yr_mean_err'] - fa_data_df1['Romain_gta_mbtot'] = fa_glac_data1['Romain_gta_mbtot'] - fa_data_df1['Romain_gta_mbclim'] = fa_glac_data1['Romain_gta_mbclim'] - fa_data_df1['Romain_mwea_mbtot'] = fa_glac_data1['Romain_mwea_mbtot'] - fa_data_df1['Romain_mwea_mbclim'] = fa_glac_data1['Romain_mwea_mbclim'] - fa_data_df1['thick_measured_yn'] = fa_glac_data1['thick_measured_yn'] - fa_data_df1['start_date'] = '20009999' - fa_data_df1['end_date'] = '20199999' - fa_data_df1['source'] = 'Kochtitzky et al.' + fa_data_df1 = pd.DataFrame( + np.zeros((fa_glac_data1.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df1["RGIId"] = fa_glac_data1["RGIId"] + fa_data_df1["fa_gta_obs"] = fa_glac_data1[ + "Frontal_ablation_2000_to_2020_gt_per_yr_mean" + ] + fa_data_df1["fa_gta_obs_unc"] = fa_glac_data1[ + "Frontal_ablation_2000_to_2020_gt_per_yr_mean_err" + ] + fa_data_df1["Romain_gta_mbtot"] = fa_glac_data1["Romain_gta_mbtot"] + fa_data_df1["Romain_gta_mbclim"] = fa_glac_data1["Romain_gta_mbclim"] + fa_data_df1["Romain_mwea_mbtot"] = fa_glac_data1["Romain_mwea_mbtot"] + fa_data_df1["Romain_mwea_mbclim"] = fa_glac_data1["Romain_mwea_mbclim"] + fa_data_df1["thick_measured_yn"] = fa_glac_data1["thick_measured_yn"] + fa_data_df1["start_date"] = "20009999" + fa_data_df1["end_date"] = "20199999" + fa_data_df1["source"] = "Kochtitzky et al." # Minowa data - fa_data_df2 = pd.DataFrame(np.zeros((fa_glac_data2.shape[0], len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df2['RGIId'] = fa_glac_data2['RGIId'] - fa_data_df2['fa_gta_obs'] = fa_glac_data2['frontal_ablation_Gta'] - fa_data_df2['fa_gta_obs_unc'] = fa_glac_data2['frontal_ablation_unc_Gta'] - fa_data_df2['start_date'] = fa_glac_data2['start_date'] - fa_data_df2['end_date'] = fa_glac_data2['end_date'] - fa_data_df2['source'] = fa_glac_data2['Source'] - fa_data_df2.sort_values('RGIId', inplace=True) - + fa_data_df2 = pd.DataFrame( + np.zeros((fa_glac_data2.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df2["RGIId"] = fa_glac_data2["RGIId"] + fa_data_df2["fa_gta_obs"] = fa_glac_data2["frontal_ablation_Gta"] + fa_data_df2["fa_gta_obs_unc"] = fa_glac_data2["frontal_ablation_unc_Gta"] + fa_data_df2["start_date"] = fa_glac_data2["start_date"] + fa_data_df2["end_date"] = fa_glac_data2["end_date"] + fa_data_df2["source"] = fa_glac_data2["Source"] + fa_data_df2.sort_values("RGIId", inplace=True) + # Osmanoglu data - fa_data_df3 = pd.DataFrame(np.zeros((fa_glac_data3.shape[0],len(fa_glac_data_cns_subset))), columns=fa_glac_data_cns_subset) - fa_data_df3['RGIId'] = fa_glac_data3['RGIId'] - fa_data_df3['fa_gta_obs'] = fa_glac_data3['frontal_ablation_Gta'] - fa_data_df3['fa_gta_obs_unc'] = fa_glac_data3['frontal_ablation_unc_Gta'] - fa_data_df3['start_date'] = fa_glac_data3['start_date'] - fa_data_df3['end_date'] = fa_glac_data3['end_date'] - fa_data_df3['source'] = fa_glac_data3['Source'] - fa_data_df3.sort_values('RGIId', inplace=True) - + fa_data_df3 = pd.DataFrame( + np.zeros((fa_glac_data3.shape[0], len(fa_glac_data_cns_subset))), + columns=fa_glac_data_cns_subset, + ) + fa_data_df3["RGIId"] = fa_glac_data3["RGIId"] + fa_data_df3["fa_gta_obs"] = fa_glac_data3["frontal_ablation_Gta"] + fa_data_df3["fa_gta_obs_unc"] = fa_glac_data3["frontal_ablation_unc_Gta"] + fa_data_df3["start_date"] = fa_glac_data3["start_date"] + fa_data_df3["end_date"] = fa_glac_data3["end_date"] + fa_data_df3["source"] = fa_glac_data3["Source"] + fa_data_df3.sort_values("RGIId", inplace=True) + # Concatenate datasets dfs = [fa_data_df1, fa_data_df2, fa_data_df3] fa_data_df = pd.concat([df for df in dfs if not df.empty], axis=0) @@ -691,116 +1016,188 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): # Export frontal ablation data for Will fa_data_df.to_csv(frontalablation_fp + out_fn, index=False) if verbose: - print(f'Combined frontal ablation dataset exported: {frontalablation_fp+out_fn}') + print( + f"Combined frontal ablation dataset exported: {frontalablation_fp + out_fn}" + ) return out_fn -def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablation_fn='', output_fp='', hugonnet2021_fp=''): - verbose=args.verbose - overwrite=args.overwrite +def calib_ind_calving_k( + regions, + args=None, + frontalablation_fp="", + frontalablation_fn="", + output_fp="", + hugonnet2021_fp="", +): + verbose = args.verbose + overwrite = args.overwrite # Load calving glacier data fa_glac_data = pd.read_csv(frontalablation_fp + frontalablation_fn) mb_data = pd.read_csv(hugonnet2021_fp) - fa_glac_data['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in fa_glac_data.RGIId.values] - + fa_glac_data["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) for x in fa_glac_data.RGIId.values + ] + calving_k_bndhigh_set = np.copy(calving_k_bndhigh_gl) calving_k_bndlow_set = np.copy(calving_k_bndlow_gl) calving_k_step_set = np.copy(calving_k_step_gl) for reg in [regions]: # skip over any regions we don't have data for - if reg not in fa_glac_data['O1Region'].values.tolist(): + if reg not in fa_glac_data["O1Region"].values.tolist(): continue - output_fn = str(reg) + '-frontalablation_cal_ind.csv' + output_fn = str(reg) + "-frontalablation_cal_ind.csv" # Regional data - fa_glac_data_reg = fa_glac_data.loc[fa_glac_data['O1Region'] == reg, :].copy() + fa_glac_data_reg = fa_glac_data.loc[ + fa_glac_data["O1Region"] == reg, : + ].copy() fa_glac_data_reg.reset_index(inplace=True, drop=True) - - fa_glac_data_reg['glacno'] = '' - + + fa_glac_data_reg["glacno"] = "" + for nglac, rgiid in enumerate(fa_glac_data_reg.RGIId): # Avoid regional data and observations from multiple RGIIds (len==14) - if not fa_glac_data_reg.loc[nglac,'RGIId'] == 'all' and len(fa_glac_data_reg.loc[nglac,'RGIId']) == 14: - fa_glac_data_reg.loc[nglac,'glacno'] = rgiid[-8:]#(str(int(rgiid.split('-')[1].split('.')[0])) + '.' + - # rgiid.split('-')[1].split('.')[1]) - + if ( + not fa_glac_data_reg.loc[nglac, "RGIId"] == "all" + and len(fa_glac_data_reg.loc[nglac, "RGIId"]) == 14 + ): + fa_glac_data_reg.loc[nglac, "glacno"] = rgiid[ + -8: + ] # (str(int(rgiid.split('-')[1].split('.')[0])) + '.' + + # rgiid.split('-')[1].split('.')[1]) + # Drop observations that aren't of individual glaciers - fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=['glacno']) + fa_glac_data_reg = fa_glac_data_reg.dropna(axis=0, subset=["glacno"]) fa_glac_data_reg.reset_index(inplace=True, drop=True) reg_calving_gta_obs = fa_glac_data_reg[frontal_ablation_Gta_cn].sum() - + # Glacier numbers for model runs glacno_reg_wdata = sorted(list(fa_glac_data_reg.glacno.values)) - - main_glac_rgi_all = modelsetup.selectglaciersrgitable(glac_no=glacno_reg_wdata) + + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + glac_no=glacno_reg_wdata + ) # Tidewater glaciers - termtype_list = [1,5] - main_glac_rgi = main_glac_rgi_all.loc[main_glac_rgi_all['TermType'].isin(termtype_list)] + termtype_list = [1, 5] + main_glac_rgi = main_glac_rgi_all.loc[ + main_glac_rgi_all["TermType"].isin(termtype_list) + ] main_glac_rgi.reset_index(inplace=True, drop=True) - + # ----- QUALITY CONTROL USING MB_CLIM COMPARED TO REGIONAL MASS BALANCE ----- - mb_data['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in mb_data.rgiid.values] - mb_data_reg = mb_data.loc[mb_data['O1Region'] == reg, :] + mb_data["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) for x in mb_data.rgiid.values + ] + mb_data_reg = mb_data.loc[mb_data["O1Region"] == reg, :] mb_data_reg.reset_index(inplace=True) mb_clim_reg_avg = np.mean(mb_data_reg.mb_mwea) mb_clim_reg_std = np.std(mb_data_reg.mb_mwea) - mb_clim_reg_3std = mb_clim_reg_avg + 3*mb_clim_reg_std + mb_clim_reg_3std = mb_clim_reg_avg + 3 * mb_clim_reg_std mb_clim_reg_max = np.max(mb_data_reg.mb_mwea) - mb_clim_reg_3std_min = mb_clim_reg_avg - 3*mb_clim_reg_std + mb_clim_reg_3std_min = mb_clim_reg_avg - 3 * mb_clim_reg_std if verbose: - print('mb_clim_reg_avg:', np.round(mb_clim_reg_avg,2), '+/-', np.round(mb_clim_reg_std,2)) - print('mb_clim_3std (neg):', np.round(mb_clim_reg_3std_min,2)) - print('mb_clim_3std (pos):', np.round(mb_clim_reg_3std,2)) - print('mb_clim_min:', np.round(mb_data_reg.mb_mwea.min(),2)) - print('mb_clim_max:', np.round(mb_clim_reg_max,2)) - - if not os.path.exists(output_fp + output_fn) or overwrite: + print( + "mb_clim_reg_avg:", + np.round(mb_clim_reg_avg, 2), + "+/-", + np.round(mb_clim_reg_std, 2), + ) + print("mb_clim_3std (neg):", np.round(mb_clim_reg_3std_min, 2)) + print("mb_clim_3std (pos):", np.round(mb_clim_reg_3std, 2)) + print("mb_clim_min:", np.round(mb_data_reg.mb_mwea.min(), 2)) + print("mb_clim_max:", np.round(mb_clim_reg_max, 2)) - output_cns = ['RGIId', 'calving_k', 'calving_k_nmad', 'calving_thick', 'calving_flux_Gta', 'fa_gta_obs', 'fa_gta_obs_unc', 'fa_gta_max', - 'calving_flux_Gta_bndlow', 'calving_flux_Gta_bndhigh', 'no_errors', 'oggm_dynamics', - 'mb_clim_gta', 'mb_total_gta', 'mb_clim_mwea', 'mb_total_mwea'] - - output_df_all = pd.DataFrame(np.zeros((main_glac_rgi.shape[0],len(output_cns))), columns=output_cns) - output_df_all['RGIId'] = main_glac_rgi.RGIId - output_df_all['calving_k_nmad'] = 0. - - # Load observations - fa_obs_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_cn])) - fa_obs_unc_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg[frontal_ablation_Gta_unc_cn])) - # fa_glacname_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg.glacier_name)) + if not os.path.exists(output_fp + output_fn) or overwrite: + output_cns = [ + "RGIId", + "calving_k", + "calving_k_nmad", + "calving_thick", + "calving_flux_Gta", + "fa_gta_obs", + "fa_gta_obs_unc", + "fa_gta_max", + "calving_flux_Gta_bndlow", + "calving_flux_Gta_bndhigh", + "no_errors", + "oggm_dynamics", + "mb_clim_gta", + "mb_total_gta", + "mb_clim_mwea", + "mb_total_mwea", + ] + + output_df_all = pd.DataFrame( + np.zeros((main_glac_rgi.shape[0], len(output_cns))), + columns=output_cns, + ) + output_df_all["RGIId"] = main_glac_rgi.RGIId + output_df_all["calving_k_nmad"] = 0.0 + + # Load observations + fa_obs_dict = dict( + zip( + fa_glac_data_reg.RGIId, + fa_glac_data_reg[frontal_ablation_Gta_cn], + ) + ) + fa_obs_unc_dict = dict( + zip( + fa_glac_data_reg.RGIId, + fa_glac_data_reg[frontal_ablation_Gta_unc_cn], + ) + ) + # fa_glacname_dict = dict(zip(fa_glac_data_reg.RGIId, fa_glac_data_reg.glacier_name)) rgi_area_dict = dict(zip(main_glac_rgi.RGIId, main_glac_rgi.Area)) - - output_df_all['fa_gta_obs'] = output_df_all['RGIId'].map(fa_obs_dict) - output_df_all['fa_gta_obs_unc'] = output_df_all['RGIId'].map(fa_obs_unc_dict) - # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) - output_df_all['area_km2'] = output_df_all['RGIId'].map(rgi_area_dict) - + + output_df_all["fa_gta_obs"] = output_df_all["RGIId"].map( + fa_obs_dict + ) + output_df_all["fa_gta_obs_unc"] = output_df_all["RGIId"].map( + fa_obs_unc_dict + ) + # output_df_all['name'] = output_df_all['RGIId'].map(fa_glacname_dict) + output_df_all["area_km2"] = output_df_all["RGIId"].map( + rgi_area_dict + ) + # ----- LOAD DATA ON MB_CLIM CORRECTED FOR FRONTAL ABLATION ----- # use this to assess reasonableness of results and see if calving_k values affected fa_rgiids_list = list(fa_glac_data_reg.RGIId) - output_df_all['mb_total_gta_obs'] = np.nan - output_df_all['mb_clim_gta_obs'] = np.nan - output_df_all['mb_total_mwea_obs'] = np.nan - output_df_all['mb_clim_mwea_obs'] = np.nan -# output_df_all['thick_measured_yn'] = np.nan + output_df_all["mb_total_gta_obs"] = np.nan + output_df_all["mb_clim_gta_obs"] = np.nan + output_df_all["mb_total_mwea_obs"] = np.nan + output_df_all["mb_clim_mwea_obs"] = np.nan + # output_df_all['thick_measured_yn'] = np.nan for nglac, rgiid in enumerate(list(output_df_all.RGIId)): fa_idx = fa_rgiids_list.index(rgiid) - output_df_all.loc[nglac, 'mb_total_gta_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_gta_mbtot'] - output_df_all.loc[nglac, 'mb_clim_gta_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_gta_mbclim'] - output_df_all.loc[nglac, 'mb_total_mwea_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_mwea_mbtot'] - output_df_all.loc[nglac, 'mb_clim_mwea_obs'] = fa_glac_data_reg.loc[fa_idx, 'Romain_mwea_mbclim'] -# output_df_all.loc[nglac, 'thick_measured_yn'] = fa_glac_data_reg.loc[fa_idx, 'thick_measured_yn'] + output_df_all.loc[nglac, "mb_total_gta_obs"] = ( + fa_glac_data_reg.loc[fa_idx, "Romain_gta_mbtot"] + ) + output_df_all.loc[nglac, "mb_clim_gta_obs"] = ( + fa_glac_data_reg.loc[fa_idx, "Romain_gta_mbclim"] + ) + output_df_all.loc[nglac, "mb_total_mwea_obs"] = ( + fa_glac_data_reg.loc[fa_idx, "Romain_mwea_mbtot"] + ) + output_df_all.loc[nglac, "mb_clim_mwea_obs"] = ( + fa_glac_data_reg.loc[fa_idx, "Romain_mwea_mbclim"] + ) + # output_df_all.loc[nglac, 'thick_measured_yn'] = fa_glac_data_reg.loc[fa_idx, 'thick_measured_yn'] # ----- CORRECT TOO POSITIVE CLIMATIC MASS BALANCES ----- - output_df_all['mb_clim_gta'] = output_df_all['mb_clim_gta_obs'] - output_df_all['mb_total_gta'] = output_df_all['mb_total_gta_obs'] - output_df_all['mb_clim_mwea'] = output_df_all['mb_clim_mwea_obs'] - output_df_all['mb_total_mwea'] = output_df_all['mb_total_mwea_obs'] - output_df_all['fa_gta_max'] = output_df_all['fa_gta_obs'] - - output_df_badmbclim = output_df_all.loc[output_df_all.mb_clim_mwea_obs > mb_clim_reg_3std] + output_df_all["mb_clim_gta"] = output_df_all["mb_clim_gta_obs"] + output_df_all["mb_total_gta"] = output_df_all["mb_total_gta_obs"] + output_df_all["mb_clim_mwea"] = output_df_all["mb_clim_mwea_obs"] + output_df_all["mb_total_mwea"] = output_df_all["mb_total_mwea_obs"] + output_df_all["fa_gta_max"] = output_df_all["fa_gta_obs"] + + output_df_badmbclim = output_df_all.loc[ + output_df_all.mb_clim_mwea_obs > mb_clim_reg_3std + ] # Correct by using mean + 3std as maximum climatic mass balance if output_df_badmbclim.shape[0] > 0: rgiids_toopos = list(output_df_badmbclim.RGIId) @@ -809,550 +1206,1243 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati if rgiid in rgiids_toopos: # Specify maximum frontal ablation based on maximum climatic mass balance mb_clim_mwea = mb_clim_reg_3std - area_m2 = output_df_all.loc[nglac,'area_km2'] * 1e6 + area_m2 = output_df_all.loc[nglac, "area_km2"] * 1e6 mb_clim_gta = mwea_to_gta(mb_clim_mwea, area_m2) - mb_total_gta = output_df_all.loc[nglac,'mb_total_gta_obs'] - + mb_total_gta = output_df_all.loc[ + nglac, "mb_total_gta_obs" + ] + fa_gta_max = mb_clim_gta - mb_total_gta - - output_df_all.loc[nglac,'fa_gta_max'] = fa_gta_max - output_df_all.loc[nglac,'mb_clim_mwea'] = mb_clim_mwea - output_df_all.loc[nglac,'mb_clim_gta'] = mb_clim_gta - + + output_df_all.loc[nglac, "fa_gta_max"] = fa_gta_max + output_df_all.loc[nglac, "mb_clim_mwea"] = mb_clim_mwea + output_df_all.loc[nglac, "mb_clim_gta"] = mb_clim_gta + # ---- FIRST ROUND CALIBRATION ----- # ----- OPTIMIZE CALVING_K BASED ON INDIVIDUAL GLACIER FRONTAL ABLATION DATA ----- failed_glacs = [] for nglac in np.arange(main_glac_rgi.shape[0]): - glacier_str = '{0:0.5f}'.format(main_glac_rgi.loc[nglac,'RGIId_float']) + glacier_str = "{0:0.5f}".format( + main_glac_rgi.loc[nglac, "RGIId_float"] + ) try: # Reset bounds calving_k = calving_k_init calving_k_bndlow = np.copy(calving_k_bndlow_set) calving_k_bndhigh = np.copy(calving_k_bndhigh_set) calving_k_step = np.copy(calving_k_step_set) - + # Select individual glacier - main_glac_rgi_ind = main_glac_rgi.loc[[nglac],:] + main_glac_rgi_ind = main_glac_rgi.loc[[nglac], :] main_glac_rgi_ind.reset_index(inplace=True, drop=True) - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] + rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] - fa_glac_data_ind = fa_glac_data_reg.loc[fa_glac_data_reg.RGIId == rgiid_ind, :] + fa_glac_data_ind = fa_glac_data_reg.loc[ + fa_glac_data_reg.RGIId == rgiid_ind, : + ] fa_glac_data_ind.reset_index(inplace=True, drop=True) - + # Update the data - fa_gta_max = output_df_all.loc[nglac,'fa_gta_max'] - if fa_glac_data_ind.loc[0,frontal_ablation_Gta_cn] > fa_gta_max: + fa_gta_max = output_df_all.loc[nglac, "fa_gta_max"] + if ( + fa_glac_data_ind.loc[0, frontal_ablation_Gta_cn] + > fa_gta_max + ): reg_calving_gta_obs = fa_gta_max - fa_glac_data_ind.loc[0,frontal_ablation_Gta_cn] = fa_gta_max + fa_glac_data_ind.loc[0, frontal_ablation_Gta_cn] = ( + fa_gta_max + ) # Check bounds bndlow_good = True bndhigh_good = True try: - output_df_bndhigh, reg_calving_gta_mod_bndhigh, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndhigh, args, fa_glac_data_reg=fa_glac_data_ind, ignore_nan=False)) + ( + output_df_bndhigh, + reg_calving_gta_mod_bndhigh, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndhigh, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + ) except: bndhigh_good = False reg_calving_gta_mod_bndhigh = None try: - output_df_bndlow, reg_calving_gta_mod_bndlow, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndlow, args, fa_glac_data_reg=fa_glac_data_ind, ignore_nan=False)) + ( + output_df_bndlow, + reg_calving_gta_mod_bndlow, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndlow, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + ) except: bndlow_good = False reg_calving_gta_mod_bndlow = None - + # Record bounds - output_df_all.loc[nglac,'calving_flux_Gta_bndlow'] = reg_calving_gta_mod_bndlow - output_df_all.loc[nglac,'calving_flux_Gta_bndhigh'] = reg_calving_gta_mod_bndhigh - + output_df_all.loc[nglac, "calving_flux_Gta_bndlow"] = ( + reg_calving_gta_mod_bndlow + ) + output_df_all.loc[nglac, "calving_flux_Gta_bndhigh"] = ( + reg_calving_gta_mod_bndhigh + ) + if verbose: - print(' fa_data [Gt/yr]:', np.round(reg_calving_gta_obs,4)) - print(' fa_model_bndlow [Gt/yr] :', reg_calving_gta_mod_bndlow) - print(' fa_model_bndhigh [Gt/yr] :', reg_calving_gta_mod_bndhigh) - - + print( + " fa_data [Gt/yr]:", + np.round(reg_calving_gta_obs, 4), + ) + print( + " fa_model_bndlow [Gt/yr] :", + reg_calving_gta_mod_bndlow, + ) + print( + " fa_model_bndhigh [Gt/yr] :", + reg_calving_gta_mod_bndhigh, + ) + run_opt = False if bndhigh_good and bndlow_good: if reg_calving_gta_obs < reg_calving_gta_mod_bndlow: - output_df_all.loc[nglac,'calving_k'] = output_df_bndlow.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df_bndlow.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df_bndlow.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df_bndlow.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df_bndlow.loc[0,'oggm_dynamics'] + output_df_all.loc[nglac, "calving_k"] = ( + output_df_bndlow.loc[0, "calving_k"] + ) + output_df_all.loc[nglac, "calving_thick"] = ( + output_df_bndlow.loc[0, "calving_thick"] + ) + output_df_all.loc[nglac, "calving_flux_Gta"] = ( + output_df_bndlow.loc[0, "calving_flux_Gta"] + ) + output_df_all.loc[nglac, "no_errors"] = ( + output_df_bndlow.loc[0, "no_errors"] + ) + output_df_all.loc[nglac, "oggm_dynamics"] = ( + output_df_bndlow.loc[0, "oggm_dynamics"] + ) elif reg_calving_gta_obs > reg_calving_gta_mod_bndhigh: - output_df_all.loc[nglac,'calving_k'] = output_df_bndhigh.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df_bndhigh.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df_bndhigh.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df_bndhigh.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df_bndhigh.loc[0,'oggm_dynamics'] + output_df_all.loc[nglac, "calving_k"] = ( + output_df_bndhigh.loc[0, "calving_k"] + ) + output_df_all.loc[nglac, "calving_thick"] = ( + output_df_bndhigh.loc[0, "calving_thick"] + ) + output_df_all.loc[nglac, "calving_flux_Gta"] = ( + output_df_bndhigh.loc[0, "calving_flux_Gta"] + ) + output_df_all.loc[nglac, "no_errors"] = ( + output_df_bndhigh.loc[0, "no_errors"] + ) + output_df_all.loc[nglac, "oggm_dynamics"] = ( + output_df_bndhigh.loc[0, "oggm_dynamics"] + ) else: run_opt = True else: run_opt = True - + if run_opt: - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k, calving_k_bndlow, calving_k_bndhigh, - fa_glac_data_ind, ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k, + calving_k_bndlow, + calving_k_bndhigh, + fa_glac_data_ind, + ignore_nan=False, + ) calving_k_med = np.copy(calving_k) - output_df_all.loc[nglac,'calving_k'] = output_df.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_thick'] = output_df.loc[0,'calving_thick'] - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'no_errors'] = output_df.loc[0,'no_errors'] - output_df_all.loc[nglac,'oggm_dynamics'] = output_df.loc[0,'oggm_dynamics'] - + output_df_all.loc[nglac, "calving_k"] = output_df.loc[ + 0, "calving_k" + ] + output_df_all.loc[nglac, "calving_thick"] = ( + output_df.loc[0, "calving_thick"] + ) + output_df_all.loc[nglac, "calving_flux_Gta"] = ( + output_df.loc[0, "calving_flux_Gta"] + ) + output_df_all.loc[nglac, "no_errors"] = output_df.loc[ + 0, "no_errors" + ] + output_df_all.loc[nglac, "oggm_dynamics"] = ( + output_df.loc[0, "oggm_dynamics"] + ) + # ----- ADD UNCERTAINTY ----- # Upper uncertainty - if verbose: print('\n\n----- upper uncertainty:') + if verbose: + print("\n\n----- upper uncertainty:") fa_glac_data_ind_high = fa_glac_data_ind.copy() - fa_gta_obs_high = fa_glac_data_ind.loc[0,'fa_gta_obs'] + fa_glac_data_ind.loc[0,'fa_gta_obs_unc'] - fa_glac_data_ind_high.loc[0,'fa_gta_obs'] = fa_gta_obs_high + fa_gta_obs_high = ( + fa_glac_data_ind.loc[0, "fa_gta_obs"] + + fa_glac_data_ind.loc[0, "fa_gta_obs_unc"] + ) + fa_glac_data_ind_high.loc[0, "fa_gta_obs"] = ( + fa_gta_obs_high + ) calving_k_bndlow_upper = np.copy(calving_k_med) - 0.01 calving_k_start = np.copy(calving_k_med) - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k_start, calving_k_bndlow_upper, calving_k_bndhigh, - fa_glac_data_ind_high, ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k_start, + calving_k_bndlow_upper, + calving_k_bndhigh, + fa_glac_data_ind_high, + ignore_nan=False, + ) calving_k_nmadhigh = np.copy(calving_k) - + if verbose: - print('calving_k:', np.round(calving_k,2), 'fa_data high:', np.round(fa_glac_data_ind_high.loc[0,'fa_gta_obs'],4), - 'fa_mod high:', np.round(output_df.loc[0,'calving_flux_Gta'],4)) - + print( + "calving_k:", + np.round(calving_k, 2), + "fa_data high:", + np.round( + fa_glac_data_ind_high.loc[0, "fa_gta_obs"], + 4, + ), + "fa_mod high:", + np.round( + output_df.loc[0, "calving_flux_Gta"], 4 + ), + ) + # Lower uncertainty - if verbose: print('\n\n----- lower uncertainty:') + if verbose: + print("\n\n----- lower uncertainty:") fa_glac_data_ind_low = fa_glac_data_ind.copy() - fa_gta_obs_low = fa_glac_data_ind.loc[0,'fa_gta_obs'] - fa_glac_data_ind.loc[0,'fa_gta_obs_unc'] + fa_gta_obs_low = ( + fa_glac_data_ind.loc[0, "fa_gta_obs"] + - fa_glac_data_ind.loc[0, "fa_gta_obs_unc"] + ) if fa_gta_obs_low < 0: - calving_k_nmadlow = calving_k_med - abs(calving_k_nmadhigh - calving_k_med) + calving_k_nmadlow = calving_k_med - abs( + calving_k_nmadhigh - calving_k_med + ) if verbose: - print('calving_k:', np.round(calving_k_nmadlow,2), 'fa_data low:', np.round(fa_gta_obs_low,4)) + print( + "calving_k:", + np.round(calving_k_nmadlow, 2), + "fa_data low:", + np.round(fa_gta_obs_low, 4), + ) else: - fa_glac_data_ind_low.loc[0,'fa_gta_obs'] = fa_gta_obs_low - calving_k_bndhigh_lower = np.copy(calving_k_med) + 0.01 + fa_glac_data_ind_low.loc[0, "fa_gta_obs"] = ( + fa_gta_obs_low + ) + calving_k_bndhigh_lower = ( + np.copy(calving_k_med) + 0.01 + ) calving_k_start = np.copy(calving_k_med) - output_df, calving_k = run_opt_fa(main_glac_rgi_ind, args, calving_k_start, calving_k_bndlow, calving_k_bndhigh_lower, - fa_glac_data_ind_low, - calving_k_step=(calving_k_med - calving_k_bndlow) / 10, - ignore_nan=False) + output_df, calving_k = run_opt_fa( + main_glac_rgi_ind, + args, + calving_k_start, + calving_k_bndlow, + calving_k_bndhigh_lower, + fa_glac_data_ind_low, + calving_k_step=( + calving_k_med - calving_k_bndlow + ) + / 10, + ignore_nan=False, + ) calving_k_nmadlow = np.copy(calving_k) if verbose: - print('calving_k:', np.round(calving_k,2), 'fa_data low:', np.round(fa_glac_data_ind_low.loc[0,'fa_gta_obs'],4), - 'fa_mod low:', np.round(output_df.loc[0,'calving_flux_Gta'],4)) - - - calving_k_nmad = np.mean([abs(calving_k_nmadhigh - calving_k_med), abs(calving_k_nmadlow - calving_k_med)]) - + print( + "calving_k:", + np.round(calving_k, 2), + "fa_data low:", + np.round( + fa_glac_data_ind_low.loc[ + 0, "fa_gta_obs" + ], + 4, + ), + "fa_mod low:", + np.round( + output_df.loc[0, "calving_flux_Gta"], 4 + ), + ) + + calving_k_nmad = np.mean( + [ + abs(calving_k_nmadhigh - calving_k_med), + abs(calving_k_nmadlow - calving_k_med), + ] + ) + # Final if verbose: - print('----- final -----') - print(rgiid, 'calving_k (med/high/low/nmad):', np.round(calving_k_med,2), - np.round(calving_k_nmadhigh,2), np.round(calving_k_nmadlow,2), np.round(calving_k_nmad,2)) - - output_df_all.loc[nglac,'calving_k_nmad'] = calving_k_nmad + print("----- final -----") + print( + rgiid, + "calving_k (med/high/low/nmad):", + np.round(calving_k_med, 2), + np.round(calving_k_nmadhigh, 2), + np.round(calving_k_nmadlow, 2), + np.round(calving_k_nmad, 2), + ) + + output_df_all.loc[nglac, "calving_k_nmad"] = ( + calving_k_nmad + ) except: failed_glacs.append(glacier_str) pass -# # Glaciers at bounds, have calving_k_nmad based on regional mean -# output_df_all_subset = output_df_all.loc[output_df_all.calving_k_nmad > 0, :] -# calving_k_nmad = 1.4826 * median_abs_deviation(output_df_all_subset.calving_k) -# output_df_all.loc[output_df_all['calving_k_nmad']==0,'calving_k_nmad'] = calving_k_nmad - + # # Glaciers at bounds, have calving_k_nmad based on regional mean + # output_df_all_subset = output_df_all.loc[output_df_all.calving_k_nmad > 0, :] + # calving_k_nmad = 1.4826 * median_abs_deviation(output_df_all_subset.calving_k) + # output_df_all.loc[output_df_all['calving_k_nmad']==0,'calving_k_nmad'] = calving_k_nmad + # ----- EXPORT MODEL RESULTS ----- output_df_all.to_csv(output_fp + output_fn, index=False) # Write list of failed glaciers - if len(failed_glacs)>0: - with open(output_fp + output_fn[:-4] + "-failed.txt", "w") as f: + if len(failed_glacs) > 0: + with open( + output_fp + output_fn[:-4] + "-failed.txt", "w" + ) as f: for item in failed_glacs: f.write(f"{item}\n") - + else: output_df_all = pd.read_csv(output_fp + output_fn) - + # ----- VIEW DIAGNOSTICS OF 'GOOD' GLACIERS ----- # special for 17 because so few 'good' glaciers if reg in [17]: - output_df_all_good = output_df_all.loc[(output_df_all['calving_k'] < calving_k_bndhigh_set), :] + output_df_all_good = output_df_all.loc[ + (output_df_all["calving_k"] < calving_k_bndhigh_set), : + ] else: - output_df_all_good = output_df_all.loc[(output_df_all['fa_gta_obs'] == output_df_all['fa_gta_max']) & - (output_df_all['calving_k'] < calving_k_bndhigh_set), :] - + output_df_all_good = output_df_all.loc[ + (output_df_all["fa_gta_obs"] == output_df_all["fa_gta_max"]) + & (output_df_all["calving_k"] < calving_k_bndhigh_set), + :, + ] + rgiids_good = list(output_df_all_good.RGIId) calving_k_reg_mean = output_df_all_good.calving_k.mean() if verbose: - print(' calving_k mean/med:', np.round(calving_k_reg_mean,2), - np.round(np.median(output_df_all_good.calving_k),2)) - - output_df_all['calving_flux_Gta_rnd1'] = output_df_all['calving_flux_Gta'].copy() - output_df_all['calving_k_rnd1'] = output_df_all['calving_k'].copy() - - # ----- PLOT RESULTS FOR EACH GLACIER ----- - if len(rgiids_good)>0: - with np.errstate(all='ignore'): - plot_max_raw = np.max([output_df_all_good.calving_flux_Gta.max(), output_df_all_good.fa_gta_obs.max()]) - plot_max = 10**np.ceil(np.log10(plot_max_raw)) + print( + " calving_k mean/med:", + np.round(calving_k_reg_mean, 2), + np.round(np.median(output_df_all_good.calving_k), 2), + ) + + output_df_all["calving_flux_Gta_rnd1"] = output_df_all[ + "calving_flux_Gta" + ].copy() + output_df_all["calving_k_rnd1"] = output_df_all["calving_k"].copy() - plot_min_raw = np.max([output_df_all_good.calving_flux_Gta.min(), output_df_all_good.fa_gta_obs.min()]) - plot_min = 10**np.floor(np.log10(plot_min_raw)) + # ----- PLOT RESULTS FOR EACH GLACIER ----- + if len(rgiids_good) > 0: + with np.errstate(all="ignore"): + plot_max_raw = np.max( + [ + output_df_all_good.calving_flux_Gta.max(), + output_df_all_good.fa_gta_obs.max(), + ] + ) + plot_max = 10 ** np.ceil(np.log10(plot_max_raw)) + + plot_min_raw = np.max( + [ + output_df_all_good.calving_flux_Gta.min(), + output_df_all_good.fa_gta_obs.min(), + ] + ) + plot_min = 10 ** np.floor(np.log10(plot_min_raw)) if plot_min < 1e-3: plot_min = 1e-4 x_min, x_max = plot_min, plot_max - - fig, ax = plt.subplots(2, 2, squeeze=False, gridspec_kw = {'wspace':0.4, 'hspace':0.4}) - + + fig, ax = plt.subplots( + 2, 2, squeeze=False, gridspec_kw={"wspace": 0.4, "hspace": 0.4} + ) + # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all_good['area_km2'].values - s_sizes = [10,50,250,1000] + glac_area_all = output_df_all_good["area_km2"].values + s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] - s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] - s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - - sc = ax[0,0].scatter(output_df_all_good['fa_gta_obs'], output_df_all_good['calving_flux_Gta'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) + s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[ + 1 + ] + s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = ( + s_sizes[2] + ) + + sc = ax[0, 0].scatter( + output_df_all_good["fa_gta_obs"], + output_df_all_good["calving_flux_Gta"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) # Labels - ax[0,0].set_xlabel('Observed $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_ylabel('Modeled $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_xlim(x_min,x_max) - ax[0,0].set_ylim(x_min,x_max) - ax[0,0].plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) + ax[0, 0].set_xlabel("Observed $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_ylabel("Modeled $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_xlim(x_min, x_max) + ax[0, 0].set_ylim(x_min, x_max) + ax[0, 0].plot( + [x_min, x_max], + [x_min, x_max], + color="k", + linewidth=0.5, + zorder=1, + ) # Log scale - ax[0,0].set_xscale('log') - ax[0,0].set_yscale('log') - + ax[0, 0].set_xscale("log") + ax[0, 0].set_yscale("log") + # Legend - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = [ + "< 10", + "10-10$^{2}$", + "10$^{2}$-10$^{3}$", + "> 10$^{3}$", + ] for nlabel, obs_label in enumerate(obs_labels): - ax[0,0].scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax[0,0].text(0.06, 0.98, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax[0,0].transAxes, color='grey') - leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') + ax[0, 0].scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax[0, 0].text( + 0.06, + 0.98, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax[0, 0].transAxes, + color="grey", + ) + leg = ax[0, 0].legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) # ----- Histogram ----- - # nbins = 25 - # ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') - vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) - hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) - ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - align='center', edgecolor='black', color='grey') - ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - ax[0,1].set_xticks(vn_bins, minor=True) - ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # nbins = 25 + # ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') + vn_bins = np.arange( + 0, np.max([1, output_df_all_good.calving_k.max()]) + 0.1, 0.1 + ) + hist, bins = np.histogram( + output_df_all_good.loc[ + output_df_all_good["no_errors"] == 1, "calving_k" + ], + bins=vn_bins, + ) + ax[0, 1].bar( + x=vn_bins[:-1] + 0.1 / 2, + height=hist, + width=(bins[1] - bins[0]), + align="center", + edgecolor="black", + color="grey", + ) + ax[0, 1].set_xticks( + np.arange(0, np.max([1, vn_bins.max()]) + 0.1, 1) + ) + ax[0, 1].set_xticks(vn_bins, minor=True) + ax[0, 1].set_xlim(vn_bins.min(), np.max([1, vn_bins.max()])) if hist.max() < 40: y_major_interval = 5 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = ( + np.ceil(hist.max() / y_major_interval) * y_major_interval + ) + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) elif hist.max() > 40: y_major_interval = 10 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - + y_max = ( + np.ceil(hist.max() / y_major_interval) * y_major_interval + ) + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) + # Labels - ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[0,1].set_ylabel('Count (glaciers)', size=12) - + ax[0, 1].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[0, 1].set_ylabel("Count (glaciers)", size=12) + # ----- CALVING_K VS MB_CLIM ----- - ax[1,0].scatter(output_df_all_good['calving_k'], output_df_all_good['mb_clim_mwea'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,0].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[1,0].set_ylabel('$B_{clim}$ (mwea)', size=12) - + ax[1, 0].scatter( + output_df_all_good["calving_k"], + output_df_all_good["mb_clim_mwea"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 0].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[1, 0].set_ylabel("$B_{clim}$ (mwea)", size=12) + # ----- CALVING_K VS AREA ----- - ax[1,1].scatter(output_df_all_good['area_km2'], output_df_all_good['calving_k'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,1].set_xlabel('Area (km2)', size=12) - ax[1,1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) - + ax[1, 1].scatter( + output_df_all_good["area_km2"], + output_df_all_good["calving_k"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 1].set_xlabel("Area (km2)", size=12) + ax[1, 1].set_ylabel("$k_{f}$ (yr$^{-1}$)", size=12) + # Correlation - slope, intercept, r_value, p_value, std_err = linregress(output_df_all_good['area_km2'], - output_df_all_good['calving_k'],) + slope, intercept, r_value, p_value, std_err = linregress( + output_df_all_good["area_km2"], + output_df_all_good["calving_k"], + ) if verbose: - print(' r_value =', np.round(r_value,2), 'slope = ', np.round(slope,5), - 'intercept = ', np.round(intercept,5), 'p_value = ', np.round(p_value,6)) + print( + " r_value =", + np.round(r_value, 2), + "slope = ", + np.round(slope, 5), + "intercept = ", + np.round(intercept, 5), + "p_value = ", + np.round(p_value, 6), + ) area_min = 0 area_max = output_df_all_good.area_km2.max() - ax[1,1].plot([area_min, area_max], [intercept+slope*area_min, intercept+slope*area_max], color='k') - + ax[1, 1].plot( + [area_min, area_max], + [intercept + slope * area_min, intercept + slope * area_max], + color="k", + ) + # Save figure - fig.set_size_inches(6,6) - fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind-good.png' - fig.savefig(fig_fullfn, bbox_inches='tight', dpi=300) - + fig.set_size_inches(6, 6) + fig_fullfn = ( + output_fp + + str(reg) + + "-frontalablation_glac_compare-cal_ind-good.png" + ) + fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) + # ----- REPLACE UPPER BOUND CALVING_K WITH MEDIAN CALVING_K ----- - rgiids_bndhigh = list(output_df_all.loc[output_df_all['calving_k'] == calving_k_bndhigh_set,'RGIId'].values) + rgiids_bndhigh = list( + output_df_all.loc[ + output_df_all["calving_k"] == calving_k_bndhigh_set, "RGIId" + ].values + ) for nglac, rgiid in enumerate(output_df_all.RGIId): if rgiid in rgiids_bndhigh: # Estimate frontal ablation for poor glaciers extrapolated from good ones - main_glac_rgi_ind = main_glac_rgi.loc[main_glac_rgi.RGIId == rgiid,:] + main_glac_rgi_ind = main_glac_rgi.loc[ + main_glac_rgi.RGIId == rgiid, : + ] main_glac_rgi_ind.reset_index(inplace=True, drop=True) - fa_glac_data_ind = fa_glac_data_reg.loc[fa_glac_data_reg.RGIId == rgiid, :] + fa_glac_data_ind = fa_glac_data_reg.loc[ + fa_glac_data_reg.RGIId == rgiid, : + ] fa_glac_data_ind.reset_index(inplace=True, drop=True) - + calving_k = np.median(output_df_all_good.calving_k) -# calving_k = intercept + slope * main_glac_rgi_ind.loc[0,'Area'] -# if calving_k > output_df_all_good.calving_k.max(): -# calving_k = output_df_all_good.calving_k.max() - + # calving_k = intercept + slope * main_glac_rgi_ind.loc[0,'Area'] + # if calving_k > output_df_all_good.calving_k.max(): + # calving_k = output_df_all_good.calving_k.max() + output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, fa_glac_data_reg=fa_glac_data_ind, ignore_nan=False)) - - output_df_all.loc[nglac,'calving_flux_Gta'] = output_df.loc[0,'calving_flux_Gta'] - output_df_all.loc[nglac,'calving_k'] = output_df.loc[0,'calving_k'] - output_df_all.loc[nglac,'calving_k_nmad'] = np.median(output_df_all_good.calving_k_nmad) - + reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + ) + ) + + output_df_all.loc[nglac, "calving_flux_Gta"] = output_df.loc[ + 0, "calving_flux_Gta" + ] + output_df_all.loc[nglac, "calving_k"] = output_df.loc[ + 0, "calving_k" + ] + output_df_all.loc[nglac, "calving_k_nmad"] = np.median( + output_df_all_good.calving_k_nmad + ) + # ----- EXPORT MODEL RESULTS ----- output_df_all.to_csv(output_fp + output_fn, index=False) - + # ----- PROCESS MISSING GLACIERS WHERE GEODETIC MB IS NOT CORRECTED FOR AREA ABOVE SEA LEVEL LOSSES - if reg in [1,3,4,5,7,9,17]: - output_fn_missing = output_fn.replace('.csv','-missing.csv') - - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], rgi_regionsO2='all', - rgi_glac_number='all', - include_landterm=False, include_laketerm=False, - include_tidewater=True) + if reg in [1, 3, 4, 5, 7, 9, 17]: + output_fn_missing = output_fn.replace(".csv", "-missing.csv") + + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + include_landterm=False, + include_laketerm=False, + include_tidewater=True, + ) rgiids_processed = list(output_df_all.RGIId) rgiids_all = list(main_glac_rgi_all.RGIId) - rgiids_missing = [x for x in rgiids_all if x not in rgiids_processed] + rgiids_missing = [ + x for x in rgiids_all if x not in rgiids_processed + ] if len(rgiids_missing) == 0: break - glac_no_missing = [x.split('-')[1] for x in rgiids_missing] - main_glac_rgi_missing = modelsetup.selectglaciersrgitable(glac_no=glac_no_missing) - - if verbose: print(reg, len(glac_no_missing), main_glac_rgi_missing.Area.sum(), glac_no_missing) - + glac_no_missing = [x.split("-")[1] for x in rgiids_missing] + main_glac_rgi_missing = modelsetup.selectglaciersrgitable( + glac_no=glac_no_missing + ) + + if verbose: + print( + reg, + len(glac_no_missing), + main_glac_rgi_missing.Area.sum(), + glac_no_missing, + ) + if not os.path.exists(output_fp + output_fn_missing) or overwrite: - # Add regions for median subsets - output_df_all['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in output_df_all.RGIId] - + output_df_all["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) + for x in output_df_all.RGIId + ] + # Update mass balance data - output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) - output_df_missing['RGIId'] = rgiids_missing - output_df_missing['fa_gta_obs'] = np.nan - rgi_area_dict = dict(zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area)) - output_df_missing['area_km2'] = output_df_missing['RGIId'].map(rgi_area_dict) - rgi_mbobs_dict = dict(zip(mb_data['rgiid'],mb_data['mb_mwea'])) - output_df_missing['mb_clim_mwea_obs'] = output_df_missing['RGIId'].map(rgi_mbobs_dict) - output_df_missing['mb_clim_gta_obs'] = [mwea_to_gta(output_df_missing.loc[x,'mb_clim_mwea_obs'], - output_df_missing.loc[x,'area_km2']*1e6) for x in output_df_missing.index] - output_df_missing['mb_total_mwea_obs'] = output_df_missing['mb_clim_mwea_obs'] - output_df_missing['mb_total_gta_obs'] = output_df_missing['mb_total_gta_obs'] - + output_df_missing = pd.DataFrame( + np.zeros( + (len(rgiids_missing), len(output_df_all.columns)) + ), + columns=output_df_all.columns, + ) + output_df_missing["RGIId"] = rgiids_missing + output_df_missing["fa_gta_obs"] = np.nan + rgi_area_dict = dict( + zip( + main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area + ) + ) + output_df_missing["area_km2"] = output_df_missing["RGIId"].map( + rgi_area_dict + ) + rgi_mbobs_dict = dict( + zip(mb_data["rgiid"], mb_data["mb_mwea"]) + ) + output_df_missing["mb_clim_mwea_obs"] = output_df_missing[ + "RGIId" + ].map(rgi_mbobs_dict) + output_df_missing["mb_clim_gta_obs"] = [ + mwea_to_gta( + output_df_missing.loc[x, "mb_clim_mwea_obs"], + output_df_missing.loc[x, "area_km2"] * 1e6, + ) + for x in output_df_missing.index + ] + output_df_missing["mb_total_mwea_obs"] = output_df_missing[ + "mb_clim_mwea_obs" + ] + output_df_missing["mb_total_gta_obs"] = output_df_missing[ + "mb_total_gta_obs" + ] + # Start with median value - calving_k_med = np.median(output_df_all.loc[output_df_all['O1Region']==reg,'calving_k']) + calving_k_med = np.median( + output_df_all.loc[ + output_df_all["O1Region"] == reg, "calving_k" + ] + ) failed_glacs = [] for nglac, rgiid in enumerate(rgiids_missing): - glacier_str = rgiid.split('-')[1] - try: - main_glac_rgi_ind = modelsetup.selectglaciersrgitable(glac_no=[rgiid.split('-')[1]]) + glacier_str = rgiid.split("-")[1] + try: + main_glac_rgi_ind = modelsetup.selectglaciersrgitable( + glac_no=[rgiid.split("-")[1]] + ) # Estimate frontal ablation for missing glaciers output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_med, args, debug=True, calc_mb_geo_correction=True)) - + reg_calving_flux( + main_glac_rgi_ind, + calving_k_med, + args, + debug=True, + calc_mb_geo_correction=True, + ) + ) + # Adjust climatic mass balance to account for the losses due to frontal ablation # add this loss because it'll come from frontal ablation instead of climatic mass balance - mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - output_df.loc[0,'mb_mwea_fa_asl_lost']) - - mb_clim_reg_95 = (mb_clim_reg_avg + 1.96*mb_clim_reg_std) + mb_clim_fa_corrected = ( + output_df_missing.loc[nglac, "mb_clim_mwea_obs"] + + output_df.loc[0, "mb_mwea_fa_asl_lost"] + ) + + mb_clim_reg_95 = ( + mb_clim_reg_avg + 1.96 * mb_clim_reg_std + ) if verbose: - print('mb_clim (raw):', np.round(output_df_missing.loc[nglac,'mb_clim_mwea_obs'],2)) - print('mb_clim (fa_corrected):', np.round(mb_clim_fa_corrected,2)) - print('mb_clim (reg 95%):', np.round(mb_clim_reg_95,2)) - print('mb_total (95% min):', np.round(mb_clim_reg_3std_min,2)) - + print( + "mb_clim (raw):", + np.round( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ], + 2, + ), + ) + print( + "mb_clim (fa_corrected):", + np.round(mb_clim_fa_corrected, 2), + ) + print( + "mb_clim (reg 95%):", + np.round(mb_clim_reg_95, 2), + ) + print( + "mb_total (95% min):", + np.round(mb_clim_reg_3std_min, 2), + ) + # Set nmad to median value - correct if value reduced -# calving_k_nmad_missing = 1.4826*median_abs_deviation(output_df_all_good.calving_k) - calving_k_nmad_missing = np.median(output_df_all_good.calving_k_nmad) - output_df_missing.loc[nglac,'calving_k_nmad'] = calving_k_nmad_missing - + # calving_k_nmad_missing = 1.4826*median_abs_deviation(output_df_all_good.calving_k) + calving_k_nmad_missing = np.median( + output_df_all_good.calving_k_nmad + ) + output_df_missing.loc[nglac, "calving_k_nmad"] = ( + calving_k_nmad_missing + ) + if mb_clim_fa_corrected < mb_clim_reg_95: - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_fa_corrected - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], - output_df_missing.loc[nglac,'area_km2']*1e6) - output_df_missing.loc[nglac,'mb_total_gta'] = (output_df_missing.loc[nglac,'mb_clim_gta'] - - output_df_missing.loc[nglac,'calving_flux_Gta']) - output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], - output_df_missing.loc[nglac,'area_km2']*1e6) - + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + output_df_missing.loc[nglac, "mb_clim_mwea"] = ( + mb_clim_fa_corrected + ) + output_df_missing.loc[nglac, "mb_clim_gta"] = ( + mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + output_df_missing.loc[nglac, "area_km2"] + * 1e6, + ) + ) + output_df_missing.loc[nglac, "mb_total_gta"] = ( + output_df_missing.loc[nglac, "mb_clim_gta"] + - output_df_missing.loc[ + nglac, "calving_flux_Gta" + ] + ) + output_df_missing.loc[nglac, "mb_total_mwea"] = ( + gta_to_mwea( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + output_df_missing.loc[nglac, "area_km2"] + * 1e6, + ) + ) + if mb_clim_fa_corrected > mb_clim_reg_95: - # Calibrate frontal ablation based on fa_mwea_max # i.e., the maximum frontal ablation that is consistent with reasonable mb_clim - fa_mwea_max = mb_clim_reg_95 - output_df_missing.loc[nglac,'mb_clim_mwea_obs'] - + fa_mwea_max = ( + mb_clim_reg_95 + - output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + ) + # Reset bounds calving_k = calving_k_med calving_k_bndlow = np.copy(calving_k_bndlow_set) calving_k_bndhigh = np.copy(calving_k_bndhigh_set) calving_k_step = np.copy(calving_k_step_set) - + # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] - # fa_glac_data_ind = pd.DataFrame(np.zeros((1,len(fa_glac_data_reg.columns))), + rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] + # fa_glac_data_ind = pd.DataFrame(np.zeros((1,len(fa_glac_data_reg.columns))), # columns=fa_glac_data_reg.columns) - fa_glac_data_ind = pd.DataFrame(columns=fa_glac_data_reg.columns) - fa_glac_data_ind.loc[0,'RGIId'] = rgiid_ind - + fa_glac_data_ind = pd.DataFrame( + columns=fa_glac_data_reg.columns + ) + fa_glac_data_ind.loc[0, "RGIId"] = rgiid_ind + # Check bounds bndlow_good = True bndhigh_good = True try: - output_df_bndhigh, reg_calving_gta_mod_bndhigh, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndhigh, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndhigh, + reg_calving_gta_mod_bndhigh, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndhigh, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndhigh_good = False reg_calving_gta_mod_bndhigh = None - + try: - output_df_bndlow, reg_calving_gta_mod_bndlow, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndlow, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndlow, + reg_calving_gta_mod_bndlow, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndlow, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndlow_good = False reg_calving_gta_mod_bndlow = None - + if verbose: - print('mb_mwea_fa_asl_lost_bndhigh:', output_df_bndhigh.loc[0,'mb_mwea_fa_asl_lost']) - print('mb_mwea_fa_asl_lost_bndlow:', output_df_bndlow.loc[0,'mb_mwea_fa_asl_lost']) - + print( + "mb_mwea_fa_asl_lost_bndhigh:", + output_df_bndhigh.loc[ + 0, "mb_mwea_fa_asl_lost" + ], + ) + print( + "mb_mwea_fa_asl_lost_bndlow:", + output_df_bndlow.loc[ + 0, "mb_mwea_fa_asl_lost" + ], + ) + # Record bounds - output_df_missing.loc[nglac,'calving_flux_Gta_bndlow'] = reg_calving_gta_mod_bndlow - output_df_missing.loc[nglac,'calving_flux_Gta_bndhigh'] = reg_calving_gta_mod_bndhigh - + output_df_missing.loc[ + nglac, "calving_flux_Gta_bndlow" + ] = reg_calving_gta_mod_bndlow + output_df_missing.loc[ + nglac, "calving_flux_Gta_bndhigh" + ] = reg_calving_gta_mod_bndhigh + if verbose: - print(' fa_model_bndlow [Gt/yr] :', reg_calving_gta_mod_bndlow) - print(' fa_model_bndhigh [Gt/yr] :', reg_calving_gta_mod_bndhigh) - - + print( + " fa_model_bndlow [Gt/yr] :", + reg_calving_gta_mod_bndlow, + ) + print( + " fa_model_bndhigh [Gt/yr] :", + reg_calving_gta_mod_bndhigh, + ) + run_opt = True if fa_mwea_max > 0: if bndhigh_good and bndlow_good: - if fa_mwea_max < output_df_bndlow.loc[0,'mb_mwea_fa_asl_lost']: + if ( + fa_mwea_max + < output_df_bndlow.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + ): # Adjust climatic mass balance to note account for the losses due to frontal ablation - mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - output_df_bndlow.loc[0,'mb_mwea_fa_asl_lost']) + mb_clim_fa_corrected = ( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + + output_df_bndlow.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + ) # Record output - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df_bndlow.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_fa_corrected - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], - output_df_missing.loc[nglac,'area_km2']*1e6) - output_df_missing.loc[nglac,'mb_total_gta'] = (output_df_missing.loc[nglac,'mb_clim_gta'] - - output_df_missing.loc[nglac,'calving_flux_Gta']) - output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], - output_df_missing.loc[nglac,'area_km2']*1e6) - + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[ + nglac, cn + ] = output_df_bndlow.loc[0, cn] + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ] = mb_clim_fa_corrected + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] = mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + output_df_missing.loc[ + nglac, "mb_total_gta" + ] = ( + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] + - output_df_missing.loc[ + nglac, "calving_flux_Gta" + ] + ) + output_df_missing.loc[ + nglac, "mb_total_mwea" + ] = gta_to_mwea( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + run_opt = False - - elif output_df_bndhigh.loc[0,'mb_mwea_fa_asl_lost'] == output_df_bndlow.loc[0,'mb_mwea_fa_asl_lost']: + + elif ( + output_df_bndhigh.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + == output_df_bndlow.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + ): # Adjust climatic mass balance to note account for the losses due to frontal ablation - mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - output_df.loc[0,'mb_mwea_fa_asl_lost']) + mb_clim_fa_corrected = ( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + + output_df.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + ) # Record output - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_fa_corrected - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], - output_df_missing.loc[nglac,'area_km2']*1e6) - output_df_missing.loc[nglac,'mb_total_gta'] = (output_df_missing.loc[nglac,'mb_clim_gta'] - - output_df_missing.loc[nglac,'calving_flux_Gta']) - output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], - output_df_missing.loc[nglac,'area_km2']*1e6) + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[ + nglac, cn + ] = output_df.loc[0, cn] + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ] = mb_clim_fa_corrected + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] = mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + output_df_missing.loc[ + nglac, "mb_total_gta" + ] = ( + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] + - output_df_missing.loc[ + nglac, "calving_flux_Gta" + ] + ) + output_df_missing.loc[ + nglac, "mb_total_mwea" + ] = gta_to_mwea( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) run_opt = False - + if run_opt: - # mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - # output_df.loc[0,'mb_mwea_fa_asl_lost']) + # mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + + # output_df.loc[0,'mb_mwea_fa_asl_lost']) if verbose: - print('\n\n\n-------') - print('mb_clim_obs:', np.round(output_df_missing.loc[nglac,'mb_clim_mwea_obs'],2)) - print('mb_clim_fa_corrected:', np.round(mb_clim_fa_corrected,2)) - - calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 - calving_k_next = calving_k - calving_k_step_missing - while output_df.loc[0,'mb_mwea_fa_asl_lost'] > fa_mwea_max and calving_k_next > 0: + print("\n\n\n-------") + print( + "mb_clim_obs:", + np.round( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ], + 2, + ), + ) + print( + "mb_clim_fa_corrected:", + np.round(mb_clim_fa_corrected, 2), + ) + + calving_k_step_missing = ( + calving_k_med - calving_k_bndlow + ) / 20 + calving_k_next = ( + calving_k - calving_k_step_missing + ) + while ( + output_df.loc[0, "mb_mwea_fa_asl_lost"] + > fa_mwea_max + and calving_k_next > 0 + ): calving_k -= calving_k_step_missing - + # Estimate frontal ablation for missing glaciers - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, debug=True, calc_mb_geo_correction=True)) - - calving_k_next = calving_k - calving_k_step_missing - + ( + output_df, + reg_calving_gta_mod, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + debug=True, + calc_mb_geo_correction=True, + ) + + calving_k_next = ( + calving_k - calving_k_step_missing + ) + # Adjust climatic mass balance to note account for the losses due to frontal ablation - mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - output_df.loc[0,'mb_mwea_fa_asl_lost']) + mb_clim_fa_corrected = ( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + + output_df.loc[ + 0, "mb_mwea_fa_asl_lost" + ] + ) # Record output - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_fa_corrected - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], - output_df_missing.loc[nglac,'area_km2']*1e6) - output_df_missing.loc[nglac,'mb_total_gta'] = (output_df_missing.loc[nglac,'mb_clim_gta'] - - output_df_missing.loc[nglac,'calving_flux_Gta']) - output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], - output_df_missing.loc[nglac,'area_km2']*1e6) - - if verbose: print('mb_clim_fa_corrected (updated):', np.round(mb_clim_fa_corrected,2)) - + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ] = mb_clim_fa_corrected + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] = mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + output_df_missing.loc[ + nglac, "mb_total_gta" + ] = ( + output_df_missing.loc[ + nglac, "mb_clim_gta" + ] + - output_df_missing.loc[ + nglac, "calving_flux_Gta" + ] + ) + output_df_missing.loc[ + nglac, "mb_total_mwea" + ] = gta_to_mwea( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + + if verbose: + print( + "mb_clim_fa_corrected (updated):", + np.round(mb_clim_fa_corrected, 2), + ) + # If mass balance is higher than 95% threshold, then just make sure correction is reasonable (no more than 10%) else: calving_k = calving_k_med - calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 - calving_k_next = calving_k - calving_k_step_missing - while (output_df.loc[0,'mb_mwea_fa_asl_lost'] > 0.1*output_df_missing.loc[nglac,'mb_clim_mwea_obs'] and - calving_k_next > 0): + calving_k_step_missing = ( + calving_k_med - calving_k_bndlow + ) / 20 + calving_k_next = ( + calving_k - calving_k_step_missing + ) + while ( + output_df.loc[0, "mb_mwea_fa_asl_lost"] + > 0.1 + * output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + and calving_k_next > 0 + ): calving_k -= calving_k_step_missing - + # Estimate frontal ablation for missing glaciers - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, debug=True, calc_mb_geo_correction=True)) - - calving_k_next = calving_k - calving_k_step_missing - + ( + output_df, + reg_calving_gta_mod, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + debug=True, + calc_mb_geo_correction=True, + ) + + calving_k_next = ( + calving_k - calving_k_step_missing + ) + # Adjust climatic mass balance to note account for the losses due to frontal ablation - mb_clim_fa_corrected = (output_df_missing.loc[nglac,'mb_clim_mwea_obs'] + - output_df.loc[0,'mb_mwea_fa_asl_lost']) + mb_clim_fa_corrected = ( + output_df_missing.loc[ + nglac, "mb_clim_mwea_obs" + ] + + output_df.loc[0, "mb_mwea_fa_asl_lost"] + ) # Record output - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_fa_corrected - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], - output_df_missing.loc[nglac,'area_km2']*1e6) - output_df_missing.loc[nglac,'mb_total_gta'] = (output_df_missing.loc[nglac,'mb_clim_gta'] - - output_df_missing.loc[nglac,'calving_flux_Gta']) - output_df_missing.loc[nglac,'mb_total_mwea'] = gta_to_mwea(output_df_missing.loc[nglac,'mb_total_gta'], - output_df_missing.loc[nglac,'area_km2']*1e6) - - if verbose: print('mb_clim_fa_corrected (updated):', np.round(mb_clim_fa_corrected,2)) - - + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ] = mb_clim_fa_corrected + output_df_missing.loc[nglac, "mb_clim_gta"] = ( + mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + output_df_missing.loc[ + nglac, "area_km2" + ] + * 1e6, + ) + ) + output_df_missing.loc[ + nglac, "mb_total_gta" + ] = ( + output_df_missing.loc[nglac, "mb_clim_gta"] + - output_df_missing.loc[ + nglac, "calving_flux_Gta" + ] + ) + output_df_missing.loc[ + nglac, "mb_total_mwea" + ] = gta_to_mwea( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + output_df_missing.loc[nglac, "area_km2"] + * 1e6, + ) + + if verbose: + print( + "mb_clim_fa_corrected (updated):", + np.round(mb_clim_fa_corrected, 2), + ) + # Adjust calving_k_nmad if calving_k is very low to avoid poor values - if output_df_missing.loc[nglac,'calving_k'] < calving_k_nmad_missing: - output_df_missing.loc[nglac,'calving_k_nmad'] = output_df_missing.loc[nglac,'calving_k'] - calving_k_bndlow_set + if ( + output_df_missing.loc[nglac, "calving_k"] + < calving_k_nmad_missing + ): + output_df_missing.loc[ + nglac, "calving_k_nmad" + ] = ( + output_df_missing.loc[ + nglac, "calving_k" + ] + - calving_k_bndlow_set + ) except: failed_glacs.append(glacier_str) pass - # Export - output_df_missing.to_csv(output_fp + output_fn_missing, index=False) + # Export + output_df_missing.to_csv( + output_fp + output_fn_missing, index=False + ) # Write list of failed glaciers - if len(failed_glacs)>0: - with open(output_fp + output_fn_missing[:-4] + "-failed.txt", "w") as f: + if len(failed_glacs) > 0: + with open( + output_fp + output_fn_missing[:-4] + "-failed.txt", "w" + ) as f: for item in failed_glacs: f.write(f"{item}\n") else: @@ -1360,398 +2450,732 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # ----- CORRECTION FOR TOTAL MASS BALANCE IN ANTARCTICA/SUBANTARCTIC ----- if reg in [19]: - -# #%% STATISTICS FOR CALIBRATED GLACIERS -# for nglac, rgiid in enumerate(output_df_all.RGIId.values): -# mb_rgiids = list(mb_data_reg.RGIId.values) -# mb_idx = mb_rgiids.index(rgiid) -# output_df_all.loc[nglac,'mb_clim_mwea_obs'] = mb_data_reg.loc[mb_idx,'mb_mwea'] -# output_df_all.loc[nglac,'mb_clim_gta_obs'] = mwea_to_gta(output_df_all.loc[nglac,'mb_clim_mwea_obs'], -# output_df_all.loc[nglac,'area_km2']*1e6) -# output_df_all.loc[nglac,'mb_total_gta_obs'] = output_df_all.loc[nglac,'mb_clim_gta_obs'] - output_df_all.loc[nglac,'calving_flux_Gta'] -# output_df_all.loc[nglac,'mb_total_mwea_obs'] = gta_to_mwea(output_df_all.loc[nglac,'mb_total_gta_obs'], -# output_df_all.loc[nglac,'area_km2']*1e6) -# print(mb_data_reg.loc[mb_idx,'RGIId'], rgiid) -# -## mb_clim_reg_avg_1std = mb_clim_reg_avg + mb_clim_reg_std -## print('clim threshold:', np.round(mb{})) -# #%% - - output_fn_missing = output_fn.replace('.csv','-missing.csv') - - main_glac_rgi_all = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], rgi_regionsO2='all', - rgi_glac_number='all', - include_landterm=False, include_laketerm=False, - include_tidewater=True) + # #%% STATISTICS FOR CALIBRATED GLACIERS + # for nglac, rgiid in enumerate(output_df_all.RGIId.values): + # mb_rgiids = list(mb_data_reg.RGIId.values) + # mb_idx = mb_rgiids.index(rgiid) + # output_df_all.loc[nglac,'mb_clim_mwea_obs'] = mb_data_reg.loc[mb_idx,'mb_mwea'] + # output_df_all.loc[nglac,'mb_clim_gta_obs'] = mwea_to_gta(output_df_all.loc[nglac,'mb_clim_mwea_obs'], + # output_df_all.loc[nglac,'area_km2']*1e6) + # output_df_all.loc[nglac,'mb_total_gta_obs'] = output_df_all.loc[nglac,'mb_clim_gta_obs'] - output_df_all.loc[nglac,'calving_flux_Gta'] + # output_df_all.loc[nglac,'mb_total_mwea_obs'] = gta_to_mwea(output_df_all.loc[nglac,'mb_total_gta_obs'], + # output_df_all.loc[nglac,'area_km2']*1e6) + # print(mb_data_reg.loc[mb_idx,'RGIId'], rgiid) + # + ## mb_clim_reg_avg_1std = mb_clim_reg_avg + mb_clim_reg_std + ## print('clim threshold:', np.round(mb{})) + # #%% + + output_fn_missing = output_fn.replace(".csv", "-missing.csv") + + main_glac_rgi_all = modelsetup.selectglaciersrgitable( + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + include_landterm=False, + include_laketerm=False, + include_tidewater=True, + ) rgiids_processed = list(output_df_all.RGIId) rgiids_all = list(main_glac_rgi_all.RGIId) - rgiids_missing = [x for x in rgiids_all if x not in rgiids_processed] - - glac_no_missing = [x.split('-')[1] for x in rgiids_missing] - main_glac_rgi_missing = modelsetup.selectglaciersrgitable(glac_no=glac_no_missing) - - if verbose: print(reg, len(glac_no_missing), main_glac_rgi_missing.Area.sum(), glac_no_missing) - + rgiids_missing = [ + x for x in rgiids_all if x not in rgiids_processed + ] + + glac_no_missing = [x.split("-")[1] for x in rgiids_missing] + main_glac_rgi_missing = modelsetup.selectglaciersrgitable( + glac_no=glac_no_missing + ) + + if verbose: + print( + reg, + len(glac_no_missing), + main_glac_rgi_missing.Area.sum(), + glac_no_missing, + ) + if not os.path.exists(output_fp + output_fn_missing) or overwrite: - # Add regions for median subsets - output_df_all['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in output_df_all.RGIId] - + output_df_all["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) + for x in output_df_all.RGIId + ] + # Update mass balance data - output_df_missing = pd.DataFrame(np.zeros((len(rgiids_missing),len(output_df_all.columns))), columns=output_df_all.columns) - output_df_missing['RGIId'] = rgiids_missing - output_df_missing['fa_gta_obs'] = np.nan - rgi_area_dict = dict(zip(main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area)) - output_df_missing['area_km2'] = output_df_missing['RGIId'].map(rgi_area_dict) - rgi_mbobs_dict = dict(zip(mb_data['rgiid'],mb_data['mb_mwea'])) - output_df_missing['mb_clim_mwea_obs'] = output_df_missing['RGIId'].map(rgi_mbobs_dict) - output_df_missing['mb_clim_gta_obs'] = [mwea_to_gta(output_df_missing.loc[x,'mb_clim_mwea_obs'], - output_df_missing.loc[x,'area_km2']*1e6) for x in output_df_missing.index] - output_df_missing['mb_total_mwea_obs'] = output_df_missing['mb_clim_mwea_obs'] - output_df_missing['mb_total_gta_obs'] = output_df_missing['mb_clim_gta_obs'] - + output_df_missing = pd.DataFrame( + np.zeros( + (len(rgiids_missing), len(output_df_all.columns)) + ), + columns=output_df_all.columns, + ) + output_df_missing["RGIId"] = rgiids_missing + output_df_missing["fa_gta_obs"] = np.nan + rgi_area_dict = dict( + zip( + main_glac_rgi_missing.RGIId, main_glac_rgi_missing.Area + ) + ) + output_df_missing["area_km2"] = output_df_missing["RGIId"].map( + rgi_area_dict + ) + rgi_mbobs_dict = dict( + zip(mb_data["rgiid"], mb_data["mb_mwea"]) + ) + output_df_missing["mb_clim_mwea_obs"] = output_df_missing[ + "RGIId" + ].map(rgi_mbobs_dict) + output_df_missing["mb_clim_gta_obs"] = [ + mwea_to_gta( + output_df_missing.loc[x, "mb_clim_mwea_obs"], + output_df_missing.loc[x, "area_km2"] * 1e6, + ) + for x in output_df_missing.index + ] + output_df_missing["mb_total_mwea_obs"] = output_df_missing[ + "mb_clim_mwea_obs" + ] + output_df_missing["mb_total_gta_obs"] = output_df_missing[ + "mb_clim_gta_obs" + ] + # Uncertainty with calving_k based on regional calibration -# calving_k_nmad_missing = 1.4826 * median_abs_deviation(output_df_all.calving_k) - calving_k_nmad_missing = np.median(output_df_all_good.calving_k_nmad) - output_df_missing['calving_k_nmad'] = calving_k_nmad_missing - + # calving_k_nmad_missing = 1.4826 * median_abs_deviation(output_df_all.calving_k) + calving_k_nmad_missing = np.median( + output_df_all_good.calving_k_nmad + ) + output_df_missing["calving_k_nmad"] = calving_k_nmad_missing + # Check that climatic mass balance is reasonable - mb_clim_reg_95 = (mb_clim_reg_avg + 1.96*mb_clim_reg_std) - + mb_clim_reg_95 = mb_clim_reg_avg + 1.96 * mb_clim_reg_std + # Start with median value - calving_k_med = np.median(output_df_all.loc[output_df_all['O1Region']==reg,'calving_k']) + calving_k_med = np.median( + output_df_all.loc[ + output_df_all["O1Region"] == reg, "calving_k" + ] + ) for nglac, rgiid in enumerate(rgiids_missing): try: - main_glac_rgi_ind = modelsetup.selectglaciersrgitable(glac_no=[rgiid.split('-')[1]]) - area_km2 = main_glac_rgi_ind.loc[0,'Area'] + main_glac_rgi_ind = modelsetup.selectglaciersrgitable( + glac_no=[rgiid.split("-")[1]] + ) + area_km2 = main_glac_rgi_ind.loc[0, "Area"] # Estimate frontal ablation for missing glaciers output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_med, args, debug=True, calc_mb_geo_correction=True)) - + reg_calving_flux( + main_glac_rgi_ind, + calving_k_med, + args, + debug=True, + calc_mb_geo_correction=True, + ) + ) + # ASSUME THE TOTAL MASS BALANCE EQUALS THE GEODETIC MASS BALANCE CORRECTED FOR THE FA BELOW SEA LEVEL - mb_total_mwea = output_df_missing.loc[nglac,'mb_total_mwea_obs'] - mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) + mb_total_mwea = output_df_missing.loc[ + nglac, "mb_total_mwea_obs" + ] + mb_fa_mwea = gta_to_mwea( + output_df.loc[0, "calving_flux_Gta"], + area_km2 * 1e6, + ) mb_clim_mwea = mb_total_mwea + mb_fa_mwea - + if verbose: - print('mb_total_mwea:', np.round(mb_total_mwea,2)) - print('mb_clim_mwea:', np.round(mb_clim_mwea,2)) - print('mb_fa_mwea:', np.round(mb_fa_mwea,2)) - print('mb_clim (reg 95%):', np.round(mb_clim_reg_95,2)) -# print('mb_total (95% min):', np.round(mb_clim_reg_3std_min,2)) - + print("mb_total_mwea:", np.round(mb_total_mwea, 2)) + print("mb_clim_mwea:", np.round(mb_clim_mwea, 2)) + print("mb_fa_mwea:", np.round(mb_fa_mwea, 2)) + print( + "mb_clim (reg 95%):", + np.round(mb_clim_reg_95, 2), + ) + # print('mb_total (95% min):', np.round(mb_clim_reg_3std_min,2)) + if mb_clim_mwea < mb_clim_reg_95: - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_mwea - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], area_km2*1e6) - output_df_missing.loc[nglac,'mb_total_mwea'] = mb_total_mwea - output_df_missing.loc[nglac,'mb_total_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_total_gta'], area_km2*1e6) + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + output_df_missing.loc[nglac, "mb_clim_mwea"] = ( + mb_clim_mwea + ) + output_df_missing.loc[nglac, "mb_clim_gta"] = ( + mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + area_km2 * 1e6, + ) + ) + output_df_missing.loc[nglac, "mb_total_mwea"] = ( + mb_total_mwea + ) + output_df_missing.loc[nglac, "mb_total_gta"] = ( + mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_total_gta" + ], + area_km2 * 1e6, + ) + ) else: - # Calibrate frontal ablation based on fa_mwea_max # i.e., the maximum frontal ablation that is consistent with reasonable mb_clim - fa_mwea_max = mb_fa_mwea - (mb_clim_mwea - mb_clim_reg_95) - + fa_mwea_max = mb_fa_mwea - ( + mb_clim_mwea - mb_clim_reg_95 + ) + # If mb_clim_mwea is already greater than mb_clim_reg_95, then going to have this be positive # therefore, correct it to only let it be 10% of the positive mb_total such that it stays "reasonable" if fa_mwea_max < 0: - if verbose: print('\n too positive, limiting fa_mwea_max to 10% mb_total_mwea') - fa_mwea_max = 0.1*mb_total_mwea - + if verbose: + print( + "\n too positive, limiting fa_mwea_max to 10% mb_total_mwea" + ) + fa_mwea_max = 0.1 * mb_total_mwea + # Reset bounds calving_k = np.copy(calving_k_med) calving_k_bndlow = np.copy(calving_k_bndlow_set) calving_k_bndhigh = np.copy(calving_k_bndhigh_set) calving_k_step = np.copy(calving_k_step_set) - + # Select individual glacier - rgiid_ind = main_glac_rgi_ind.loc[0,'RGIId'] - # fa_glac_data_ind = pd.DataFrame(np.zeros((1,len(fa_glac_data_reg.columns))), + rgiid_ind = main_glac_rgi_ind.loc[0, "RGIId"] + # fa_glac_data_ind = pd.DataFrame(np.zeros((1,len(fa_glac_data_reg.columns))), # columns=fa_glac_data_reg.columns) - fa_glac_data_ind = pd.DataFrame(columns=fa_glac_data_reg.columns) - fa_glac_data_ind.loc[0,'RGIId'] = rgiid_ind - + fa_glac_data_ind = pd.DataFrame( + columns=fa_glac_data_reg.columns + ) + fa_glac_data_ind.loc[0, "RGIId"] = rgiid_ind + # Check bounds bndlow_good = True bndhigh_good = True try: - output_df_bndhigh, reg_calving_gta_mod_bndhigh, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndhigh, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndhigh, + reg_calving_gta_mod_bndhigh, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndhigh, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndhigh_good = False reg_calving_gta_mod_bndhigh = None - + try: - output_df_bndlow, reg_calving_gta_mod_bndlow, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k_bndlow, args, fa_glac_data_reg=fa_glac_data_ind, - ignore_nan=False, calc_mb_geo_correction=True)) + ( + output_df_bndlow, + reg_calving_gta_mod_bndlow, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k_bndlow, + args, + fa_glac_data_reg=fa_glac_data_ind, + ignore_nan=False, + calc_mb_geo_correction=True, + ) except: bndlow_good = False reg_calving_gta_mod_bndlow = None - + # Record bounds - output_df_missing.loc[nglac,'calving_flux_Gta_bndlow'] = reg_calving_gta_mod_bndlow - output_df_missing.loc[nglac,'calving_flux_Gta_bndhigh'] = reg_calving_gta_mod_bndhigh - + output_df_missing.loc[ + nglac, "calving_flux_Gta_bndlow" + ] = reg_calving_gta_mod_bndlow + output_df_missing.loc[ + nglac, "calving_flux_Gta_bndhigh" + ] = reg_calving_gta_mod_bndhigh + if verbose: - print(' fa_model_bndlow [mwea] :', np.round(gta_to_mwea(reg_calving_gta_mod_bndlow, area_km2*1e6),2)) - print(' fa_model_bndhigh [mwea] :', np.round(gta_to_mwea(reg_calving_gta_mod_bndhigh,area_km2*1e6),2)) - print(' fa_mwea_cal [mwea]:', np.round(fa_mwea_max,2)) - + print( + " fa_model_bndlow [mwea] :", + np.round( + gta_to_mwea( + reg_calving_gta_mod_bndlow, + area_km2 * 1e6, + ), + 2, + ), + ) + print( + " fa_model_bndhigh [mwea] :", + np.round( + gta_to_mwea( + reg_calving_gta_mod_bndhigh, + area_km2 * 1e6, + ), + 2, + ), + ) + print( + " fa_mwea_cal [mwea]:", + np.round(fa_mwea_max, 2), + ) + if bndhigh_good and bndlow_good: if verbose: - print('\n-------') - print('mb_clim_mwea:', np.round(mb_clim_mwea,2)) + print("\n-------") + print( + "mb_clim_mwea:", + np.round(mb_clim_mwea, 2), + ) - calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 - calving_k_next = calving_k - calving_k_step_missing + calving_k_step_missing = ( + calving_k_med - calving_k_bndlow + ) / 20 + calving_k_next = ( + calving_k - calving_k_step_missing + ) ncount = 0 - while mb_fa_mwea > fa_mwea_max and calving_k_next > 0: + while ( + mb_fa_mwea > fa_mwea_max + and calving_k_next > 0 + ): calving_k -= calving_k_step_missing - + if ncount == 0: - reset_gdir=True + reset_gdir = True else: - reset_gdir=False + reset_gdir = False # Estimate frontal ablation for missing glaciers - output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( - reg_calving_flux(main_glac_rgi_ind, calving_k, args, debug=True, calc_mb_geo_correction=True, reset_gdir=reset_gdir)) - - mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) - - calving_k_next = calving_k - calving_k_step_missing - - if verbose: print(calving_k, 'mb_fa_mwea:', np.round(mb_fa_mwea,2), 'mb_fa_mwea_max:', np.round(fa_mwea_max,2)) - + ( + output_df, + reg_calving_gta_mod, + reg_calving_gta_obs, + ) = reg_calving_flux( + main_glac_rgi_ind, + calving_k, + args, + debug=True, + calc_mb_geo_correction=True, + reset_gdir=reset_gdir, + ) + + mb_fa_mwea = gta_to_mwea( + output_df.loc[0, "calving_flux_Gta"], + area_km2 * 1e6, + ) + + calving_k_next = ( + calving_k - calving_k_step_missing + ) + + if verbose: + print( + calving_k, + "mb_fa_mwea:", + np.round(mb_fa_mwea, 2), + "mb_fa_mwea_max:", + np.round(fa_mwea_max, 2), + ) + # Record output - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + mb_clim_mwea = mb_total_mwea + mb_fa_mwea if verbose: - print('mb_total_mwea:', np.round(mb_total_mwea,2)) - print('mb_clim_mwea:', np.round(mb_clim_mwea,2)) - print('mb_fa_mwea:', np.round(mb_fa_mwea,2)) - print('mb_clim (reg 95%):', np.round(mb_clim_reg_95,2)) - - for cn in ['calving_k', 'calving_thick', 'calving_flux_Gta', 'no_errors', 'oggm_dynamics']: - output_df_missing.loc[nglac,cn] = output_df.loc[0,cn] - output_df_missing.loc[nglac,'mb_clim_mwea'] = mb_clim_mwea - output_df_missing.loc[nglac,'mb_clim_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_clim_mwea'], area_km2*1e6) - output_df_missing.loc[nglac,'mb_total_mwea'] = mb_total_mwea - output_df_missing.loc[nglac,'mb_total_gta'] = mwea_to_gta(output_df_missing.loc[nglac,'mb_total_mwea'], area_km2*1e6) + print( + "mb_total_mwea:", + np.round(mb_total_mwea, 2), + ) + print( + "mb_clim_mwea:", + np.round(mb_clim_mwea, 2), + ) + print( + "mb_fa_mwea:", np.round(mb_fa_mwea, 2) + ) + print( + "mb_clim (reg 95%):", + np.round(mb_clim_reg_95, 2), + ) + + for cn in [ + "calving_k", + "calving_thick", + "calving_flux_Gta", + "no_errors", + "oggm_dynamics", + ]: + output_df_missing.loc[nglac, cn] = ( + output_df.loc[0, cn] + ) + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ] = mb_clim_mwea + output_df_missing.loc[nglac, "mb_clim_gta"] = ( + mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_clim_mwea" + ], + area_km2 * 1e6, + ) + ) + output_df_missing.loc[ + nglac, "mb_total_mwea" + ] = mb_total_mwea + output_df_missing.loc[ + nglac, "mb_total_gta" + ] = mwea_to_gta( + output_df_missing.loc[ + nglac, "mb_total_mwea" + ], + area_km2 * 1e6, + ) # Adjust calving_k_nmad if calving_k is very low to avoid poor values - if output_df_missing.loc[nglac,'calving_k'] < calving_k_nmad_missing: - output_df_missing.loc[nglac,'calving_k_nmad'] = output_df_missing.loc[nglac,'calving_k'] - calving_k_bndlow_set - -# # Check uncertainty based on NMAD -# calving_k_plusnmad = calving_k_med + calving_k_nmad -# output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( -# reg_calving_flux(main_glac_rgi_ind, calving_k_plusnmad, debug=True, calc_mb_geo_correction=True)) -# mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) -# print('mb_fa_mwea (calving_k + nmad):', np.round(mb_fa_mwea,2)) -# -# calving_k_minusnmad = calving_k_med - calving_k_nmad -# output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( -# reg_calving_flux(main_glac_rgi_ind, calving_k_minusnmad, debug=True, calc_mb_geo_correction=True)) -# mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) -# print('mb_fa_mwea (calving_k - nmad):', np.round(mb_fa_mwea,2)) - -# calving_k_values = [0.001, 0.01, 0.1, 0.15, 0.18, 0.2, 0.25, 0.3, 0.35] -# mb_fa_mwea_list = [] -# for calving_k in calving_k_values: -# output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( -# reg_calving_flux(main_glac_rgi_ind, calving_k, debug=True, calc_mb_geo_correction=True)) -# mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) -# mb_fa_mwea_list.append(mb_fa_mwea) -# print(calving_k, 'mb_fa_mwea (calving_k - nmad):', np.round(mb_fa_mwea,2)) -# # Set up your plot (and/or subplots) -# fig, ax = plt.subplots(1, 1, squeeze=False, sharex=False, sharey=False, gridspec_kw = {'wspace':0.4, 'hspace':0.15}) -# ax[0,0].scatter(calving_k_values, mb_fa_mwea_list, color='k', linewidth=1, zorder=2, label='plot1') -# ax[0,0].set_xlabel('calving_k', size=12) -# ax[0,0].set_ylabel('mb_fa_mwea', size=12) -# plt.show() + if ( + output_df_missing.loc[nglac, "calving_k"] + < calving_k_nmad_missing + ): + output_df_missing.loc[nglac, "calving_k_nmad"] = ( + output_df_missing.loc[nglac, "calving_k"] + - calving_k_bndlow_set + ) + + # # Check uncertainty based on NMAD + # calving_k_plusnmad = calving_k_med + calving_k_nmad + # output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( + # reg_calving_flux(main_glac_rgi_ind, calving_k_plusnmad, debug=True, calc_mb_geo_correction=True)) + # mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) + # print('mb_fa_mwea (calving_k + nmad):', np.round(mb_fa_mwea,2)) + # + # calving_k_minusnmad = calving_k_med - calving_k_nmad + # output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( + # reg_calving_flux(main_glac_rgi_ind, calving_k_minusnmad, debug=True, calc_mb_geo_correction=True)) + # mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) + # print('mb_fa_mwea (calving_k - nmad):', np.round(mb_fa_mwea,2)) + + # calving_k_values = [0.001, 0.01, 0.1, 0.15, 0.18, 0.2, 0.25, 0.3, 0.35] + # mb_fa_mwea_list = [] + # for calving_k in calving_k_values: + # output_df, reg_calving_gta_mod, reg_calving_gta_obs = ( + # reg_calving_flux(main_glac_rgi_ind, calving_k, debug=True, calc_mb_geo_correction=True)) + # mb_fa_mwea = gta_to_mwea(output_df.loc[0,'calving_flux_Gta'], area_km2*1e6) + # mb_fa_mwea_list.append(mb_fa_mwea) + # print(calving_k, 'mb_fa_mwea (calving_k - nmad):', np.round(mb_fa_mwea,2)) + # # Set up your plot (and/or subplots) + # fig, ax = plt.subplots(1, 1, squeeze=False, sharex=False, sharey=False, gridspec_kw = {'wspace':0.4, 'hspace':0.15}) + # ax[0,0].scatter(calving_k_values, mb_fa_mwea_list, color='k', linewidth=1, zorder=2, label='plot1') + # ax[0,0].set_xlabel('calving_k', size=12) + # ax[0,0].set_ylabel('mb_fa_mwea', size=12) + # plt.show() except: pass - # Export - output_df_missing.to_csv(output_fp + output_fn_missing, index=False) + # Export + output_df_missing.to_csv( + output_fp + output_fn_missing, index=False + ) else: output_df_missing = pd.read_csv(output_fp + output_fn_missing) # ----- PLOT RESULTS FOR EACH GLACIER ----- - with np.errstate(all='ignore'): - plot_max_raw = np.max([output_df_all.calving_flux_Gta.max(), output_df_all.fa_gta_obs.max()]) - plot_max = 10**np.ceil(np.log10(plot_max_raw)) - - plot_min_raw = np.max([output_df_all.calving_flux_Gta.min(), output_df_all.fa_gta_obs.min()]) - plot_min = 10**np.floor(np.log10(plot_min_raw)) + with np.errstate(all="ignore"): + plot_max_raw = np.max( + [ + output_df_all.calving_flux_Gta.max(), + output_df_all.fa_gta_obs.max(), + ] + ) + plot_max = 10 ** np.ceil(np.log10(plot_max_raw)) + + plot_min_raw = np.max( + [ + output_df_all.calving_flux_Gta.min(), + output_df_all.fa_gta_obs.min(), + ] + ) + plot_min = 10 ** np.floor(np.log10(plot_min_raw)) if plot_min < 1e-3: plot_min = 1e-4 x_min, x_max = plot_min, plot_max - - fig, ax = plt.subplots(2, 2, squeeze=False, gridspec_kw = {'wspace':0.3, 'hspace':0.3}) - + + fig, ax = plt.subplots( + 2, 2, squeeze=False, gridspec_kw={"wspace": 0.3, "hspace": 0.3} + ) + # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all['area_km2'].values - s_sizes = [10,50,250,1000] + glac_area_all = output_df_all["area_km2"].values + s_sizes = [10, 50, 250, 1000] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - - sc = ax[0,0].scatter(output_df_all['fa_gta_obs'], output_df_all['calving_flux_Gta'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) + + sc = ax[0, 0].scatter( + output_df_all["fa_gta_obs"], + output_df_all["calving_flux_Gta"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) # Labels - ax[0,0].set_xlabel('Observed $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_ylabel('Modeled $A_{f}$ (Gt/yr)', size=12) - ax[0,0].set_xlim(x_min,x_max) - ax[0,0].set_ylim(x_min,x_max) - ax[0,0].plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) + ax[0, 0].set_xlabel("Observed $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_ylabel("Modeled $A_{f}$ (Gt/yr)", size=12) + ax[0, 0].set_xlim(x_min, x_max) + ax[0, 0].set_ylim(x_min, x_max) + ax[0, 0].plot( + [x_min, x_max], [x_min, x_max], color="k", linewidth=0.5, zorder=1 + ) # Log scale - ax[0,0].set_xscale('log') - ax[0,0].set_yscale('log') - + ax[0, 0].set_xscale("log") + ax[0, 0].set_yscale("log") + # Legend - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = ["< 10", "10-10$^{2}$", "10$^{2}$-10$^{3}$", "> 10$^{3}$"] for nlabel, obs_label in enumerate(obs_labels): - ax[0,0].scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax[0,0].text(0.06, 0.98, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax[0,0].transAxes, color='grey') - leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') -# ax[0,0].text(1.08, 0.97, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', -# transform=ax[0,0].transAxes) -# leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, -# handletextpad=1, borderpad=0.25, labelspacing=1, bbox_to_anchor=(1.035, 0.9)) - + ax[0, 0].scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax[0, 0].text( + 0.06, + 0.98, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax[0, 0].transAxes, + color="grey", + ) + leg = ax[0, 0].legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) + # ax[0,0].text(1.08, 0.97, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', + # transform=ax[0,0].transAxes) + # leg = ax[0,0].legend(loc='upper left', ncol=1, fontsize=10, frameon=False, + # handletextpad=1, borderpad=0.25, labelspacing=1, bbox_to_anchor=(1.035, 0.9)) + # ----- Histogram ----- -# nbins = 25 -# ax[0,1].hist(output_df_all['calving_k'], bins=nbins, color='grey', edgecolor='k') - vn_bins = np.arange(0, np.max([1,output_df_all.calving_k.max()]) + 0.1, 0.1) - hist, bins = np.histogram(output_df_all.loc[output_df_all['no_errors'] == 1, 'calving_k'], bins=vn_bins) - ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - align='center', edgecolor='black', color='grey') - ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - ax[0,1].set_xticks(vn_bins, minor=True) - ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # nbins = 25 + # ax[0,1].hist(output_df_all['calving_k'], bins=nbins, color='grey', edgecolor='k') + vn_bins = np.arange( + 0, np.max([1, output_df_all.calving_k.max()]) + 0.1, 0.1 + ) + hist, bins = np.histogram( + output_df_all.loc[output_df_all["no_errors"] == 1, "calving_k"], + bins=vn_bins, + ) + ax[0, 1].bar( + x=vn_bins[:-1] + 0.1 / 2, + height=hist, + width=(bins[1] - bins[0]), + align="center", + edgecolor="black", + color="grey", + ) + ax[0, 1].set_xticks(np.arange(0, np.max([1, vn_bins.max()]) + 0.1, 1)) + ax[0, 1].set_xticks(vn_bins, minor=True) + ax[0, 1].set_xlim(vn_bins.min(), np.max([1, vn_bins.max()])) if hist.max() < 40: y_major_interval = 5 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) elif hist.max() > 40: y_major_interval = 10 - y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - + y_max = np.ceil(hist.max() / y_major_interval) * y_major_interval + ax[0, 1].set_yticks( + np.arange(0, y_max + y_major_interval, y_major_interval) + ) + # Labels - ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[0,1].set_ylabel('Count (glaciers)', size=12) - + ax[0, 1].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[0, 1].set_ylabel("Count (glaciers)", size=12) + # ----- CALVING_K VS MB_CLIM ----- - ax[1,0].scatter(output_df_all['calving_k'], output_df_all['mb_clim_mwea'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,0].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - ax[1,0].set_ylabel('$B_{clim}$ (mwea)', size=12) - + ax[1, 0].scatter( + output_df_all["calving_k"], + output_df_all["mb_clim_mwea"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 0].set_xlabel("$k_{f}$ (yr$^{-1}$)", size=12) + ax[1, 0].set_ylabel("$B_{clim}$ (mwea)", size=12) + # ----- CALVING_K VS AREA ----- - ax[1,1].scatter(output_df_all['area_km2'], output_df_all['calving_k'], - color='k', marker='o', linewidth=1, facecolor='none', - s=s_byarea, clip_on=True) - ax[1,1].set_xlabel('Area (km2)', size=12) - ax[1,1].set_ylabel('$k_{f}$ (yr$^{-1}$)', size=12) - - + ax[1, 1].scatter( + output_df_all["area_km2"], + output_df_all["calving_k"], + color="k", + marker="o", + linewidth=1, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + ax[1, 1].set_xlabel("Area (km2)", size=12) + ax[1, 1].set_ylabel("$k_{f}$ (yr$^{-1}$)", size=12) + # Save figure - fig.set_size_inches(6,6) - fig_fullfn = output_fp + str(reg) + '-frontalablation_glac_compare-cal_ind.png' - fig.savefig(fig_fullfn, bbox_inches='tight', dpi=300) + fig.set_size_inches(6, 6) + fig_fullfn = ( + output_fp + str(reg) + "-frontalablation_glac_compare-cal_ind.png" + ) + fig.savefig(fig_fullfn, bbox_inches="tight", dpi=300) plt.close(fig) # ----- MERGE CALIBRATED CALVING DATASETS ----- -def merge_ind_calving_k(regions=list(range(1,20)), output_fp='', merged_calving_k_fn='', verbose=False): +def merge_ind_calving_k( + regions=list(range(1, 20)), + output_fp="", + merged_calving_k_fn="", + verbose=False, +): # get list of all regional frontal ablation calibration file names - output_reg_fns = sorted(glob.glob(f'{output_fp}/*-frontalablation_cal_ind.csv')) - output_reg_fns = [x.split('/')[-1] for x in output_reg_fns] + output_reg_fns = sorted( + glob.glob(f"{output_fp}/*-frontalablation_cal_ind.csv") + ) + output_reg_fns = [x.split("/")[-1] for x in output_reg_fns] # loop through and merge for nreg, output_fn_reg in enumerate(output_reg_fns): - - # Load quality controlled frontal ablation data + # Load quality controlled frontal ablation data output_df_reg = pd.read_csv(output_fp + output_fn_reg) - - if not 'calving_k_nmad' in list(output_df_reg.columns): - output_df_reg['calving_k_nmad'] = 0 - + + if "calving_k_nmad" not in list(output_df_reg.columns): + output_df_reg["calving_k_nmad"] = 0 + if nreg == 0: output_df_all = output_df_reg else: output_df_all = pd.concat([output_df_all, output_df_reg], axis=0) - - output_fn_reg_missing = output_fn_reg.replace('.csv','-missing.csv') + + output_fn_reg_missing = output_fn_reg.replace(".csv", "-missing.csv") if os.path.exists(output_fp + output_fn_reg_missing): - # Check if second correction exists - output_fn_reg_missing_v2 = output_fn_reg_missing.replace('.csv','_wmbtotal_correction.csv') - if os.path.exists(output_fp + output_fn_reg_missing_v2): - output_df_reg_missing = pd.read_csv(output_fp + output_fn_reg_missing_v2) + output_fn_reg_missing_v2 = output_fn_reg_missing.replace( + ".csv", "_wmbtotal_correction.csv" + ) + if os.path.exists(output_fp + output_fn_reg_missing_v2): + output_df_reg_missing = pd.read_csv( + output_fp + output_fn_reg_missing_v2 + ) else: - output_df_reg_missing = pd.read_csv(output_fp + output_fn_reg_missing) - - if not 'calving_k_nmad' in list(output_df_reg_missing.columns): - output_df_reg_missing['calving_k_nmad'] = 0 - - output_df_all = pd.concat([output_df_all, output_df_reg_missing], axis=0) - + output_df_reg_missing = pd.read_csv( + output_fp + output_fn_reg_missing + ) + + if "calving_k_nmad" not in list(output_df_reg_missing.columns): + output_df_reg_missing["calving_k_nmad"] = 0 + + output_df_all = pd.concat( + [output_df_all, output_df_reg_missing], axis=0 + ) + output_df_all.to_csv(output_fp + merged_calving_k_fn, index=0) if verbose: - print(f'Merged calving calibration exported: {output_fp+merged_calving_k_fn}') - + print( + f"Merged calving calibration exported: {output_fp + merged_calving_k_fn}" + ) + return # ----- UPDATE MASS BALANCE DATA WITH FRONTAL ABLATION ESTIMATES ----- -def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablation_fn='', hugonnet2021_fp='', hugonnet2021_facorr_fp='', ncores=1, overwrite=False, verbose=False): +def update_mbdata( + regions=list(range(1, 20)), + frontalablation_fp="", + frontalablation_fn="", + hugonnet2021_fp="", + hugonnet2021_facorr_fp="", + ncores=1, + overwrite=False, + verbose=False, +): # Load calving glacier data (already quality controlled during calibration) - assert os.path.exists(frontalablation_fp + frontalablation_fn), 'Calibrated frontal ablation output dataset does not exist' + assert os.path.exists(frontalablation_fp + frontalablation_fn), ( + "Calibrated frontal ablation output dataset does not exist" + ) fa_glac_data = pd.read_csv(frontalablation_fp + frontalablation_fn) # check if fa corrected mass balance data already exists if os.path.exists(hugonnet2021_facorr_fp): - assert overwrite, f'Frontal ablation corrected mass balance dataset already exists!\t{hugonnet2021_facorr_fp}\nPass `-o` to overwrite, or pass a different filename for `hugonnet2021_facorrected_fn`' + assert overwrite, ( + f"Frontal ablation corrected mass balance dataset already exists!\t{hugonnet2021_facorr_fp}\nPass `-o` to overwrite, or pass a different filename for `hugonnet2021_facorrected_fn`" + ) mb_data = pd.read_csv(hugonnet2021_facorr_fp) else: mb_data = pd.read_csv(hugonnet2021_fp) mb_rgiids = list(mb_data.rgiid) # Record prior data - mb_data['mb_romain_mwea'] = mb_data['mb_mwea'] - mb_data['mb_romain_mwea_err'] = mb_data['mb_mwea_err'] - mb_data['mb_clim_mwea'] = mb_data['mb_mwea'] - mb_data['mb_clim_mwea_err'] = mb_data['mb_mwea_err'] + mb_data["mb_romain_mwea"] = mb_data["mb_mwea"] + mb_data["mb_romain_mwea_err"] = mb_data["mb_mwea_err"] + mb_data["mb_clim_mwea"] = mb_data["mb_mwea"] + mb_data["mb_clim_mwea_err"] = mb_data["mb_mwea_err"] # Update mass balance data for nglac, rgiid in enumerate(fa_glac_data.RGIId): - - O1region = int(rgiid.split('-')[1].split('.')[0]) - if O1region in regions: - + O1region = int(rgiid.split("-")[1].split(".")[0]) + if O1region in regions: # Update the mass balance data in Romain's file mb_idx = mb_rgiids.index(rgiid) - mb_data.loc[mb_idx,'mb_mwea'] = fa_glac_data.loc[nglac,'mb_total_mwea'] - mb_data.loc[mb_idx,'mb_clim_mwea'] = fa_glac_data.loc[nglac,'mb_clim_mwea'] - + mb_data.loc[mb_idx, "mb_mwea"] = fa_glac_data.loc[ + nglac, "mb_total_mwea" + ] + mb_data.loc[mb_idx, "mb_clim_mwea"] = fa_glac_data.loc[ + nglac, "mb_clim_mwea" + ] + if verbose: - print(rgiid, 'mb_mwea:', np.round(mb_data.loc[mb_idx,'mb_mwea'],2), - 'mb_clim:', np.round(mb_data.loc[mb_idx,'mb_clim_mwea'],2), - 'mb_romain:', np.round(mb_data.loc[mb_idx,'mb_romain_mwea'],2)) + print( + rgiid, + "mb_mwea:", + np.round(mb_data.loc[mb_idx, "mb_mwea"], 2), + "mb_clim:", + np.round(mb_data.loc[mb_idx, "mb_clim_mwea"], 2), + "mb_romain:", + np.round(mb_data.loc[mb_idx, "mb_romain_mwea"], 2), + ) # Export the updated dataset mb_data.to_csv(hugonnet2021_facorr_fp, index=False) @@ -1759,15 +3183,17 @@ def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablat # Update gdirs glac_strs = [] for nglac, rgiid in enumerate(fa_glac_data.RGIId): - - O1region = int(rgiid.split('-')[1].split('.')[0]) - if O1region in regions: - + O1region = int(rgiid.split("-")[1].split(".")[0]) + if O1region in regions: # Select subsets of data - glacier_str = rgiid.split('-')[1] + glacier_str = rgiid.split("-")[1] glac_strs.append(glacier_str) # paralllelize - func_ = partial(single_flowline_glacier_directory_with_calving, reset=True, facorrected=True) + func_ = partial( + single_flowline_glacier_directory_with_calving, + reset=True, + facorrected=True, + ) with multiprocessing.Pool(ncores) as p: p.map(func_, glac_strs) @@ -1775,165 +3201,278 @@ def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablat # plot calving_k by region -def plot_calving_k_allregions(output_fp=''): - +def plot_calving_k_allregions(output_fp=""): fig = plt.figure() - gs = fig.add_gridspec(nrows=3,ncols=3,wspace=0.4,hspace=0.4) - ax1 = fig.add_subplot(gs[0,0]) - ax2 = fig.add_subplot(gs[0,1]) - ax3 = fig.add_subplot(gs[0,2]) - ax4 = fig.add_subplot(gs[1,0]) - ax5 = fig.add_subplot(gs[1,1]) - ax6 = fig.add_subplot(gs[1,2]) - ax7 = fig.add_subplot(gs[2,0]) - ax8 = fig.add_subplot(gs[2,1]) - ax9 = fig.add_subplot(gs[2,2]) - - regions_ordered = [1,3,4,5,7,9,17,19] - for nax, ax in enumerate([ax1,ax2,ax3,ax4,ax5,ax6,ax7,ax8, ax9]): - + gs = fig.add_gridspec(nrows=3, ncols=3, wspace=0.4, hspace=0.4) + ax1 = fig.add_subplot(gs[0, 0]) + ax2 = fig.add_subplot(gs[0, 1]) + ax3 = fig.add_subplot(gs[0, 2]) + ax4 = fig.add_subplot(gs[1, 0]) + ax5 = fig.add_subplot(gs[1, 1]) + ax6 = fig.add_subplot(gs[1, 2]) + ax7 = fig.add_subplot(gs[2, 0]) + ax8 = fig.add_subplot(gs[2, 1]) + ax9 = fig.add_subplot(gs[2, 2]) + + regions_ordered = [1, 3, 4, 5, 7, 9, 17, 19] + for nax, ax in enumerate([ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8, ax9]): if ax not in [ax9]: - reg = regions_ordered[nax] - - calving_k_fn = str(reg) + '-frontalablation_cal_ind.csv' - if not os.path.isfile(output_fp+calving_k_fn): + reg = regions_ordered[nax] + + calving_k_fn = str(reg) + "-frontalablation_cal_ind.csv" + if not os.path.isfile(output_fp + calving_k_fn): continue - output_df_all_good = pd.read_csv(output_fp + calving_k_fn) - + output_df_all_good = pd.read_csv(output_fp + calving_k_fn) + # ----- PLOT RESULTS FOR EACH GLACIER ----- - # plot_max_raw = np.max([output_df_all_good.calving_flux_Gta.max(), output_df_all_good.fa_gta_obs.max()]) - # plot_max = 10**np.ceil(np.log10(plot_max_raw)) - # - # plot_min_raw = np.max([output_df_all_good.calving_flux_Gta.min(), output_df_all_good.fa_gta_obs.min()]) - # plot_min = 10**np.floor(np.log10(plot_min_raw)) - # if plot_min < 1e-3: + # plot_max_raw = np.max([output_df_all_good.calving_flux_Gta.max(), output_df_all_good.fa_gta_obs.max()]) + # plot_max = 10**np.ceil(np.log10(plot_max_raw)) + # + # plot_min_raw = np.max([output_df_all_good.calving_flux_Gta.min(), output_df_all_good.fa_gta_obs.min()]) + # plot_min = 10**np.floor(np.log10(plot_min_raw)) + # if plot_min < 1e-3: plot_min = 1e-4 plot_max = 10 - + x_min, x_max = plot_min, plot_max - + # ----- Scatter plot ----- # Marker size - glac_area_all = output_df_all_good['area_km2'].values - s_sizes = [10,40,120,240] + glac_area_all = output_df_all_good["area_km2"].values + s_sizes = [10, 40, 120, 240] s_byarea = np.zeros(glac_area_all.shape) + s_sizes[3] s_byarea[(glac_area_all < 10)] = s_sizes[0] - s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[1] - s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = s_sizes[2] - - sc = ax.scatter(output_df_all_good['fa_gta_obs'], output_df_all_good['calving_flux_Gta'], - color='k', marker='o', linewidth=0.5, facecolor='none', - s=s_byarea, clip_on=True) - - ax.plot([x_min, x_max], [x_min, x_max], color='k', linewidth=0.5, zorder=1) - - ax.text(0.98, 1.02, rgi_reg_dict[reg], size=10, horizontalalignment='right', - verticalalignment='bottom', transform=ax.transAxes) - + s_byarea[(glac_area_all >= 10) & (glac_area_all < 100)] = s_sizes[ + 1 + ] + s_byarea[(glac_area_all >= 100) & (glac_area_all < 1000)] = ( + s_sizes[2] + ) + + sc = ax.scatter( + output_df_all_good["fa_gta_obs"], + output_df_all_good["calving_flux_Gta"], + color="k", + marker="o", + linewidth=0.5, + facecolor="none", + s=s_byarea, + clip_on=True, + ) + + ax.plot( + [x_min, x_max], + [x_min, x_max], + color="k", + linewidth=0.5, + zorder=1, + ) + + ax.text( + 0.98, + 1.02, + rgi_reg_dict[reg], + size=10, + horizontalalignment="right", + verticalalignment="bottom", + transform=ax.transAxes, + ) + # Labels - ax.set_xlim(x_min,x_max) - ax.set_ylim(x_min,x_max) + ax.set_xlim(x_min, x_max) + ax.set_ylim(x_min, x_max) # Log scale - ax.set_xscale('log') - ax.set_yscale('log') - - ax.tick_params(axis='both', which='major', direction='inout', right=True) - ax.tick_params(axis='both', which='minor', direction='in', right=True) - - # # ----- Histogram ----- - ## nbins = 25 - ## ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') - # vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) - # hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) - # ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), - # align='center', edgecolor='black', color='grey') - # ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) - # ax[0,1].set_xticks(vn_bins, minor=True) - # ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) - # if hist.max() < 40: - # y_major_interval = 5 - # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - # elif hist.max() > 40: - # y_major_interval = 10 - # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval - # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) - # - # # Labels - # ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) - # ax[0,1].set_ylabel('Count (glaciers)', size=12) - - # Plot - # ax.plot(years, reg_vol_med_norm, color=temp_colordict[deg_group], linestyle='-', - # linewidth=1, zorder=4, label=deg_group) - # ax.plot(years, reg_vol_med_norm_nocalving, color=temp_colordict[deg_group], linestyle=':', - # linewidth=1, zorder=3, label=None) - # - # if ax in [ax1, ax4, ax7]: - # ax.set_ylabel('Mass (rel. to 2015)') - # ax.set_xlim(startyear, endyear) - # ax.xaxis.set_major_locator(MultipleLocator(40)) - # ax.xaxis.set_minor_locator(MultipleLocator(10)) - # ax.set_ylim(0,1.1) - # ax.yaxis.set_major_locator(MultipleLocator(0.2)) - # ax.yaxis.set_minor_locator(MultipleLocator(0.1)) - + ax.set_xscale("log") + ax.set_yscale("log") + + ax.tick_params( + axis="both", which="major", direction="inout", right=True + ) + ax.tick_params(axis="both", which="minor", direction="in", right=True) + + # # ----- Histogram ----- + ## nbins = 25 + ## ax[0,1].hist(output_df_all_good['calving_k'], bins=nbins, color='grey', edgecolor='k') + # vn_bins = np.arange(0, np.max([1,output_df_all_good.calving_k.max()]) + 0.1, 0.1) + # hist, bins = np.histogram(output_df_all_good.loc[output_df_all_good['no_errors'] == 1, 'calving_k'], bins=vn_bins) + # ax[0,1].bar(x=vn_bins[:-1] + 0.1/2, height=hist, width=(bins[1]-bins[0]), + # align='center', edgecolor='black', color='grey') + # ax[0,1].set_xticks(np.arange(0,np.max([1,vn_bins.max()])+0.1, 1)) + # ax[0,1].set_xticks(vn_bins, minor=True) + # ax[0,1].set_xlim(vn_bins.min(), np.max([1,vn_bins.max()])) + # if hist.max() < 40: + # y_major_interval = 5 + # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval + # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + # elif hist.max() > 40: + # y_major_interval = 10 + # y_max = np.ceil(hist.max()/y_major_interval)*y_major_interval + # ax[0,1].set_yticks(np.arange(0,y_max+y_major_interval,y_major_interval)) + # + # # Labels + # ax[0,1].set_xlabel('$k_{f}$ (yr$^{-1}$)', size=12) + # ax[0,1].set_ylabel('Count (glaciers)', size=12) + + # Plot + # ax.plot(years, reg_vol_med_norm, color=temp_colordict[deg_group], linestyle='-', + # linewidth=1, zorder=4, label=deg_group) + # ax.plot(years, reg_vol_med_norm_nocalving, color=temp_colordict[deg_group], linestyle=':', + # linewidth=1, zorder=3, label=None) + # + # if ax in [ax1, ax4, ax7]: + # ax.set_ylabel('Mass (rel. to 2015)') + # ax.set_xlim(startyear, endyear) + # ax.xaxis.set_major_locator(MultipleLocator(40)) + # ax.xaxis.set_minor_locator(MultipleLocator(10)) + # ax.set_ylim(0,1.1) + # ax.yaxis.set_major_locator(MultipleLocator(0.2)) + # ax.yaxis.set_minor_locator(MultipleLocator(0.1)) + # Legend if ax in [ax9]: - obs_labels = ['< 10', '10-10$^{2}$', '10$^{2}$-10$^{3}$', '> 10$^{3}$'] + obs_labels = [ + "< 10", + "10-10$^{2}$", + "10$^{2}$-10$^{3}$", + "> 10$^{3}$", + ] for nlabel, obs_label in enumerate(obs_labels): - ax.scatter([-10],[-10], color='grey', marker='o', linewidth=1, - facecolor='none', s=s_sizes[nlabel], zorder=3, label=obs_label) - ax.text(0.1, 1.06, 'Area (km$^{2}$)', size=12, horizontalalignment='left', verticalalignment='top', - transform=ax.transAxes, color='grey') - leg = ax.legend(loc='upper left', ncol=1, fontsize=10, frameon=False, - handletextpad=1, borderpad=0.25, labelspacing=0.4, bbox_to_anchor=(0.0, 0.93), - labelcolor='grey') - - ax.spines['top'].set_visible(False) - ax.spines['right'].set_visible(False) - ax.spines['bottom'].set_visible(False) - ax.spines['left'].set_visible(False) + ax.scatter( + [-10], + [-10], + color="grey", + marker="o", + linewidth=1, + facecolor="none", + s=s_sizes[nlabel], + zorder=3, + label=obs_label, + ) + ax.text( + 0.1, + 1.06, + "Area (km$^{2}$)", + size=12, + horizontalalignment="left", + verticalalignment="top", + transform=ax.transAxes, + color="grey", + ) + leg = ax.legend( + loc="upper left", + ncol=1, + fontsize=10, + frameon=False, + handletextpad=1, + borderpad=0.25, + labelspacing=0.4, + bbox_to_anchor=(0.0, 0.93), + labelcolor="grey", + ) + + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["bottom"].set_visible(False) + ax.spines["left"].set_visible(False) ax.get_xaxis().set_ticks([]) ax.get_yaxis().set_ticks([]) - + # Labels - fig.text(0.5,0.04,'Observed frontal ablation (Gt yr$^{-1}$)', fontsize=12, horizontalalignment='center', verticalalignment='bottom') - fig.text(0.04,0.5,'Modeled frontal ablation (Gt yr$^{-1}$)', size=12, horizontalalignment='center', verticalalignment='center', rotation=90) - + fig.text( + 0.5, + 0.04, + "Observed frontal ablation (Gt yr$^{-1}$)", + fontsize=12, + horizontalalignment="center", + verticalalignment="bottom", + ) + fig.text( + 0.04, + 0.5, + "Modeled frontal ablation (Gt yr$^{-1}$)", + size=12, + horizontalalignment="center", + verticalalignment="center", + rotation=90, + ) + # Save figure - fig_fn = ('allregions_calving_ObsMod.png') - fig.set_size_inches(6.5,5.5) - fig.savefig(output_fp + fig_fn, bbox_inches='tight', dpi=300) + fig_fn = "allregions_calving_ObsMod.png" + fig.set_size_inches(6.5, 5.5) + fig.savefig(output_fp + fig_fn, bbox_inches="tight", dpi=300) plt.close(fig) return def main(): - - parser = argparse.ArgumentParser(description="Calibrate frontal ablation against reference calving datasets and update the reference mass balance data accordingly") + parser = argparse.ArgumentParser( + description="Calibrate frontal ablation against reference calving datasets and update the reference mass balance data accordingly" + ) # add arguments - parser.add_argument('-rgi_region01', type=int, default=list(range(1,20)), - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-hugonnet2021_fn', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", - help='reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-hugonnet2021_facorrected_fn', action='store', type=str, default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn']}", - help='reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization') - parser.add_argument('-prms_from_reg_priors', action='store_true', - help='Take model parameters from regional priors (default False and use calibrated glacier parameters)') - parser.add_argument('-o', '--overwrite', action='store_true', - help='Flag to overwrite existing calibrated frontal ablation datasets') - parser.add_argument('-v', '--verbose', action='store_true', - help='Flag for verbose') + parser.add_argument( + "-rgi_region01", + type=int, + default=list(range(1, 20)), + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-hugonnet2021_fn", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn']}", + help="reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-hugonnet2021_facorrected_fn", + action="store", + type=str, + default=f"{pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn']}", + help="reference mass balance data file name (default: df_pergla_global_20yr-filled.csv)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use, defualt is 1, ie. no parallelization", + ) + parser.add_argument( + "-prms_from_reg_priors", + action="store_true", + help="Take model parameters from regional priors (default False and use calibrated glacier parameters)", + ) + parser.add_argument( + "-o", + "--overwrite", + action="store_true", + help="Flag to overwrite existing calibrated frontal ablation datasets", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Flag for verbose" + ) args = parser.parse_args() args.prms_from_glac_cal = not args.prms_from_reg_priors @@ -1945,28 +3484,56 @@ def main(): # data paths frontalablation_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['frontalablation']['frontalablation_relpath']}" - frontalablation_cal_fn = pygem_prms['calib']['data']['frontalablation']['frontalablation_cal_fn'] - output_fp = frontalablation_fp + '/analysis/' + frontalablation_cal_fn = pygem_prms["calib"]["data"]["frontalablation"][ + "frontalablation_cal_fn" + ] + output_fp = frontalablation_fp + "/analysis/" hugonnet2021_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}/{args.hugonnet2021_fn}" hugonnet2021_facorr_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}/{args.hugonnet2021_facorrected_fn}" - os.makedirs(output_fp,exist_ok=True) + os.makedirs(output_fp, exist_ok=True) # marge input calving datasets - merged_calving_data_fn = merge_data(frontalablation_fp=frontalablation_fp, overwrite=args.overwrite, verbose=args.verbose) + merged_calving_data_fn = merge_data( + frontalablation_fp=frontalablation_fp, + overwrite=args.overwrite, + verbose=args.verbose, + ) # calibrate each individual glacier's calving_k parameter - calib_ind_calving_k_partial = partial(calib_ind_calving_k, args=args, frontalablation_fp=frontalablation_fp, frontalablation_fn=merged_calving_data_fn, output_fp=output_fp, hugonnet2021_fp=hugonnet2021_fp) + calib_ind_calving_k_partial = partial( + calib_ind_calving_k, + args=args, + frontalablation_fp=frontalablation_fp, + frontalablation_fn=merged_calving_data_fn, + output_fp=output_fp, + hugonnet2021_fp=hugonnet2021_fp, + ) with multiprocessing.Pool(args.ncores) as p: p.map(calib_ind_calving_k_partial, args.rgi_region01) # merge all individual calving_k calibration results by region - merge_ind_calving_k(regions=args.rgi_region01, output_fp=output_fp, merged_calving_k_fn=frontalablation_cal_fn, verbose=args.verbose) + merge_ind_calving_k( + regions=args.rgi_region01, + output_fp=output_fp, + merged_calving_k_fn=frontalablation_cal_fn, + verbose=args.verbose, + ) # update reference mass balance data accordingly - update_mbdata(regions=args.rgi_region01, frontalablation_fp=output_fp, frontalablation_fn=frontalablation_cal_fn, hugonnet2021_fp=hugonnet2021_fp, hugonnet2021_facorr_fp=hugonnet2021_facorr_fp, ncores=args.ncores, overwrite=args.overwrite, verbose=args.verbose) + update_mbdata( + regions=args.rgi_region01, + frontalablation_fp=output_fp, + frontalablation_fn=frontalablation_cal_fn, + hugonnet2021_fp=hugonnet2021_fp, + hugonnet2021_facorr_fp=hugonnet2021_facorr_fp, + ncores=args.ncores, + overwrite=args.overwrite, + verbose=args.verbose, + ) # # plot results plot_calving_k_allregions(output_fp=output_fp) + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_calibration_reg_glena.py b/pygem/bin/run/run_calibration_reg_glena.py index fb0d1bb5..858b735b 100644 --- a/pygem/bin/run/run_calibration_reg_glena.py +++ b/pygem/bin/run/run_calibration_reg_glena.py @@ -5,39 +5,42 @@ Distrubted under the MIT lisence -Find the optimal values of glens_a_multiplier to match the consensus ice thickness estimates +Find the optimal values of glens_a_multiplier to match the consensus ice thickness estimates """ + # Built-in libraries import argparse -from collections import OrderedDict -import os -import sys -import time import json -# External libraries -import pandas as pd +import os import pickle +import time +from collections import OrderedDict + import matplotlib.pyplot as plt import numpy as np + +# External libraries +import pandas as pd from scipy.optimize import brentq + # pygem imports -import pygem import pygem.setup.config as config + # Check for config config.ensure_config() # This will ensure the config file is created # Read the config pygem_prms = config.read_config() # This reads the configuration file +# oggm imports +from oggm import cfg, tasks +from oggm.core.massbalance import apparent_mb_from_any_mb + +import pygem.pygem_modelsetup as modelsetup from pygem import class_climate from pygem.massbalance import PyGEMMassBalance from pygem.oggm_compat import single_flowline_glacier_directory -import pygem.pygem_modelsetup as modelsetup -# oggm imports -from oggm import cfg -from oggm import tasks -from oggm.core.massbalance import apparent_mb_from_any_mb -#%% FUNCTIONS +# %% FUNCTIONS def getparser(): """ Use argparse to add arguments from the command line @@ -63,32 +66,87 @@ def getparser(): """ parser = argparse.ArgumentParser(description="run calibration in parallel") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-rgi_glac_number_fn', action='store', type=str, default=None, - help='Filename containing list of rgi_glac_number, helpful for running batches on spc') - parser.add_argument('-rgi_glac_number', action='store', type=float, default=pygem_prms['setup']['glac_no'], nargs='+', - help='Randoph Glacier Inventory glacier number (can take multiple)') - parser.add_argument('-fs', action='store', type=float, default=pygem_prms['out']['fs'], - help='Sliding parameter') - parser.add_argument('-a_multiplier', action='store', type=float, default=pygem_prms['out']['glen_a_multiplier'], - help="Glen’s creep parameter A multiplier") - parser.add_argument('-a_multiplier_bndlow', action='store', type=float, default=0.1, - help="Glen’s creep parameter A multiplier, low bound (default 0.1)") - parser.add_argument('-a_multiplier_bndhigh', action='store', type=float, default=10, - help="Glen’s creep parameter A multiplier, upper bound (default 10)") + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-rgi_glac_number_fn", + action="store", + type=str, + default=None, + help="Filename containing list of rgi_glac_number, helpful for running batches on spc", + ) + parser.add_argument( + "-rgi_glac_number", + action="store", + type=float, + default=pygem_prms["setup"]["glac_no"], + nargs="+", + help="Randoph Glacier Inventory glacier number (can take multiple)", + ) + parser.add_argument( + "-fs", + action="store", + type=float, + default=pygem_prms["out"]["fs"], + help="Sliding parameter", + ) + parser.add_argument( + "-a_multiplier", + action="store", + type=float, + default=pygem_prms["out"]["glen_a_multiplier"], + help="Glen’s creep parameter A multiplier", + ) + parser.add_argument( + "-a_multiplier_bndlow", + action="store", + type=float, + default=0.1, + help="Glen’s creep parameter A multiplier, low bound (default 0.1)", + ) + parser.add_argument( + "-a_multiplier_bndhigh", + action="store", + type=float, + default=10, + help="Glen’s creep parameter A multiplier, upper bound (default 10)", + ) # flags - parser.add_argument('-option_ordered', action='store_true', - help='Flag to keep glacier lists ordered (default is off)') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-option_ordered", + action="store_true", + help="Flag to keep glacier lists ordered (default is off)", + ) + parser.add_argument( + "-v", "--debug", action="store_true", help="Flag for debugging" + ) return parser @@ -113,39 +171,49 @@ def plot_nfls_section(nfls): # Plot histo posax = ax.get_position() - posax = [posax.x0 + 2 * posax.width / 3.0, - posax.y0, posax.width / 3.0, - posax.height] + posax = [ + posax.x0 + 2 * posax.width / 3.0, + posax.y0, + posax.width / 3.0, + posax.height, + ] axh = fig.add_axes(posax, frameon=False) - axh.hist(height, orientation='horizontal', range=ylim, bins=20, - alpha=0.3, weights=area) + axh.hist( + height, + orientation="horizontal", + range=ylim, + bins=20, + alpha=0.3, + weights=area, + ) axh.invert_xaxis() axh.xaxis.tick_top() - axh.set_xlabel('Area incl. tributaries (km$^2$)') - axh.xaxis.set_label_position('top') + axh.set_xlabel("Area incl. tributaries (km$^2$)") + axh.xaxis.set_label_position("top") axh.set_ylim(ylim) - axh.yaxis.set_ticks_position('right') + axh.yaxis.set_ticks_position("right") axh.set_yticks([]) - axh.axhline(y=ylim[1], color='black', alpha=1) # qick n dirty trick + axh.axhline(y=ylim[1], color="black", alpha=1) # qick n dirty trick # plot Centerlines cls = nfls[-1] x = np.arange(cls.nx) * cls.dx * cls.map_dx # Plot the bed - ax.plot(x, cls.bed_h, color='k', linewidth=2.5, label='Bed (Parab.)') + ax.plot(x, cls.bed_h, color="k", linewidth=2.5, label="Bed (Parab.)") # Where trapezoid change color - if hasattr(cls, '_do_trapeze') and cls._do_trapeze: + if hasattr(cls, "_do_trapeze") and cls._do_trapeze: bed_t = cls.bed_h * np.nan pt = cls.is_trapezoid & (~cls.is_rectangular) bed_t[pt] = cls.bed_h[pt] - ax.plot(x, bed_t, color='rebeccapurple', linewidth=2.5, - label='Bed (Trap.)') + ax.plot( + x, bed_t, color="rebeccapurple", linewidth=2.5, label="Bed (Trap.)" + ) bed_t = cls.bed_h * np.nan bed_t[cls.is_rectangular] = cls.bed_h[cls.is_rectangular] - ax.plot(x, bed_t, color='crimson', linewidth=2.5, label='Bed (Rect.)') + ax.plot(x, bed_t, color="crimson", linewidth=2.5, label="Bed (Rect.)") # Plot glacier def surf_to_nan(surf_h, thick): @@ -155,61 +223,75 @@ def surf_to_nan(surf_h, thick): pnan = ((t1 == 0) & (t2 == 0)) & ((t2 == 0) & (t3 == 0)) surf_h[np.where(pnan)[0] + 1] = np.nan return surf_h - + surfh = surf_to_nan(cls.surface_h, cls.thick) - ax.plot(x, surfh, color='#003399', linewidth=2, label='Glacier') + ax.plot(x, surfh, color="#003399", linewidth=2, label="Glacier") ax.set_ylim(ylim) - ax.spines['top'].set_color('none') - ax.xaxis.set_ticks_position('bottom') - ax.set_xlabel('Distance along flowline (m)') - ax.set_ylabel('Altitude (m)') + ax.spines["top"].set_color("none") + ax.xaxis.set_ticks_position("bottom") + ax.set_xlabel("Distance along flowline (m)") + ax.set_ylabel("Altitude (m)") # Legend handles, labels = ax.get_legend_handles_labels() by_label = OrderedDict(zip(labels, handles)) - ax.legend(list(by_label.values()), list(by_label.keys()), - bbox_to_anchor=(0.5, 1.0), - frameon=False) + ax.legend( + list(by_label.values()), + list(by_label.keys()), + bbox_to_anchor=(0.5, 1.0), + frameon=False, + ) plt.show() -def reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False): - """ Calculate the modeled volume [km3] and consensus volume [km3] for the given set of glaciers """ - + +def reg_vol_comparison( + gdirs, mbmods, nyears, a_multiplier=1, fs=0, debug=False +): + """Calculate the modeled volume [km3] and consensus volume [km3] for the given set of glaciers""" + reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 for nglac, gdir in enumerate(gdirs): - if nglac%2000 == 0: + if nglac % 2000 == 0: print(gdir.rgi_id) mbmod_inv = mbmods[nglac] - + # Arbitrariliy shift the MB profile up (or down) until mass balance is zero (equilibrium for inversion) - apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears)) - + apparent_mb_from_any_mb( + gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears) + ) + tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*a_multiplier, fs=fs) - tasks.init_present_time_glacier(gdir) # adds bins below - nfls = gdir.read_pickle('model_flowlines') - + tasks.mass_conservation_inversion( + gdir, glen_a=cfg.PARAMS["glen_a"] * a_multiplier, fs=fs + ) + tasks.init_present_time_glacier(gdir) # adds bins below + nfls = gdir.read_pickle("model_flowlines") + # Load consensus volume - if os.path.exists(gdir.get_filepath('consensus_mass')): - consensus_fn = gdir.get_filepath('consensus_mass') - with open(consensus_fn, 'rb') as f: - consensus_km3 = pickle.load(f) / pygem_prms['constants']['density_ice'] / 1e9 - + if os.path.exists(gdir.get_filepath("consensus_mass")): + consensus_fn = gdir.get_filepath("consensus_mass") + with open(consensus_fn, "rb") as f: + consensus_km3 = ( + pickle.load(f) + / pygem_prms["constants"]["density_ice"] + / 1e9 + ) + reg_vol_km3_consensus += consensus_km3 reg_vol_km3_modeled += nfls[0].volume_km3 - - if debug: + + if debug: plot_nfls_section(nfls) - print('\n\n Modeled vol [km3]: ', nfls[0].volume_km3) - print(' Consensus vol [km3]:', consensus_km3,'\n\n') - + print("\n\n Modeled vol [km3]: ", nfls[0].volume_km3) + print(" Consensus vol [km3]:", consensus_km3, "\n\n") + return reg_vol_km3_modeled, reg_vol_km3_consensus - -#%% + +# %% def main(): parser = getparser() args = parser.parse_args() @@ -222,152 +304,224 @@ def main(): # Calibrate each region for reg in args.rgi_region01: - - print('Region:', reg) - + print("Region:", reg) + # ===== LOAD GLACIERS ===== main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=[reg], rgi_regionsO2='all', rgi_glac_number='all', - include_landterm=True,include_laketerm=True, include_tidewater=True) - - - main_glac_rgi_all = main_glac_rgi_all.sort_values('Area', ascending=False) + rgi_regionsO1=[reg], + rgi_regionsO2="all", + rgi_glac_number="all", + include_landterm=True, + include_laketerm=True, + include_tidewater=True, + ) + + main_glac_rgi_all = main_glac_rgi_all.sort_values( + "Area", ascending=False + ) main_glac_rgi_all.reset_index(inplace=True, drop=True) - main_glac_rgi_all['Area_cum'] = np.cumsum(main_glac_rgi_all['Area']) - main_glac_rgi_all['Area_cum_frac'] = main_glac_rgi_all['Area_cum'] / main_glac_rgi_all.Area.sum() - - glac_idx = np.where(main_glac_rgi_all.Area_cum_frac > pygem_prms['calib']['icethickness_cal_frac_byarea'])[0][0] + main_glac_rgi_all["Area_cum"] = np.cumsum(main_glac_rgi_all["Area"]) + main_glac_rgi_all["Area_cum_frac"] = ( + main_glac_rgi_all["Area_cum"] / main_glac_rgi_all.Area.sum() + ) + + glac_idx = np.where( + main_glac_rgi_all.Area_cum_frac + > pygem_prms["calib"]["icethickness_cal_frac_byarea"] + )[0][0] main_glac_rgi_subset = main_glac_rgi_all.loc[0:glac_idx, :] - main_glac_rgi_subset = main_glac_rgi_subset.sort_values('O1Index', ascending=True) + main_glac_rgi_subset = main_glac_rgi_subset.sort_values( + "O1Index", ascending=True + ) main_glac_rgi_subset.reset_index(inplace=True, drop=True) - - print(f'But only the largest {int(100*pygem_prms['calib']['icethickness_cal_frac_byarea'])}% of the glaciers by area, which includes', main_glac_rgi_subset.shape[0], 'glaciers.') - + + print( + f"But only the largest {int(100 * pygem_prms['calib']['icethickness_cal_frac_byarea'])}% of the glaciers by area, which includes", + main_glac_rgi_subset.shape[0], + "glaciers.", + ) + # ===== TIME PERIOD ===== dates_table = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=args.ref_endyear, spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) - + startyear=args.ref_startyear, + endyear=args.ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) + # ===== LOAD CLIMATE DATA ===== # Climate class gcm_name = args.ref_gcm_name - assert gcm_name in ['ERA5', 'ERA-Interim'], 'Error: Calibration not set up for ' + gcm_name + assert gcm_name in ["ERA5", "ERA-Interim"], ( + "Error: Calibration not set up for " + gcm_name + ) gcm = class_climate.GCM(name=gcm_name) # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi_subset, dates_table) - if pygem_prms['mbmod']['option_ablation'] == 2 and gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi_subset, dates_table) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi_subset, dates_table + ) + if pygem_prms["mbmod"]["option_ablation"] == 2 and gcm_name in [ + "ERA5" + ]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, + gcm.tempstd_vn, + main_glac_rgi_subset, + dates_table, + ) else: gcm_tempstd = np.zeros(gcm_temp.shape) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi_subset, dates_table) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi_subset, dates_table + ) # Elevation [m asl] - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi_subset) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi_subset + ) # Lapse rate [degC m-1] - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi_subset, dates_table) - + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi_subset, dates_table + ) + # ===== RUN MASS BALANCE ===== # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' - + assert True == False, "Adjust nyears for non-monthly timestep" + reg_vol_km3_consensus = 0 reg_vol_km3_modeled = 0 mbmods = [] gdirs = [] for glac in range(main_glac_rgi_subset.shape[0]): # Select subsets of data - glacier_rgi_table = main_glac_rgi_subset.loc[main_glac_rgi_subset.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) - - if glac%1000 == 0: + glacier_rgi_table = main_glac_rgi_subset.loc[ + main_glac_rgi_subset.index.values[glac], : + ] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) + + if glac % 1000 == 0: print(glacier_str) - + # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== try: gdir = single_flowline_glacier_directory(glacier_str) - + # Flowlines - fls = gdir.read_pickle('inversion_flowlines') - + fls = gdir.read_pickle("inversion_flowlines") + # Add climate data to glacier directory - gdir.historical_climate = {'elev': gcm_elev[glac], - 'temp': gcm_temp[glac,:], - 'tempstd': gcm_tempstd[glac,:], - 'prec': gcm_prec[glac,:], - 'lr': gcm_lr[glac,:]} + gdir.historical_climate = { + "elev": gcm_elev[glac], + "temp": gcm_temp[glac, :], + "tempstd": gcm_tempstd[glac, :], + "prec": gcm_prec[glac, :], + "lr": gcm_lr[glac, :], + } gdir.dates_table = dates_table - + glacier_area_km2 = fls[0].widths_m * fls[0].dx_meter / 1e6 if (fls is not None) and (glacier_area_km2.sum() > 0): - - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) + '/' + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) modelprms_fullfn = modelprms_fp + modelprms_fn - assert os.path.exists(modelprms_fullfn), glacier_str + ' calibrated parameters do not exist.' - with open(modelprms_fullfn, 'r') as f: + assert os.path.exists(modelprms_fullfn), ( + glacier_str + " calibrated parameters do not exist." + ) + with open(modelprms_fullfn, "r") as f: modelprms_dict = json.load(f) - - assert 'emulator' in modelprms_dict, ('Error: ' + glacier_str + ' emulator not in modelprms_dict') - modelprms_all = modelprms_dict['emulator'] - + + assert "emulator" in modelprms_dict, ( + "Error: " + + glacier_str + + " emulator not in modelprms_dict" + ) + modelprms_all = modelprms_dict["emulator"] + # Loop through model parameters - modelprms = {'kp': modelprms_all['kp'][0], - 'tbias': modelprms_all['tbias'][0], - 'ddfsnow': modelprms_all['ddfsnow'][0], - 'ddfice': modelprms_all['ddfice'][0], - 'tsnow_threshold': modelprms_all['tsnow_threshold'][0], - 'precgrad': modelprms_all['precgrad'][0]} - + modelprms = { + "kp": modelprms_all["kp"][0], + "tbias": modelprms_all["tbias"][0], + "ddfsnow": modelprms_all["ddfsnow"][0], + "ddfice": modelprms_all["ddfice"][0], + "tsnow_threshold": modelprms_all["tsnow_threshold"][0], + "precgrad": modelprms_all["precgrad"][0], + } + # ----- ICE THICKNESS INVERSION using OGGM ----- # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms['mbmod']['include_debris']: + if pygem_prms["mbmod"]["include_debris"]: inversion_filter = True else: inversion_filter = False - + # Perform inversion based on PyGEM MB - mbmod_inv = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True, - inversion_filter=inversion_filter) - - # if debug: - # h, w = gdir.get_inversion_flowline_hw() - # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * - # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) - # plt.plot(mb_t0, h, '.') - # plt.ylabel('Elevation') - # plt.xlabel('Mass balance (mwea)') - # plt.show() - + mbmod_inv = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + inversion_filter=inversion_filter, + ) + + # if debug: + # h, w = gdir.get_inversion_flowline_hw() + # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * + # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + # plt.plot(mb_t0, h, '.') + # plt.ylabel('Elevation') + # plt.xlabel('Mass balance (mwea)') + # plt.show() + mbmods.append(mbmod_inv) gdirs.append(gdir) except: - print(glacier_str + ' failed - likely no gdir') - - print('\n\n------\nModel setup time:', time.time()-time_start, 's') - + print(glacier_str + " failed - likely no gdir") + + print("\n\n------\nModel setup time:", time.time() - time_start, "s") + # ===== CHECK BOUNDS ===== - reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=args.a_multiplier, fs=args.fs, - debug=debug) + reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=args.a_multiplier, + fs=args.fs, + debug=debug, + ) # Lower bound - reg_vol_km3_mod_bndlow, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, - a_multiplier=args.a_multiplier_bndlow, fs=args.fs, - debug=debug) + reg_vol_km3_mod_bndlow, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=args.a_multiplier_bndlow, + fs=args.fs, + debug=debug, + ) # Higher bound - reg_vol_km3_mod_bndhigh, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, - a_multiplier=args.a_multiplier_bndhigh, fs=args.fs, - debug=debug) - - print('Region:', reg) - print('Consensus [km3] :', reg_vol_km3_con) - print('Model [km3] :', reg_vol_km3_mod) - print('Model bndlow [km3] :', reg_vol_km3_mod_bndlow) - print('Model bndhigh [km3]:', reg_vol_km3_mod_bndhigh) - + reg_vol_km3_mod_bndhigh, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=args.a_multiplier_bndhigh, + fs=args.fs, + debug=debug, + ) + + print("Region:", reg) + print("Consensus [km3] :", reg_vol_km3_con) + print("Model [km3] :", reg_vol_km3_mod) + print("Model bndlow [km3] :", reg_vol_km3_mod_bndlow) + print("Model bndhigh [km3]:", reg_vol_km3_mod_bndhigh) + # ===== OPTIMIZATION ===== # Check consensus is within bounds if reg_vol_km3_con < reg_vol_km3_mod_bndhigh: @@ -376,48 +530,92 @@ def main(): a_multiplier_opt = args.a_multiplier_bndhigh # If so, then find optimal glens_a_multiplier else: + def to_minimize(a_multiplier): """Objective function to minimize""" - reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=a_multiplier, fs=args.fs, - debug=debug) + reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=a_multiplier, + fs=args.fs, + debug=debug, + ) return reg_vol_km3_mod - reg_vol_km3_con + # Brentq minimization - a_multiplier_opt, r = brentq(to_minimize, args.a_multiplier_bndlow, args.a_multiplier_bndhigh, rtol=1e-2, - full_output=True) + a_multiplier_opt, r = brentq( + to_minimize, + args.a_multiplier_bndlow, + args.a_multiplier_bndhigh, + rtol=1e-2, + full_output=True, + ) # Re-run to get estimates - reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison(gdirs, mbmods, nyears, a_multiplier=a_multiplier_opt, fs=args.fs, - debug=debug) - - print('\n\nOptimized:\n glens_a_multiplier:', np.round(a_multiplier_opt,3)) - print(' Consensus [km3]:', reg_vol_km3_con) - print(' Model [km3] :', reg_vol_km3_mod) - + reg_vol_km3_mod, reg_vol_km3_con = reg_vol_comparison( + gdirs, + mbmods, + nyears, + a_multiplier=a_multiplier_opt, + fs=args.fs, + debug=debug, + ) + + print( + "\n\nOptimized:\n glens_a_multiplier:", + np.round(a_multiplier_opt, 3), + ) + print(" Consensus [km3]:", reg_vol_km3_con) + print(" Model [km3] :", reg_vol_km3_mod) + # ===== EXPORT RESULTS ===== - glena_cns = ['O1Region', 'count', 'glens_a_multiplier', 'fs', 'reg_vol_km3_consensus', 'reg_vol_km3_modeled'] - glena_df_single = pd.DataFrame(np.zeros((1,len(glena_cns))), columns=glena_cns) - glena_df_single.loc[0,:] = [reg, main_glac_rgi_subset.shape[0], a_multiplier_opt, args.fs, reg_vol_km3_con, reg_vol_km3_mod] + glena_cns = [ + "O1Region", + "count", + "glens_a_multiplier", + "fs", + "reg_vol_km3_consensus", + "reg_vol_km3_modeled", + ] + glena_df_single = pd.DataFrame( + np.zeros((1, len(glena_cns))), columns=glena_cns + ) + glena_df_single.loc[0, :] = [ + reg, + main_glac_rgi_subset.shape[0], + a_multiplier_opt, + args.fs, + reg_vol_km3_con, + reg_vol_km3_mod, + ] try: - glena_df = pd.read_csv(f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}") - + glena_df = pd.read_csv( + f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}" + ) + # Add or overwrite existing file glena_idx = np.where((glena_df.O1Region == reg))[0] if len(glena_idx) > 0: - glena_df.loc[glena_idx,:] = glena_df_single.values + glena_df.loc[glena_idx, :] = glena_df_single.values else: glena_df = pd.concat([glena_df, glena_df_single], axis=0) - + except FileNotFoundError: glena_df = glena_df_single - + except Exception as err: - print(f'Error saving results: {err}') - - glena_df = glena_df.sort_values('O1Region', ascending=True) + print(f"Error saving results: {err}") + + glena_df = glena_df.sort_values("O1Region", ascending=True) glena_df.reset_index(inplace=True, drop=True) - glena_df.to_csv(f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}", index=False) - - print('\n\n------\nTotal processing time:', time.time()-time_start, 's') + glena_df.to_csv( + f"{pygem_prms['root']}/{pygem_prms['out']['glena_reg_relpath']}", + index=False, + ) + + print("\n\n------\nTotal processing time:", time.time() - time_start, "s") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_mcmc_priors.py b/pygem/bin/run/run_mcmc_priors.py index b196a7f1..5edfa298 100644 --- a/pygem/bin/run/run_mcmc_priors.py +++ b/pygem/bin/run/run_mcmc_priors.py @@ -1,12 +1,12 @@ -""" Export regional priors """ +"""Export regional priors""" import argparse -import os -import sys import json -import time import multiprocessing +import os +import time from functools import partial + import matplotlib.pyplot as plt import numpy as np import pandas as pd @@ -14,6 +14,7 @@ # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config @@ -21,29 +22,47 @@ import pygem.pygem_modelsetup as modelsetup # Region dictionary for titles -reg_dict = {1:'Alaska', - 2:'W CA/USA', - 3:'Arctic CA N', - 4:'Arctic CA S', - 5:'Greenland', - 6:'Iceland', - 7:'Svalbard', - 8:'Scandinavia', - 9:'Russian Arctic', - 10:'N Asia', - 11:'C Europe', - 12:'Caucasus/Middle East', - 13:'C Asia', - 14:'S Asia W', - 15:'S Asia E', - 16:'Low Latitudes', - 17:'S Andes', - 18:'New Zealand', - 19:'Antarctica'} +reg_dict = { + 1: "Alaska", + 2: "W CA/USA", + 3: "Arctic CA N", + 4: "Arctic CA S", + 5: "Greenland", + 6: "Iceland", + 7: "Svalbard", + 8: "Scandinavia", + 9: "Russian Arctic", + 10: "N Asia", + 11: "C Europe", + 12: "Caucasus/Middle East", + 13: "C Asia", + 14: "S Asia W", + 15: "S Asia E", + 16: "Low Latitudes", + 17: "S Andes", + 18: "New Zealand", + 19: "Antarctica", +} # list of prior fields -priors_cn = ['O1Region', 'O2Region', 'count', - 'kp_mean', 'kp_std', 'kp_med', 'kp_min', 'kp_max', 'kp_alpha', 'kp_beta', - 'tbias_mean', 'tbias_std', 'tbias_med', 'tbias_min', 'tbias_max'] +priors_cn = [ + "O1Region", + "O2Region", + "count", + "kp_mean", + "kp_std", + "kp_med", + "kp_min", + "kp_max", + "kp_alpha", + "kp_beta", + "tbias_mean", + "tbias_std", + "tbias_med", + "tbias_min", + "tbias_max", +] + + # FUNCTIONS def getparser(): """ @@ -51,93 +70,172 @@ def getparser(): """ parser = argparse.ArgumentParser(description="run calibration in parallel") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-option_calibration', action='store', type=str, default='emulator', - help='calibration option (defaultss to "emulator")') - parser.add_argument('-priors_reg_outpath', action='store', type=str, default=pygem_prms['root'] + '/Output/calibration/' + pygem_prms['calib']['priors_reg_fn'], - help='output path') + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=str, + default="emulator", + help='calibration option (defaultss to "emulator")', + ) + parser.add_argument( + "-priors_reg_outpath", + action="store", + type=str, + default=pygem_prms["root"] + + "/Output/calibration/" + + pygem_prms["calib"]["priors_reg_fn"], + help="output path", + ) # flags - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') - parser.add_argument('-p', '--plot', action='store_true', - help='Flag for plotting regional priors') + parser.add_argument( + "-v", "--debug", action="store_true", help="Flag for debugging" + ) + parser.add_argument( + "-p", + "--plot", + action="store_true", + help="Flag for plotting regional priors", + ) return parser -def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): +def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=""): # EXPORT PRIORS if os.path.exists(priors_reg_outpath): priors_df = pd.read_csv(priors_reg_outpath) # Add or overwrite existing priors - priors_idx = np.where((priors_df.O1Region == reg) & (priors_df.O2Region == regO2))[0] + priors_idx = np.where( + (priors_df.O1Region == reg) & (priors_df.O2Region == regO2) + )[0] if len(priors_idx) > 0: - priors_df.loc[priors_idx,:] = priors_df_single.values + priors_df.loc[priors_idx, :] = priors_df_single.values else: priors_df = pd.concat([priors_df, priors_df_single], axis=0) - + else: priors_df = priors_df_single - - priors_df = priors_df.sort_values(['O1Region', 'O2Region'], ascending=[True, True]) + + priors_df = priors_df.sort_values( + ["O1Region", "O2Region"], ascending=[True, True] + ) priors_df.reset_index(inplace=True, drop=True) priors_df.to_csv(priors_reg_outpath, index=False) return priors_df -def plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2=''): - # Histograms and record model parameter statistics - fig, ax = plt.subplots(1,2, figsize=(6,4), gridspec_kw = {'wspace':0.3, 'hspace':0.3}) - labelsize = 1 - fig.text(0.5,0.9, 'Region ' + str(reg) + ' (subregion: ' + str(regO2) + ')'.replace(' (subregion: )', '(all subregions)'), ha='center', size=14) - - nbins = 50 - ax[0].hist(main_glac_rgi_subset['kp'], bins=nbins, color='grey') - ax[0].set_xlabel('kp (-)') - ax[0].set_ylabel('Count (glaciers)') - ax[1].hist(main_glac_rgi_subset['tbias'], bins=50, color='grey') - ax[1].set_xlabel('tbias (degC)') - - fig_fn = str(reg) + '-' + str(regO2) + '_hist_mcmc_priors.png'.replace('-_','_') - fig.savefig(fig_fp + fig_fn, pad_inches=0, dpi=150) +def plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2=""): + # Histograms and record model parameter statistics + fig, ax = plt.subplots( + 1, 2, figsize=(6, 4), gridspec_kw={"wspace": 0.3, "hspace": 0.3} + ) + labelsize = 1 + fig.text( + 0.5, + 0.9, + "Region " + + str(reg) + + " (subregion: " + + str(regO2) + + ")".replace(" (subregion: )", "(all subregions)"), + ha="center", + size=14, + ) + + nbins = 50 + ax[0].hist(main_glac_rgi_subset["kp"], bins=nbins, color="grey") + ax[0].set_xlabel("kp (-)") + ax[0].set_ylabel("Count (glaciers)") + ax[1].hist(main_glac_rgi_subset["tbias"], bins=50, color="grey") + ax[1].set_xlabel("tbias (degC)") + + fig_fn = ( + str(reg) + + "-" + + str(regO2) + + "_hist_mcmc_priors.png".replace("-_", "_") + ) + fig.savefig(fig_fp + fig_fn, pad_inches=0, dpi=150) def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): # ===== REGIONAL PRIOR: PRECIPITATION FACTOR ====== - nbins = 50 + nbins = 50 ncols = 3 - nrows = int(np.ceil(len(rgi_regionsO2)/ncols)) - priors_df_regO1 = priors_df.loc[priors_df['O1Region'] == reg] - - fig, ax = plt.subplots(nrows, ncols, squeeze=False, gridspec_kw={'wspace':0.5, 'hspace':0.5}) + nrows = int(np.ceil(len(rgi_regionsO2) / ncols)) + priors_df_regO1 = priors_df.loc[priors_df["O1Region"] == reg] + + fig, ax = plt.subplots( + nrows, ncols, squeeze=False, gridspec_kw={"wspace": 0.5, "hspace": 0.5} + ) nrow = 0 ncol = 0 for nreg, regO2 in enumerate(rgi_regionsO2): - priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] - kp_values = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, 'kp'].values + priors_df_regO2 = priors_df_regO1.loc[priors_df["O2Region"] == regO2] + kp_values = main_glac_rgi.loc[ + main_glac_rgi["O2Region"] == regO2, "kp" + ].values nglaciers = kp_values.shape[0] # Plot histogram - counts, bins, patches = ax[nrow,ncol].hist(kp_values, facecolor='grey', edgecolor='grey', - linewidth=0.1, bins=nbins, density=True) - + counts, bins, patches = ax[nrow, ncol].hist( + kp_values, + facecolor="grey", + edgecolor="grey", + linewidth=0.1, + bins=nbins, + density=True, + ) + # Plot gamma distribution alpha = priors_df_regO2.kp_alpha.values[0] beta = priors_df_regO2.kp_beta.values[0] - rv = stats.gamma(alpha, scale=1/beta) - ax[nrow,ncol].plot(bins, rv.pdf(bins), color='k') + rv = stats.gamma(alpha, scale=1 / beta) + ax[nrow, ncol].plot(bins, rv.pdf(bins), color="k") # add alpha and beta as text - gammatext = (r'$\alpha$=' + str(np.round(alpha,2)) + '\n' + r'$\beta$=' + str(np.round(beta,2)) - + '\n$n$=' + str(nglaciers)) - ax[nrow,ncol].text(0.98, 0.95, gammatext, size=10, horizontalalignment='right', - verticalalignment='top', transform=ax[nrow,ncol].transAxes) - + gammatext = ( + r"$\alpha$=" + + str(np.round(alpha, 2)) + + "\n" + + r"$\beta$=" + + str(np.round(beta, 2)) + + "\n$n$=" + + str(nglaciers) + ) + ax[nrow, ncol].text( + 0.98, + 0.95, + gammatext, + size=10, + horizontalalignment="right", + verticalalignment="top", + transform=ax[nrow, ncol].transAxes, + ) + # Subplot title - title_str = reg_dict[reg] + ' (' + str(regO2) + ')' - ax[nrow,ncol].text(0.5, 1.01, title_str, size=10, horizontalalignment='center', - verticalalignment='bottom', transform=ax[nrow,ncol].transAxes) + title_str = reg_dict[reg] + " (" + str(regO2) + ")" + ax[nrow, ncol].text( + 0.5, + 1.01, + title_str, + size=10, + horizontalalignment="center", + verticalalignment="bottom", + transform=ax[nrow, ncol].transAxes, + ) # Adjust row and column ncol += 1 @@ -146,49 +244,91 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): ncol = 0 # Remove extra plots - if len(rgi_regionsO2)%ncols > 0: - n_extras = ncols-len(rgi_regionsO2)%ncols + if len(rgi_regionsO2) % ncols > 0: + n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: - for nextra in np.arange(0,n_extras): - ax[nrow,ncol].axis('off') + for nextra in np.arange(0, n_extras): + ax[nrow, ncol].axis("off") ncol += 1 - + # Labels - fig.text(0.04, 0.5, 'Probability Density', va='center', ha='center', rotation='vertical', size=12) - fig.text(0.5, 0.04, '$k_{p}$ (-)', va='center', ha='center', size=12) + fig.text( + 0.04, + 0.5, + "Probability Density", + va="center", + ha="center", + rotation="vertical", + size=12, + ) + fig.text(0.5, 0.04, "$k_{p}$ (-)", va="center", ha="center", size=12) fig.set_size_inches(6, 6) - fig.savefig(fig_fp + 'priors_kp_O2Regions-' + str(reg) + '.png', bbox_inches='tight', dpi=300) + fig.savefig( + fig_fp + "priors_kp_O2Regions-" + str(reg) + ".png", + bbox_inches="tight", + dpi=300, + ) # ===== REGIONAL PRIOR: TEMPERATURE BIAS ====== - fig, ax = plt.subplots(nrows, ncols, squeeze=False, gridspec_kw={'wspace':0.3, 'hspace':0.3}) + fig, ax = plt.subplots( + nrows, ncols, squeeze=False, gridspec_kw={"wspace": 0.3, "hspace": 0.3} + ) nrow = 0 ncol = 0 for nreg, regO2 in enumerate(rgi_regionsO2): - - priors_df_regO2 = priors_df_regO1.loc[priors_df['O2Region'] == regO2] - tbias_values = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, 'tbias'].values + priors_df_regO2 = priors_df_regO1.loc[priors_df["O2Region"] == regO2] + tbias_values = main_glac_rgi.loc[ + main_glac_rgi["O2Region"] == regO2, "tbias" + ].values nglaciers = tbias_values.shape[0] - + # Plot histogram - counts, bins, patches = ax[nrow,ncol].hist(tbias_values, facecolor='grey', edgecolor='grey', - linewidth=0.1, bins=nbins, density=True) - + counts, bins, patches = ax[nrow, ncol].hist( + tbias_values, + facecolor="grey", + edgecolor="grey", + linewidth=0.1, + bins=nbins, + density=True, + ) + # Plot gamma distribution mu = priors_df_regO2.tbias_mean.values[0] sigma = priors_df_regO2.tbias_std.values[0] rv = stats.norm(loc=mu, scale=sigma) - ax[nrow,ncol].plot(bins, rv.pdf(bins), color='k') + ax[nrow, ncol].plot(bins, rv.pdf(bins), color="k") # add alpha and beta as text - normtext = (r'$\mu$=' + str(np.round(mu,2)) + '\n' + r'$\sigma$=' + str(np.round(sigma,2)) - + '\n$n$=' + str(nglaciers)) - ax[nrow,ncol].text(0.98, 0.95, normtext, size=10, horizontalalignment='right', - verticalalignment='top', transform=ax[nrow,ncol].transAxes) - + normtext = ( + r"$\mu$=" + + str(np.round(mu, 2)) + + "\n" + + r"$\sigma$=" + + str(np.round(sigma, 2)) + + "\n$n$=" + + str(nglaciers) + ) + ax[nrow, ncol].text( + 0.98, + 0.95, + normtext, + size=10, + horizontalalignment="right", + verticalalignment="top", + transform=ax[nrow, ncol].transAxes, + ) + # Title - title_str = reg_dict[reg] + ' (' + str(regO2) + ')' - ax[nrow,ncol].text(0.5, 1.01, title_str, size=10, horizontalalignment='center', - verticalalignment='bottom', transform=ax[nrow,ncol].transAxes) - + title_str = reg_dict[reg] + " (" + str(regO2) + ")" + ax[nrow, ncol].text( + 0.5, + 1.01, + title_str, + size=10, + horizontalalignment="center", + verticalalignment="bottom", + transform=ax[nrow, ncol].transAxes, + ) + # Adjust row and column ncol += 1 if ncol == ncols: @@ -196,113 +336,178 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): ncol = 0 # Remove extra plots - if len(rgi_regionsO2)%ncols > 0: - n_extras = ncols-len(rgi_regionsO2)%ncols + if len(rgi_regionsO2) % ncols > 0: + n_extras = ncols - len(rgi_regionsO2) % ncols if n_extras > 0: - for nextra in np.arange(0,n_extras): - ax[nrow,ncol].axis('off') + for nextra in np.arange(0, n_extras): + ax[nrow, ncol].axis("off") ncol += 1 - + # Labels - fig.text(0.04, 0.5, 'Probability Density', va='center', ha='center', rotation='vertical', size=12) - fig.text(0.5, 0.04, r'$T_{bias}$ ($^\circ$C)', va='center', ha='center', size=12) + fig.text( + 0.04, + 0.5, + "Probability Density", + va="center", + ha="center", + rotation="vertical", + size=12, + ) + fig.text( + 0.5, 0.04, r"$T_{bias}$ ($^\circ$C)", va="center", ha="center", size=12 + ) fig.set_size_inches(6, 6) - fig.savefig(fig_fp + 'priors_tbias_O2Regions-' + str(reg) + '.png', bbox_inches='tight', dpi=300) + fig.savefig( + fig_fp + "priors_tbias_O2Regions-" + str(reg) + ".png", + bbox_inches="tight", + dpi=300, + ) -def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, plot=False): - +def run( + reg, + option_calibration="emulator", + priors_reg_outpath="", + debug=False, + plot=False, +): # Calibration filepath - modelprms_fp = pygem_prms['root'] + '/Output/calibration/' + str(reg).zfill(2) + '/' + modelprms_fp = ( + pygem_prms["root"] + "/Output/calibration/" + str(reg).zfill(2) + "/" + ) # Load glaciers - glac_list = [x.split('-')[0] for x in os.listdir(modelprms_fp) if x.endswith('-modelprms_dict.json')] + glac_list = [ + x.split("-")[0] + for x in os.listdir(modelprms_fp) + if x.endswith("-modelprms_dict.json") + ] glac_list = sorted(glac_list) - + main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_list) - + # Add model parameters to main dataframe - main_glac_rgi['kp'] = np.nan - main_glac_rgi['tbias'] = np.nan - main_glac_rgi['ddfsnow'] = np.nan - main_glac_rgi['mb_mwea'] = np.nan - main_glac_rgi['kp_em'] = np.nan - main_glac_rgi['tbias_em'] = np.nan - main_glac_rgi['ddfsnow_em'] = np.nan - main_glac_rgi['mb_mwea_em'] = np.nan + main_glac_rgi["kp"] = np.nan + main_glac_rgi["tbias"] = np.nan + main_glac_rgi["ddfsnow"] = np.nan + main_glac_rgi["mb_mwea"] = np.nan + main_glac_rgi["kp_em"] = np.nan + main_glac_rgi["tbias_em"] = np.nan + main_glac_rgi["ddfsnow_em"] = np.nan + main_glac_rgi["mb_mwea_em"] = np.nan for nglac, rgino_str in enumerate(list(main_glac_rgi.rgino_str.values)): - - glac_str = str(int(rgino_str.split('.')[0])) + '.' + rgino_str.split('.')[1] - + glac_str = ( + str(int(rgino_str.split(".")[0])) + "." + rgino_str.split(".")[1] + ) + # Load model parameters - modelprms_fn = glac_str + '-modelprms_dict.json' - with open(modelprms_fp + modelprms_fn, 'r') as f: + modelprms_fn = glac_str + "-modelprms_dict.json" + with open(modelprms_fp + modelprms_fn, "r") as f: modelprms_dict = json.load(f) - assert option_calibration in list(modelprms_dict.keys()), f'{glac_str}: {option_calibration} not in calibration data.' - modelprms = modelprms_dict[option_calibration] - - main_glac_rgi.loc[nglac, 'kp'] = modelprms['kp'][0] - main_glac_rgi.loc[nglac, 'tbias'] = modelprms['tbias'][0] - main_glac_rgi.loc[nglac, 'ddfsnow'] = modelprms['ddfsnow'][0] - main_glac_rgi.loc[nglac, 'mb_mwea'] = modelprms['mb_mwea'][0] - main_glac_rgi.loc[nglac, 'mb_obs_mwea'] = modelprms['mb_obs_mwea'][0] - + assert option_calibration in list(modelprms_dict.keys()), ( + f"{glac_str}: {option_calibration} not in calibration data." + ) + modelprms = modelprms_dict[option_calibration] + + main_glac_rgi.loc[nglac, "kp"] = modelprms["kp"][0] + main_glac_rgi.loc[nglac, "tbias"] = modelprms["tbias"][0] + main_glac_rgi.loc[nglac, "ddfsnow"] = modelprms["ddfsnow"][0] + main_glac_rgi.loc[nglac, "mb_mwea"] = modelprms["mb_mwea"][0] + main_glac_rgi.loc[nglac, "mb_obs_mwea"] = modelprms["mb_obs_mwea"][0] + # get regional difference between calibrated mb_mwea and observed - main_glac_rgi['mb_dif_obs_cal'] = main_glac_rgi['mb_obs_mwea'] - main_glac_rgi['mb_mwea'] + main_glac_rgi["mb_dif_obs_cal"] = ( + main_glac_rgi["mb_obs_mwea"] - main_glac_rgi["mb_mwea"] + ) # define figure output path if plot: - fig_fp = os.path.split(priors_reg_outpath)[0] + '/figs/' + fig_fp = os.path.split(priors_reg_outpath)[0] + "/figs/" os.makedirs(fig_fp, exist_ok=True) - + # Priors for each subregion if reg not in [19]: rgi_regionsO2 = np.unique(main_glac_rgi.O2Region.values) for regO2 in rgi_regionsO2: - main_glac_rgi_subset = main_glac_rgi.loc[main_glac_rgi['O2Region'] == regO2, :] + main_glac_rgi_subset = main_glac_rgi.loc[ + main_glac_rgi["O2Region"] == regO2, : + ] if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg, regO2) - + # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset['kp']) - kp_std = np.std(main_glac_rgi_subset['kp']) - kp_med = np.median(main_glac_rgi_subset['kp']) - kp_min = np.min(main_glac_rgi_subset['kp']) - kp_max = np.max(main_glac_rgi_subset['kp']) - + kp_mean = np.mean(main_glac_rgi_subset["kp"]) + kp_std = np.std(main_glac_rgi_subset["kp"]) + kp_med = np.median(main_glac_rgi_subset["kp"]) + kp_min = np.min(main_glac_rgi_subset["kp"]) + kp_max = np.max(main_glac_rgi_subset["kp"]) + # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: kp_std = 0.5 - + kp_beta = kp_mean / kp_std kp_alpha = kp_mean * kp_beta - + # Temperature bias - tbias_mean = main_glac_rgi_subset['tbias'].mean() - tbias_std = main_glac_rgi_subset['tbias'].std() - tbias_med = np.median(main_glac_rgi_subset['tbias']) - tbias_min = np.min(main_glac_rgi_subset['tbias']) - tbias_max = np.max(main_glac_rgi_subset['tbias']) - + tbias_mean = main_glac_rgi_subset["tbias"].mean() + tbias_std = main_glac_rgi_subset["tbias"].std() + tbias_med = np.median(main_glac_rgi_subset["tbias"]) + tbias_min = np.min(main_glac_rgi_subset["tbias"]) + tbias_max = np.max(main_glac_rgi_subset["tbias"]) + # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 - + if debug: - print('\n', reg, '(' + str(regO2) + ')') - print('kp (mean/std/med/min/max):', np.round(kp_mean,2), np.round(kp_std,2), - np.round(kp_med,2), np.round(kp_min,2), np.round(kp_max,2)) - print(' alpha/beta:', np.round(kp_alpha,2), np.round(kp_beta,2)) - print('tbias (mean/std/med/min/max):', np.round(tbias_mean,2), np.round(tbias_std,2), - np.round(tbias_med,2), np.round(tbias_min,2), np.round(tbias_max,2)) + print("\n", reg, "(" + str(regO2) + ")") + print( + "kp (mean/std/med/min/max):", + np.round(kp_mean, 2), + np.round(kp_std, 2), + np.round(kp_med, 2), + np.round(kp_min, 2), + np.round(kp_max, 2), + ) + print( + " alpha/beta:", + np.round(kp_alpha, 2), + np.round(kp_beta, 2), + ) + print( + "tbias (mean/std/med/min/max):", + np.round(tbias_mean, 2), + np.round(tbias_std, 2), + np.round(tbias_med, 2), + np.round(tbias_min, 2), + np.round(tbias_max, 2), + ) # export results - priors_df_single = pd.DataFrame(np.zeros((1,len(priors_cn))), columns=priors_cn) - priors_df_single.loc[0,:] = ( - [reg, regO2, main_glac_rgi_subset.shape[0], - kp_mean, kp_std, kp_med, kp_min, kp_max, kp_alpha, kp_beta, - tbias_mean, tbias_std, tbias_med, tbias_min, tbias_max]) - priors_df = export_priors(priors_df_single, reg, regO2, priors_reg_outpath) - + priors_df_single = pd.DataFrame( + np.zeros((1, len(priors_cn))), columns=priors_cn + ) + priors_df_single.loc[0, :] = [ + reg, + regO2, + main_glac_rgi_subset.shape[0], + kp_mean, + kp_std, + kp_med, + kp_min, + kp_max, + kp_alpha, + kp_beta, + tbias_mean, + tbias_std, + tbias_med, + tbias_min, + tbias_max, + ] + priors_df = export_priors( + priors_df_single, reg, regO2, priors_reg_outpath + ) + # Use the entire region for the prior (sometimes subregions make no sense; e.g., 24 regions in Antarctica) else: rgi_regionsO2 = np.unique(main_glac_rgi.O2Region.values) @@ -310,50 +515,80 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, if plot: plot_hist(main_glac_rgi_subset, fig_fp, reg) # Precipitation factors - kp_mean = np.mean(main_glac_rgi_subset['kp']) - kp_std = np.std(main_glac_rgi_subset['kp']) - kp_med = np.median(main_glac_rgi_subset['kp']) - kp_min = np.min(main_glac_rgi_subset['kp']) - kp_max = np.max(main_glac_rgi_subset['kp']) - + kp_mean = np.mean(main_glac_rgi_subset["kp"]) + kp_std = np.std(main_glac_rgi_subset["kp"]) + kp_med = np.median(main_glac_rgi_subset["kp"]) + kp_min = np.min(main_glac_rgi_subset["kp"]) + kp_max = np.max(main_glac_rgi_subset["kp"]) + # Small regions may all have the same values (e.g., 16-4 has 5 glaciers) if kp_std == 0: kp_std = 0.5 - + kp_beta = kp_mean / kp_std kp_alpha = kp_mean * kp_beta - + # Temperature bias - tbias_mean = main_glac_rgi_subset['tbias'].mean() - tbias_std = main_glac_rgi_subset['tbias'].std() - tbias_med = np.median(main_glac_rgi_subset['tbias']) - tbias_min = np.min(main_glac_rgi_subset['tbias']) - tbias_max = np.max(main_glac_rgi_subset['tbias']) - + tbias_mean = main_glac_rgi_subset["tbias"].mean() + tbias_std = main_glac_rgi_subset["tbias"].std() + tbias_med = np.median(main_glac_rgi_subset["tbias"]) + tbias_min = np.min(main_glac_rgi_subset["tbias"]) + tbias_max = np.max(main_glac_rgi_subset["tbias"]) + # tbias_std of 1 is reasonable for most subregions if tbias_std == 0: tbias_std = 1 - + if debug: - print('\n', reg, '(all subregions)') - print('kp (mean/std/med/min/max):', np.round(kp_mean,2), np.round(kp_std,2), - np.round(kp_med,2), np.round(kp_min,2), np.round(kp_max,2)) - print(' alpha/beta:', np.round(kp_alpha,2), np.round(kp_beta,2)) - print('tbias (mean/std/med/min/max):', np.round(tbias_mean,2), np.round(tbias_std,2), - np.round(tbias_med,2), np.round(tbias_min,2), np.round(tbias_max,2)) - - for regO2 in rgi_regionsO2: + print("\n", reg, "(all subregions)") + print( + "kp (mean/std/med/min/max):", + np.round(kp_mean, 2), + np.round(kp_std, 2), + np.round(kp_med, 2), + np.round(kp_min, 2), + np.round(kp_max, 2), + ) + print(" alpha/beta:", np.round(kp_alpha, 2), np.round(kp_beta, 2)) + print( + "tbias (mean/std/med/min/max):", + np.round(tbias_mean, 2), + np.round(tbias_std, 2), + np.round(tbias_med, 2), + np.round(tbias_min, 2), + np.round(tbias_max, 2), + ) + + for regO2 in rgi_regionsO2: # export results - priors_df_single = pd.DataFrame(np.zeros((1,len(priors_cn))), columns=priors_cn) - priors_df_single.loc[0,:] = ( - [reg, regO2, main_glac_rgi_subset.shape[0], - kp_mean, kp_std, kp_med, kp_min, kp_max, kp_alpha, kp_beta, - tbias_mean, tbias_std, tbias_med, tbias_min, tbias_max]) - priors_df = export_priors(priors_df_single, reg, regO2, priors_reg_outpath) - + priors_df_single = pd.DataFrame( + np.zeros((1, len(priors_cn))), columns=priors_cn + ) + priors_df_single.loc[0, :] = [ + reg, + regO2, + main_glac_rgi_subset.shape[0], + kp_mean, + kp_std, + kp_med, + kp_min, + kp_max, + kp_alpha, + kp_beta, + tbias_mean, + tbias_std, + tbias_med, + tbias_min, + tbias_max, + ] + priors_df = export_priors( + priors_df_single, reg, regO2, priors_reg_outpath + ) + if plot: plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp) + def main(): parser = getparser() args = parser.parse_args() @@ -366,12 +601,19 @@ def main(): ncores = 1 # Parallel processing - print('Processing with ' + str(ncores) + ' cores...') - partial_function = partial(run, option_calibration=args.option_calibration, priors_reg_outpath=args.priors_reg_outpath, debug=args.debug, plot=args.plot) + print("Processing with " + str(ncores) + " cores...") + partial_function = partial( + run, + option_calibration=args.option_calibration, + priors_reg_outpath=args.priors_reg_outpath, + debug=args.debug, + plot=args.plot, + ) with multiprocessing.Pool(ncores) as p: p.map(partial_function, args.rgi_region01) - print('\n\n------\nTotal processing time:', time.time()-time_start, 's') + print("\n\n------\nTotal processing time:", time.time() - time_start, "s") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/bin/run/run_simulation.py b/pygem/bin/run/run_simulation.py index fb6069c7..e1475271 100755 --- a/pygem/bin/run/run_simulation.py +++ b/pygem/bin/run/run_simulation.py @@ -16,52 +16,51 @@ # Built-in libraries import argparse -import collections import copy import inspect +import json import multiprocessing import os import sys import time -import cftime -import json -# External libraries -import pandas as pd -import pickle + import matplotlib.pyplot as plt import numpy as np -from scipy.stats import median_abs_deviation + +# External libraries +import pandas as pd import xarray as xr +from scipy.stats import median_abs_deviation # pygem imports import pygem import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() +# oggm imports +from oggm import cfg, graphics, tasks, utils +from oggm.core.flowline import FluxBasedModel +from oggm.core.massbalance import apparent_mb_from_any_mb + import pygem.gcmbiasadj as gcmbiasadj import pygem.pygem_modelsetup as modelsetup -from pygem.massbalance import PyGEMMassBalance +from pygem import class_climate, output from pygem.glacierdynamics import MassRedistributionCurveModel -from pygem.oggm_compat import single_flowline_glacier_directory -from pygem.oggm_compat import single_flowline_glacier_directory_with_calving -from pygem.shop import debris -from pygem import class_climate -from pygem import output +from pygem.massbalance import PyGEMMassBalance +from pygem.oggm_compat import ( + single_flowline_glacier_directory, + single_flowline_glacier_directory_with_calving, +) from pygem.output import calc_stats_array -# oggm imports -import oggm -from oggm import cfg -from oggm import graphics -from oggm import tasks -from oggm import utils -from oggm.core.massbalance import apparent_mb_from_any_mb -from oggm.core.flowline import FluxBasedModel, SemiImplicitModel +from pygem.shop import debris + +cfg.PARAMS["hydro_month_nh"] = 1 +cfg.PARAMS["hydro_month_sh"] = 1 +cfg.PARAMS["trapezoid_lambdas"] = 1 -cfg.PARAMS['hydro_month_nh']=1 -cfg.PARAMS['hydro_month_sh']=1 -cfg.PARAMS['trapezoid_lambdas'] = 1 # ----- FUNCTIONS ----- def none_or_value(value): @@ -69,7 +68,8 @@ def none_or_value(value): if value.lower() in {"none", "null"}: return None return value - + + def getparser(): """ Use argparse to add arguments from the command line @@ -106,75 +106,231 @@ def getparser(): """ parser = argparse.ArgumentParser(description="Run PyGEM simulation") # add arguments - parser.add_argument('-rgi_region01', type=int, default=pygem_prms['setup']['rgi_region01'], - help='Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)', nargs='+') - parser.add_argument('-rgi_region02', type=str, default=pygem_prms['setup']['rgi_region02'], nargs='+', - help='Randoph Glacier Inventory subregion (either `all` or multiple spaced integers, e.g. `-run_region02 1 2 3`)') - parser.add_argument('-rgi_glac_number', action='store', type=float, default=pygem_prms['setup']['glac_no'], nargs='+', - help='Randoph Glacier Inventory glacier number (can take multiple)') - parser.add_argument('-ref_gcm_name', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='reference gcm name') - parser.add_argument('-ref_startyear', action='store', type=int, default=pygem_prms['climate']['ref_startyear'], - help='reference period starting year for calibration (typically 2000)') - parser.add_argument('-ref_endyear', action='store', type=int, default=pygem_prms['climate']['ref_endyear'], - help='reference period ending year for calibration (typically 2019)') - parser.add_argument('-rgi_glac_number_fn', action='store', type=str, default=None, - help='filepath containing list of rgi_glac_number, helpful for running batches on spc') - parser.add_argument('-gcm_list_fn', action='store', type=str, default=pygem_prms['climate']['ref_gcm_name'], - help='text file full of commands to run (ex. CanESM2 or CESM2)') - parser.add_argument('-gcm_name', action='store', type=str, default=pygem_prms['climate']['gcm_name'], - help='GCM name used for model run') - parser.add_argument('-scenario', action='store', type=none_or_value, default=pygem_prms['climate']['scenario'], - help='rcp or ssp scenario used for model run (ex. rcp26 or ssp585)') - parser.add_argument('-realization', action='store', type=str, default=None, - help='realization from large ensemble used for model run (ex. 1011.001 or 1301.020)') - parser.add_argument('-realization_list', action='store', type=str, default=None, - help='text file full of realizations to run') - parser.add_argument('-gcm_startyear', action='store', type=int, default=pygem_prms['climate']['gcm_startyear'], - help='start year for the model run') - parser.add_argument('-gcm_endyear', action='store', type=int, default=pygem_prms['climate']['gcm_endyear'], - help='start year for the model run') - parser.add_argument('-mcmc_burn_pct', action='store', type=int, default=0, - help='percent of MCMC chain to burn off from beginning (defaults to 0, assuming that burn in was performed in calibration)') - parser.add_argument('-ncores', action='store', type=int, default=1, - help='number of simultaneous processes (cores) to use') - parser.add_argument('-batch_number', action='store', type=int, default=None, - help='Batch number used to differentiate output on supercomputer') - parser.add_argument('-kp', action='store', type=float, default=pygem_prms['sim']['params']['kp'], - help='Precipitation bias') - parser.add_argument('-tbias', action='store', type=float, default=pygem_prms['sim']['params']['tbias'], - help='Temperature bias') - parser.add_argument('-ddfsnow', action='store', type=float, default=pygem_prms['sim']['params']['ddfsnow'], - help='Degree-day factor of snow') - parser.add_argument('-oggm_working_dir', action='store', type=str, default=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}", - help='Specify OGGM working dir - useful if performing a grid search and have duplicated glacier directories') - parser.add_argument('-option_calibration', action='store', type=none_or_value, default=pygem_prms['calib']['option_calibration'], - help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")') - parser.add_argument('-option_dynamics', action='store', type=none_or_value, default=pygem_prms['sim']['option_dynamics'], - help='glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)') - parser.add_argument('-use_reg_glena', action='store', type=bool, default=pygem_prms['sim']['oggm_dynamics']['use_reg_glena'], - help='Take the glacier flow parameterization from regionally calibrated priors (boolean: `0` or `1`, `True` or `False`)') - parser.add_argument('-option_bias_adjustment', action='store', type=int, default=pygem_prms['sim']['option_bias_adjustment'], - help='Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ + parser.add_argument( + "-rgi_region01", + type=int, + default=pygem_prms["setup"]["rgi_region01"], + help="Randoph Glacier Inventory region (can take multiple, e.g. `-run_region01 1 2 3`)", + nargs="+", + ) + parser.add_argument( + "-rgi_region02", + type=str, + default=pygem_prms["setup"]["rgi_region02"], + nargs="+", + help="Randoph Glacier Inventory subregion (either `all` or multiple spaced integers, e.g. `-run_region02 1 2 3`)", + ) + parser.add_argument( + "-rgi_glac_number", + action="store", + type=float, + default=pygem_prms["setup"]["glac_no"], + nargs="+", + help="Randoph Glacier Inventory glacier number (can take multiple)", + ) + parser.add_argument( + "-ref_gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="reference gcm name", + ) + parser.add_argument( + "-ref_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_startyear"], + help="reference period starting year for calibration (typically 2000)", + ) + parser.add_argument( + "-ref_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["ref_endyear"], + help="reference period ending year for calibration (typically 2019)", + ) + parser.add_argument( + "-rgi_glac_number_fn", + action="store", + type=str, + default=None, + help="filepath containing list of rgi_glac_number, helpful for running batches on spc", + ) + parser.add_argument( + "-gcm_list_fn", + action="store", + type=str, + default=pygem_prms["climate"]["ref_gcm_name"], + help="text file full of commands to run (ex. CanESM2 or CESM2)", + ) + parser.add_argument( + "-gcm_name", + action="store", + type=str, + default=pygem_prms["climate"]["gcm_name"], + help="GCM name used for model run", + ) + parser.add_argument( + "-scenario", + action="store", + type=none_or_value, + default=pygem_prms["climate"]["scenario"], + help="rcp or ssp scenario used for model run (ex. rcp26 or ssp585)", + ) + parser.add_argument( + "-realization", + action="store", + type=str, + default=None, + help="realization from large ensemble used for model run (ex. 1011.001 or 1301.020)", + ) + parser.add_argument( + "-realization_list", + action="store", + type=str, + default=None, + help="text file full of realizations to run", + ) + parser.add_argument( + "-gcm_startyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_startyear"], + help="start year for the model run", + ) + parser.add_argument( + "-gcm_endyear", + action="store", + type=int, + default=pygem_prms["climate"]["gcm_endyear"], + help="start year for the model run", + ) + parser.add_argument( + "-mcmc_burn_pct", + action="store", + type=int, + default=0, + help="percent of MCMC chain to burn off from beginning (defaults to 0, assuming that burn in was performed in calibration)", + ) + parser.add_argument( + "-ncores", + action="store", + type=int, + default=1, + help="number of simultaneous processes (cores) to use", + ) + parser.add_argument( + "-batch_number", + action="store", + type=int, + default=None, + help="Batch number used to differentiate output on supercomputer", + ) + parser.add_argument( + "-kp", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["kp"], + help="Precipitation bias", + ) + parser.add_argument( + "-tbias", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["tbias"], + help="Temperature bias", + ) + parser.add_argument( + "-ddfsnow", + action="store", + type=float, + default=pygem_prms["sim"]["params"]["ddfsnow"], + help="Degree-day factor of snow", + ) + parser.add_argument( + "-oggm_working_dir", + action="store", + type=str, + default=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}", + help="Specify OGGM working dir - useful if performing a grid search and have duplicated glacier directories", + ) + parser.add_argument( + "-option_calibration", + action="store", + type=none_or_value, + default=pygem_prms["calib"]["option_calibration"], + help='calibration option ("emulator", "MCMC", "HH2015", "HH2015mod", "None")', + ) + parser.add_argument( + "-option_dynamics", + action="store", + type=none_or_value, + default=pygem_prms["sim"]["option_dynamics"], + help="glacier dynamics scheme (options: ``OGGM`, `MassRedistributionCurves`, `None`)", + ) + parser.add_argument( + "-use_reg_glena", + action="store", + type=bool, + default=pygem_prms["sim"]["oggm_dynamics"]["use_reg_glena"], + help="Take the glacier flow parameterization from regionally calibrated priors (boolean: `0` or `1`, `True` or `False`)", + ) + parser.add_argument( + "-option_bias_adjustment", + action="store", + type=int, + default=pygem_prms["sim"]["option_bias_adjustment"], + help="Bias adjustment option (options: `0`, `1`, `2`, `3`. 0: no adjustment, \ 1: new prec scheme and temp building on HH2015, \ - 2: HH2015 methods, 3: quantile delta mapping)') - parser.add_argument('-nsims', action='store', type=int, default=pygem_prms['sim']['nsims'], - help='number of simulations (note, defaults to 1 if `option_calibration` != `MCMC`)') - parser.add_argument('-modelprms_fp', action='store', type=str, default=None, - help='model parameters filepath') + 2: HH2015 methods, 3: quantile delta mapping)", + ) + parser.add_argument( + "-nsims", + action="store", + type=int, + default=pygem_prms["sim"]["nsims"], + help="number of simulations (note, defaults to 1 if `option_calibration` != `MCMC`)", + ) + parser.add_argument( + "-modelprms_fp", + action="store", + type=str, + default=None, + help="model parameters filepath", + ) # flags - parser.add_argument('-export_all_simiters', action='store_true', - help='Flag to export data from all simulations', default=pygem_prms['sim']['out']['export_all_simiters']) - parser.add_argument('-export_extra_vars', action='store_true', - help='Flag to export extra variables (temp, prec, melt, acc, etc.)', default=pygem_prms['sim']['out']['export_extra_vars']) - parser.add_argument('-export_binned_data', action='store_true', - help='Flag to export binned data', default=pygem_prms['sim']['out']['export_binned_data']) - parser.add_argument('-export_binned_components', action='store_true', - help='Flag to export binned mass balance components (melt, accumulation, refreeze)', default=pygem_prms['sim']['out']['export_binned_components']) - parser.add_argument('-option_ordered', action='store_true', - help='Flag to keep glacier lists ordered (default is off)') - parser.add_argument('-v', '--debug', action='store_true', - help='Flag for debugging') + parser.add_argument( + "-export_all_simiters", + action="store_true", + help="Flag to export data from all simulations", + default=pygem_prms["sim"]["out"]["export_all_simiters"], + ) + parser.add_argument( + "-export_extra_vars", + action="store_true", + help="Flag to export extra variables (temp, prec, melt, acc, etc.)", + default=pygem_prms["sim"]["out"]["export_extra_vars"], + ) + parser.add_argument( + "-export_binned_data", + action="store_true", + help="Flag to export binned data", + default=pygem_prms["sim"]["out"]["export_binned_data"], + ) + parser.add_argument( + "-export_binned_components", + action="store_true", + help="Flag to export binned mass balance components (melt, accumulation, refreeze)", + default=pygem_prms["sim"]["out"]["export_binned_components"], + ) + parser.add_argument( + "-option_ordered", + action="store_true", + help="Flag to keep glacier lists ordered (default is off)", + ) + parser.add_argument( + "-v", "--debug", action="store_true", help="Flag for debugging" + ) return parser @@ -199,44 +355,50 @@ def run(list_packed_vars): gcm_name = list_packed_vars[2] realization = list_packed_vars[3] if (gcm_name != args.ref_gcm_name) and (args.scenario is None): - scenario = os.path.basename(args.gcm_list_fn).split('_')[1] + scenario = os.path.basename(args.gcm_list_fn).split("_")[1] else: scenario = args.scenario debug = args.debug if debug: - print(f'scenario:{scenario}') + print(f"scenario:{scenario}") # ===== LOAD GLACIERS ===== main_glac_rgi = modelsetup.selectglaciersrgitable(glac_no=glac_no) - + # ===== TIME PERIOD ===== # Reference Calibration Period # adjust end year in event that gcm_end year precedes ref_startyear ref_endyear = min([args.ref_endyear, args.gcm_endyear]) dates_table_ref = modelsetup.datesmodelrun( - startyear=args.ref_startyear, endyear=ref_endyear, - spinupyears=pygem_prms['climate']['ref_spinupyears'], - option_wateryear=pygem_prms['climate']['ref_wateryear']) - + startyear=args.ref_startyear, + endyear=ref_endyear, + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], + ) + # GCM Full Period (includes reference and simulation periods) dates_table_full = modelsetup.datesmodelrun( - startyear=min([args.ref_startyear,args.gcm_startyear]), - endyear=args.gcm_endyear, spinupyears=pygem_prms['climate']['gcm_spinupyears'], - option_wateryear=pygem_prms['climate']['gcm_wateryear']) - + startyear=min([args.ref_startyear, args.gcm_startyear]), + endyear=args.gcm_endyear, + spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + option_wateryear=pygem_prms["climate"]["gcm_wateryear"], + ) + # GCM Simulation Period dates_table = modelsetup.datesmodelrun( - startyear=args.gcm_startyear, endyear=args.gcm_endyear, - spinupyears=pygem_prms['climate']['gcm_spinupyears'], - option_wateryear=pygem_prms['climate']['gcm_wateryear']) + startyear=args.gcm_startyear, + endyear=args.gcm_endyear, + spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + option_wateryear=pygem_prms["climate"]["gcm_wateryear"], + ) if debug: - print('ref years:', args.ref_startyear, ref_endyear) - print('sim years:', args.gcm_startyear, args.gcm_endyear) - + print("ref years:", args.ref_startyear, ref_endyear) + print("sim years:", args.gcm_startyear, args.gcm_endyear) + # ===== LOAD CLIMATE DATA ===== # Climate class - if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: + if gcm_name in ["ERA5", "ERA-Interim", "COAWST"]: gcm = class_climate.GCM(name=gcm_name) ref_gcm = gcm dates_table_ref = dates_table_full @@ -245,808 +407,1527 @@ def run(list_packed_vars): if realization is None: gcm = class_climate.GCM(name=gcm_name, scenario=scenario) else: - gcm = class_climate.GCM(name=gcm_name, scenario=scenario, realization=realization) + gcm = class_climate.GCM( + name=gcm_name, scenario=scenario, realization=realization + ) # Reference GCM ref_gcm = class_climate.GCM(name=args.ref_gcm_name) - + # ----- Select Temperature and Precipitation Data ----- # Air temperature [degC] - gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.temp_fn, gcm.temp_vn, main_glac_rgi, - dates_table_full) - ref_temp, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.temp_fn, ref_gcm.temp_vn, - main_glac_rgi, dates_table_ref) + gcm_temp, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.temp_fn, gcm.temp_vn, main_glac_rgi, dates_table_full + ) + ref_temp, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.temp_fn, ref_gcm.temp_vn, main_glac_rgi, dates_table_ref + ) # Precipitation [m] - gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.prec_fn, gcm.prec_vn, main_glac_rgi, - dates_table_full) - ref_prec, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.prec_fn, ref_gcm.prec_vn, - main_glac_rgi, dates_table_ref) + gcm_prec, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.prec_fn, gcm.prec_vn, main_glac_rgi, dates_table_full + ) + ref_prec, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.prec_fn, ref_gcm.prec_vn, main_glac_rgi, dates_table_ref + ) # Elevation [m asl] try: - gcm_elev = gcm.importGCMfxnearestneighbor_xarray(gcm.elev_fn, gcm.elev_vn, main_glac_rgi) + gcm_elev = gcm.importGCMfxnearestneighbor_xarray( + gcm.elev_fn, gcm.elev_vn, main_glac_rgi + ) except: gcm_elev = None - ref_elev = ref_gcm.importGCMfxnearestneighbor_xarray(ref_gcm.elev_fn, ref_gcm.elev_vn, main_glac_rgi) - + ref_elev = ref_gcm.importGCMfxnearestneighbor_xarray( + ref_gcm.elev_fn, ref_gcm.elev_vn, main_glac_rgi + ) + # ----- Temperature and Precipitation Bias Adjustments ----- # No adjustments if args.option_bias_adjustment == 0 or gcm_name == args.ref_gcm_name: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' - sim_idx_start = dates_table_full[dates_cn].to_list().index(args.gcm_startyear) + dates_cn = "year" + sim_idx_start = ( + dates_table_full[dates_cn].to_list().index(args.gcm_startyear) + ) gcm_elev_adj = gcm_elev - gcm_temp_adj = gcm_temp[:,sim_idx_start:] - gcm_prec_adj = gcm_prec[:,sim_idx_start:] + gcm_temp_adj = gcm_temp[:, sim_idx_start:] + gcm_prec_adj = gcm_prec[:, sim_idx_start:] # Bias correct based on reference climate data else: # OPTION 1: Adjust temp using Huss and Hock (2015), prec similar but addresses for variance and outliers if args.option_bias_adjustment == 1: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_opt1( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # OPTION 2: Adjust temp and prec using Huss and Hock (2015) elif args.option_bias_adjustment == 2: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_HH2015( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_HH2015( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # OPTION 3: Adjust temp and prec using quantile delta mapping, Cannon et al. (2015) elif args.option_bias_adjustment == 3: # Temperature bias correction - gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) - + gcm_temp_adj, gcm_elev_adj = gcmbiasadj.temp_biasadj_QDM( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) # Precipitation bias correction - gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, - dates_table_ref, dates_table_full, - args.gcm_startyear, args.ref_startyear, - ref_spinupyears=pygem_prms['climate']['ref_spinupyears'], - gcm_spinupyears=pygem_prms['climate']['gcm_spinupyears']) - + gcm_prec_adj, gcm_elev_adj = gcmbiasadj.prec_biasadj_QDM( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ref_spinupyears=pygem_prms["climate"]["ref_spinupyears"], + gcm_spinupyears=pygem_prms["climate"]["gcm_spinupyears"], + ) + # assert that the gcm_elev_adj is not None - assert gcm_elev_adj is not None, 'No GCM elevation data' - + assert gcm_elev_adj is not None, "No GCM elevation data" + # ----- Other Climate Datasets (Air temperature variability [degC] and Lapse rate [K m-1]) # Air temperature variability [degC] - if pygem_prms['mb']['option_ablation'] != 2: - gcm_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) - ref_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table_ref.shape[0])) - elif pygem_prms['mb']['option_ablation'] == 2 and gcm_name in ['ERA5']: - gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.tempstd_fn, gcm.tempstd_vn, - main_glac_rgi, dates_table) + if pygem_prms["mb"]["option_ablation"] != 2: + gcm_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) + ref_tempstd = np.zeros( + (main_glac_rgi.shape[0], dates_table_ref.shape[0]) + ) + elif pygem_prms["mb"]["option_ablation"] == 2 and gcm_name in ["ERA5"]: + gcm_tempstd, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.tempstd_fn, gcm.tempstd_vn, main_glac_rgi, dates_table + ) ref_tempstd = gcm_tempstd - elif pygem_prms['mb']['option_ablation'] == 2 and args.ref_gcm_name in ['ERA5']: + elif pygem_prms["mb"]["option_ablation"] == 2 and args.ref_gcm_name in [ + "ERA5" + ]: # Compute temp std based on reference climate data - ref_tempstd, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.tempstd_fn, ref_gcm.tempstd_vn, - main_glac_rgi, dates_table_ref) + ref_tempstd, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.tempstd_fn, + ref_gcm.tempstd_vn, + main_glac_rgi, + dates_table_ref, + ) # Monthly average from reference climate data - gcm_tempstd = gcmbiasadj.monthly_avg_array_rolled(ref_tempstd, dates_table_ref, dates_table_full) + gcm_tempstd = gcmbiasadj.monthly_avg_array_rolled( + ref_tempstd, dates_table_ref, dates_table_full + ) else: - gcm_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) - ref_tempstd = np.zeros((main_glac_rgi.shape[0],dates_table_ref.shape[0])) + gcm_tempstd = np.zeros((main_glac_rgi.shape[0], dates_table.shape[0])) + ref_tempstd = np.zeros( + (main_glac_rgi.shape[0], dates_table_ref.shape[0]) + ) # Lapse rate - if gcm_name in ['ERA-Interim', 'ERA5']: - gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray(gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table) + if gcm_name in ["ERA-Interim", "ERA5"]: + gcm_lr, gcm_dates = gcm.importGCMvarnearestneighbor_xarray( + gcm.lr_fn, gcm.lr_vn, main_glac_rgi, dates_table + ) ref_lr = gcm_lr else: # Compute lapse rates based on reference climate data - ref_lr, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray(ref_gcm.lr_fn, ref_gcm.lr_vn, main_glac_rgi, - dates_table_ref) + ref_lr, ref_dates = ref_gcm.importGCMvarnearestneighbor_xarray( + ref_gcm.lr_fn, ref_gcm.lr_vn, main_glac_rgi, dates_table_ref + ) # Monthly average from reference climate data - gcm_lr = gcmbiasadj.monthly_avg_array_rolled(ref_lr, dates_table_ref, dates_table_full, args.gcm_startyear, args.ref_startyear) - - + gcm_lr = gcmbiasadj.monthly_avg_array_rolled( + ref_lr, + dates_table_ref, + dates_table_full, + args.gcm_startyear, + args.ref_startyear, + ) + # ===== RUN MASS BALANCE ===== # Number of simulations - if args.option_calibration == 'MCMC': + if args.option_calibration == "MCMC": nsims = args.nsims else: nsims = 1 - + # Number of years (for OGGM's run_until_and_store) - if pygem_prms['time']['timestep'] == 'monthly': - nyears = int(dates_table.shape[0]/12) - nyears_ref = int(dates_table_ref.shape[0]/12) + if pygem_prms["time"]["timestep"] == "monthly": + nyears = int(dates_table.shape[0] / 12) + nyears_ref = int(dates_table_ref.shape[0] / 12) else: - assert True==False, 'Adjust nyears for non-monthly timestep' + assert True == False, "Adjust nyears for non-monthly timestep" for glac in range(main_glac_rgi.shape[0]): if glac == 0: - print(gcm_name,':', main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId']) + print( + gcm_name, + ":", + main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"], + ) # Select subsets of data - glacier_rgi_table = main_glac_rgi.loc[main_glac_rgi.index.values[glac], :] - glacier_str = '{0:0.5f}'.format(glacier_rgi_table['RGIId_float']) + glacier_rgi_table = main_glac_rgi.loc[ + main_glac_rgi.index.values[glac], : + ] + glacier_str = "{0:0.5f}".format(glacier_rgi_table["RGIId_float"]) reg_str = str(glacier_rgi_table.O1Region).zfill(2) - rgiid = main_glac_rgi.loc[main_glac_rgi.index.values[glac],'RGIId'] + rgiid = main_glac_rgi.loc[main_glac_rgi.index.values[glac], "RGIId"] try: - # for batman in [0]: + # for batman in [0]: # ===== Load glacier data: area (km2), ice thickness (m), width (km) ===== - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: - gdir = single_flowline_glacier_directory(glacier_str, working_dir=args.oggm_working_dir) + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + gdir = single_flowline_glacier_directory( + glacier_str, working_dir=args.oggm_working_dir + ) gdir.is_tidewater = False calving_k = None else: - gdir = single_flowline_glacier_directory_with_calving(glacier_str, working_dir=args.oggm_working_dir) + gdir = single_flowline_glacier_directory_with_calving( + glacier_str, working_dir=args.oggm_working_dir + ) gdir.is_tidewater = True - cfg.PARAMS['use_kcalving_for_inversion'] = True - cfg.PARAMS['use_kcalving_for_run'] = True + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True # Flowlines - fls = gdir.read_pickle('inversion_flowlines') - + fls = gdir.read_pickle("inversion_flowlines") + # Reference gdir for ice thickness inversion gdir_ref = copy.deepcopy(gdir) - gdir_ref.historical_climate = {'elev': ref_elev[glac], - 'temp': ref_temp[glac,:], - 'tempstd': ref_tempstd[glac,:], - 'prec': ref_prec[glac,:], - 'lr': ref_lr[glac,:]} + gdir_ref.historical_climate = { + "elev": ref_elev[glac], + "temp": ref_temp[glac, :], + "tempstd": ref_tempstd[glac, :], + "prec": ref_prec[glac, :], + "lr": ref_lr[glac, :], + } gdir_ref.dates_table = dates_table_ref # Add climate data to glacier directory - if pygem_prms['climate']['hindcast'] == True: + if pygem_prms["climate"]["hindcast"] == True: gcm_temp_adj = gcm_temp_adj[::-1] gcm_tempstd = gcm_tempstd[::-1] - gcm_prec_adj= gcm_prec_adj[::-1] + gcm_prec_adj = gcm_prec_adj[::-1] gcm_lr = gcm_lr[::-1] - - gdir.historical_climate = {'elev': gcm_elev_adj[glac], - 'temp': gcm_temp_adj[glac,:], - 'tempstd': gcm_tempstd[glac,:], - 'prec': gcm_prec_adj[glac,:], - 'lr': gcm_lr[glac,:]} + + gdir.historical_climate = { + "elev": gcm_elev_adj[glac], + "temp": gcm_temp_adj[glac, :], + "tempstd": gcm_tempstd[glac, :], + "prec": gcm_prec_adj[glac, :], + "lr": gcm_lr[glac, :], + } gdir.dates_table = dates_table - + glacier_area_km2 = fls[0].widths_m * fls[0].dx_meter / 1e6 if (fls is not None) and (glacier_area_km2.sum() > 0): - # Load model parameters if args.option_calibration: modelprms_fp = args.modelprms_fp - if not modelprms_fp: - modelprms_fn = glacier_str + '-modelprms_dict.json' - modelprms_fp = (pygem_prms['root'] + '/Output/calibration/' + glacier_str.split('.')[0].zfill(2) - + '/') + modelprms_fn - - assert os.path.exists(modelprms_fp), 'Calibrated parameters do not exist.' - with open(modelprms_fp, 'r') as f: + if not modelprms_fp: + modelprms_fn = glacier_str + "-modelprms_dict.json" + modelprms_fp = ( + pygem_prms["root"] + + "/Output/calibration/" + + glacier_str.split(".")[0].zfill(2) + + "/" + ) + modelprms_fn + + assert os.path.exists(modelprms_fp), ( + "Calibrated parameters do not exist." + ) + with open(modelprms_fp, "r") as f: modelprms_dict = json.load(f) - - assert args.option_calibration in modelprms_dict, ('Error: ' + args.option_calibration + - ' not in modelprms_dict') + + assert args.option_calibration in modelprms_dict, ( + "Error: " + + args.option_calibration + + " not in modelprms_dict" + ) modelprms_all = modelprms_dict[args.option_calibration] # MCMC needs model parameters to be selected - if args.option_calibration == 'MCMC': + if args.option_calibration == "MCMC": if nsims == 1: - modelprms_all = {'kp': [np.median(modelprms_all['kp']['chain_0'])], - 'tbias': [np.median(modelprms_all['tbias']['chain_0'])], - 'ddfsnow': [np.median(modelprms_all['ddfsnow']['chain_0'])], - 'ddfice': [np.median(modelprms_all['ddfice']['chain_0'])], - 'tsnow_threshold': modelprms_all['tsnow_threshold'], - 'precgrad': modelprms_all['precgrad']} + modelprms_all = { + "kp": [ + np.median(modelprms_all["kp"]["chain_0"]) + ], + "tbias": [ + np.median( + modelprms_all["tbias"]["chain_0"] + ) + ], + "ddfsnow": [ + np.median( + modelprms_all["ddfsnow"]["chain_0"] + ) + ], + "ddfice": [ + np.median( + modelprms_all["ddfice"]["chain_0"] + ) + ], + "tsnow_threshold": modelprms_all[ + "tsnow_threshold" + ], + "precgrad": modelprms_all["precgrad"], + } else: # Select every kth iteration to use for the ensemble - mcmc_sample_no = len(modelprms_all['kp']['chain_0']) - sims_burn = int(args.mcmc_burn_pct/100*mcmc_sample_no) - mp_spacing = int((mcmc_sample_no - sims_burn) / nsims) - mp_idx_start = np.arange(sims_burn, sims_burn + mp_spacing) + mcmc_sample_no = len( + modelprms_all["kp"]["chain_0"] + ) + sims_burn = int( + args.mcmc_burn_pct / 100 * mcmc_sample_no + ) + mp_spacing = int( + (mcmc_sample_no - sims_burn) / nsims + ) + mp_idx_start = np.arange( + sims_burn, sims_burn + mp_spacing + ) np.random.shuffle(mp_idx_start) mp_idx_start = mp_idx_start[0] - mp_idx_all = np.arange(mp_idx_start, mcmc_sample_no, mp_spacing) + mp_idx_all = np.arange( + mp_idx_start, mcmc_sample_no, mp_spacing + ) modelprms_all = { - 'kp': [modelprms_all['kp']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'tbias': [modelprms_all['tbias']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'ddfsnow': [modelprms_all['ddfsnow']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'ddfice': [modelprms_all['ddfice']['chain_0'][mp_idx] for mp_idx in mp_idx_all], - 'tsnow_threshold': modelprms_all['tsnow_threshold'] * nsims, - 'precgrad': modelprms_all['precgrad'] * nsims} + "kp": [ + modelprms_all["kp"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "tbias": [ + modelprms_all["tbias"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "ddfsnow": [ + modelprms_all["ddfsnow"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "ddfice": [ + modelprms_all["ddfice"]["chain_0"][mp_idx] + for mp_idx in mp_idx_all + ], + "tsnow_threshold": modelprms_all[ + "tsnow_threshold" + ] + * nsims, + "precgrad": modelprms_all["precgrad"] * nsims, + } else: nsims = 1 - + # Calving parameter - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): calving_k = None else: - # Load quality controlled frontal ablation data + # Load quality controlled frontal ablation data fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['frontalablation']['frontalablation_relpath']}/analysis/{pygem_prms['calib']['data']['frontalablation']['frontalablation_cal_fn']}" - assert os.path.exists(fp), 'Calibrated calving dataset does not exist' + assert os.path.exists(fp), ( + "Calibrated calving dataset does not exist" + ) calving_df = pd.read_csv(fp) calving_rgiids = list(calving_df.RGIId) - + # Use calibrated value if individual data available if rgiid in calving_rgiids: calving_idx = calving_rgiids.index(rgiid) - calving_k = calving_df.loc[calving_idx, 'calving_k'] - calving_k_nmad = calving_df.loc[calving_idx, 'calving_k_nmad'] + calving_k = calving_df.loc[ + calving_idx, "calving_k" + ] + calving_k_nmad = calving_df.loc[ + calving_idx, "calving_k_nmad" + ] # Otherwise, use region's median value else: - calving_df['O1Region'] = [int(x.split('-')[1].split('.')[0]) for x in calving_df.RGIId.values] - calving_df_reg = calving_df.loc[calving_df['O1Region'] == int(reg_str), :] + calving_df["O1Region"] = [ + int(x.split("-")[1].split(".")[0]) + for x in calving_df.RGIId.values + ] + calving_df_reg = calving_df.loc[ + calving_df["O1Region"] == int(reg_str), : + ] calving_k = np.median(calving_df_reg.calving_k) calving_k_nmad = 0 - + if nsims == 1: calving_k_values = np.array([calving_k]) else: - calving_k_values = calving_k + np.random.normal(loc=0, scale=calving_k_nmad, size=nsims) + calving_k_values = calving_k + np.random.normal( + loc=0, scale=calving_k_nmad, size=nsims + ) calving_k_values[calving_k_values < 0.001] = 0.001 calving_k_values[calving_k_values > 5] = 5 - -# calving_k_values[:] = calving_k - - while not abs(np.median(calving_k_values) - calving_k) < 0.001: - calving_k_values = calving_k + np.random.normal(loc=0, scale=calving_k_nmad, size=nsims) - calving_k_values[calving_k_values < 0.001] = 0.001 + + # calving_k_values[:] = calving_k + + while ( + not abs( + np.median(calving_k_values) - calving_k + ) + < 0.001 + ): + calving_k_values = ( + calving_k + + np.random.normal( + loc=0, scale=calving_k_nmad, size=nsims + ) + ) + calving_k_values[calving_k_values < 0.001] = ( + 0.001 + ) calving_k_values[calving_k_values > 5] = 5 - -# print(calving_k, np.median(calving_k_values)) - - assert abs(np.median(calving_k_values) - calving_k) < 0.001, 'calving_k distribution too far off' - if debug: - print('calving_k_values:', np.mean(calving_k_values), np.std(calving_k_values), '\n', calving_k_values) + # print(calving_k, np.median(calving_k_values)) - + assert ( + abs(np.median(calving_k_values) - calving_k) + < 0.001 + ), "calving_k distribution too far off" + + if debug: + print( + "calving_k_values:", + np.mean(calving_k_values), + np.std(calving_k_values), + "\n", + calving_k_values, + ) else: - modelprms_all = {'kp': [args.kp], - 'tbias': [args.tbias], - 'ddfsnow': [args.ddfsnow], - 'ddfice': [args.ddfsnow / pygem_prms['sim']['params']['ddfsnow_iceratio']], - 'tsnow_threshold': [pygem_prms['sim']['params']['tsnow_threshold']], - 'precgrad': [pygem_prms['sim']['params']['precgrad']]} - calving_k = np.zeros(nsims) + pygem_prms['sim']['params']['calving_k'] + modelprms_all = { + "kp": [args.kp], + "tbias": [args.tbias], + "ddfsnow": [args.ddfsnow], + "ddfice": [ + args.ddfsnow + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ], + "tsnow_threshold": [ + pygem_prms["sim"]["params"]["tsnow_threshold"] + ], + "precgrad": [pygem_prms["sim"]["params"]["precgrad"]], + } + calving_k = ( + np.zeros(nsims) + + pygem_prms["sim"]["params"]["calving_k"] + ) calving_k_values = calving_k - + if debug and gdir.is_tidewater: - print('calving_k:', calving_k) - + print("calving_k:", calving_k) # Load OGGM glacier dynamics parameters (if necessary) - if args.option_dynamics in ['OGGM', 'MassRedistributionCurves']: - + if args.option_dynamics in [ + "OGGM", + "MassRedistributionCurves", + ]: # CFL number (may use different values for calving to prevent errors) - if not glacier_rgi_table['TermType'] in [1,5] or not pygem_prms['setup']['include_frontalablation']: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number'] + if ( + glacier_rgi_table["TermType"] not in [1, 5] + or not pygem_prms["setup"]["include_frontalablation"] + ): + cfg.PARAMS["cfl_number"] = pygem_prms["sim"][ + "oggm_dynamics" + ]["cfl_number"] else: - cfg.PARAMS['cfl_number'] = pygem_prms['sim']['oggm_dynamics']['cfl_number_calving'] + cfg.PARAMS["cfl_number"] = pygem_prms["sim"][ + "oggm_dynamics" + ]["cfl_number_calving"] - if debug: - print('cfl number:', cfg.PARAMS['cfl_number']) - + print("cfl number:", cfg.PARAMS["cfl_number"]) + if args.use_reg_glena: - glena_df = pd.read_csv(f"{pygem_prms['root']}/{pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']}") - glena_O1regions = [int(x) for x in glena_df.O1Region.values] - assert glacier_rgi_table.O1Region in glena_O1regions, glacier_str + ' O1 region not in glena_df' - glena_idx = np.where(glena_O1regions == glacier_rgi_table.O1Region)[0][0] - glen_a_multiplier = glena_df.loc[glena_idx,'glens_a_multiplier'] - fs = glena_df.loc[glena_idx,'fs'] + glena_df = pd.read_csv( + f"{pygem_prms['root']}/{pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']}" + ) + glena_O1regions = [ + int(x) for x in glena_df.O1Region.values + ] + assert glacier_rgi_table.O1Region in glena_O1regions, ( + glacier_str + " O1 region not in glena_df" + ) + glena_idx = np.where( + glena_O1regions == glacier_rgi_table.O1Region + )[0][0] + glen_a_multiplier = glena_df.loc[ + glena_idx, "glens_a_multiplier" + ] + fs = glena_df.loc[glena_idx, "fs"] else: args.option_dynamics = None - fs = pygem_prms['sim']['oggm_dynamics']['fs'] - glen_a_multiplier = pygem_prms['sim']['oggm_dynamics']['glen_a_multiplier'] - + fs = pygem_prms["sim"]["oggm_dynamics"]["fs"] + glen_a_multiplier = pygem_prms["sim"]["oggm_dynamics"][ + "glen_a_multiplier" + ] + # Time attributes and values - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - annual_columns = np.unique(dates_table['wateryear'].values)[0:int(dates_table.shape[0]/12)] + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + annual_columns = np.unique( + dates_table["wateryear"].values + )[0 : int(dates_table.shape[0] / 12)] else: - annual_columns = np.unique(dates_table['year'].values)[0:int(dates_table.shape[0]/12)] + annual_columns = np.unique(dates_table["year"].values)[ + 0 : int(dates_table.shape[0] / 12) + ] # append additional year to year_values to account for mass and area at end of period - year_values = annual_columns[pygem_prms['climate']['gcm_spinupyears']:annual_columns.shape[0]] - year_values = np.concatenate((year_values, np.array([annual_columns[-1] + 1]))) - output_glac_temp_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_prec_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_acc_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_refreeze_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_melt_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_frontalablation_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_massbaltotal_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_runoff_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_snowline_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_glac_area_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_bsl_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_glac_mass_change_ignored_annual = np.zeros((year_values.shape[0], nsims)) - output_glac_ELA_annual = np.zeros((year_values.shape[0], nsims)) * np.nan - output_offglac_prec_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_refreeze_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_melt_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_snowpack_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan - output_offglac_runoff_monthly = np.zeros((dates_table.shape[0], nsims)) * np.nan + year_values = annual_columns[ + pygem_prms["climate"][ + "gcm_spinupyears" + ] : annual_columns.shape[0] + ] + year_values = np.concatenate( + (year_values, np.array([annual_columns[-1] + 1])) + ) + output_glac_temp_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_prec_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_acc_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_refreeze_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_melt_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_frontalablation_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_massbaltotal_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_runoff_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_snowline_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_glac_area_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_bsl_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_glac_mass_change_ignored_annual = np.zeros( + (year_values.shape[0], nsims) + ) + output_glac_ELA_annual = ( + np.zeros((year_values.shape[0], nsims)) * np.nan + ) + output_offglac_prec_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_refreeze_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_melt_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_snowpack_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) + output_offglac_runoff_monthly = ( + np.zeros((dates_table.shape[0], nsims)) * np.nan + ) output_glac_bin_icethickness_annual = None - + # Loop through model parameters count_exceed_boundary_errors = 0 mb_em_sims = [] for n_iter in range(nsims): + if debug: + print("n_iter:", n_iter) - if debug: - print('n_iter:', n_iter) - if calving_k is not None: calving_k = calving_k_values[n_iter] - cfg.PARAMS['calving_k'] = calving_k - cfg.PARAMS['inversion_calving_k'] = calving_k - + cfg.PARAMS["calving_k"] = calving_k + cfg.PARAMS["inversion_calving_k"] = calving_k + # successful_run used to continue runs when catching specific errors successful_run = True - - modelprms = {'kp': modelprms_all['kp'][n_iter], - 'tbias': modelprms_all['tbias'][n_iter], - 'ddfsnow': modelprms_all['ddfsnow'][n_iter], - 'ddfice': modelprms_all['ddfice'][n_iter], - 'tsnow_threshold': modelprms_all['tsnow_threshold'][n_iter], - 'precgrad': modelprms_all['precgrad'][n_iter]} - - if debug: - print(glacier_str + ' kp: ' + str(np.round(modelprms['kp'],2)) + - ' ddfsnow: ' + str(np.round(modelprms['ddfsnow'],4)) + - ' tbias: ' + str(np.round(modelprms['tbias'],2))) - #%% + modelprms = { + "kp": modelprms_all["kp"][n_iter], + "tbias": modelprms_all["tbias"][n_iter], + "ddfsnow": modelprms_all["ddfsnow"][n_iter], + "ddfice": modelprms_all["ddfice"][n_iter], + "tsnow_threshold": modelprms_all["tsnow_threshold"][ + n_iter + ], + "precgrad": modelprms_all["precgrad"][n_iter], + } + + if debug: + print( + glacier_str + + " kp: " + + str(np.round(modelprms["kp"], 2)) + + " ddfsnow: " + + str(np.round(modelprms["ddfsnow"], 4)) + + " tbias: " + + str(np.round(modelprms["tbias"], 2)) + ) + + # %% # ----- ICE THICKNESS INVERSION using OGGM ----- if args.option_dynamics is not None: # Apply inversion_filter on mass balance with debris to avoid negative flux - if pygem_prms['mb']['include_debris']: + if pygem_prms["mb"]["include_debris"]: inversion_filter = True else: inversion_filter = False - + # Perform inversion based on PyGEM MB using reference directory - mbmod_inv = PyGEMMassBalance(gdir_ref, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True, - inversion_filter=inversion_filter) -# if debug: -# h, w = gdir.get_inversion_flowline_hw() -# mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * -# pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) -# plt.plot(mb_t0, h, '.') -# plt.ylabel('Elevation') -# plt.xlabel('Mass balance (mwea)') -# plt.show() + mbmod_inv = PyGEMMassBalance( + gdir_ref, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + inversion_filter=inversion_filter, + ) + # if debug: + # h, w = gdir.get_inversion_flowline_hw() + # mb_t0 = (mbmod_inv.get_annual_mb(h, year=0, fl_id=0, fls=fls) * cfg.SEC_IN_YEAR * + # pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + # plt.plot(mb_t0, h, '.') + # plt.ylabel('Elevation') + # plt.xlabel('Mass balance (mwea)') + # plt.show() # Non-tidewater glaciers - if not gdir.is_tidewater or not pygem_prms['setup']['include_frontalablation']: + if ( + not gdir.is_tidewater + or not pygem_prms["setup"][ + "include_frontalablation" + ] + ): # Arbitrariliy shift the MB profile up (or down) until mass balance is zero (equilibrium for inversion) - apparent_mb_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref)) + apparent_mb_from_any_mb( + gdir, + mb_model=mbmod_inv, + mb_years=np.arange(nyears_ref), + ) tasks.prepare_for_inversion(gdir) - tasks.mass_conservation_inversion(gdir, glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) + tasks.mass_conservation_inversion( + gdir, + glen_a=cfg.PARAMS["glen_a"] + * glen_a_multiplier, + fs=fs, + ) # Tidewater glaciers else: - cfg.PARAMS['use_kcalving_for_inversion'] = True - cfg.PARAMS['use_kcalving_for_run'] = True - tasks.find_inversion_calving_from_any_mb(gdir, mb_model=mbmod_inv, mb_years=np.arange(nyears_ref), - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs) - + cfg.PARAMS["use_kcalving_for_inversion"] = True + cfg.PARAMS["use_kcalving_for_run"] = True + tasks.find_inversion_calving_from_any_mb( + gdir, + mb_model=mbmod_inv, + mb_years=np.arange(nyears_ref), + glen_a=cfg.PARAMS["glen_a"] + * glen_a_multiplier, + fs=fs, + ) + # ----- INDENTED TO BE JUST WITH DYNAMICS ----- - tasks.init_present_time_glacier(gdir) # adds bins below - if pygem_prms['mb']['include_debris']: - debris.debris_binned(gdir, fl_str='model_flowlines') # add debris enhancement factors to flowlines - + tasks.init_present_time_glacier( + gdir + ) # adds bins below + if pygem_prms["mb"]["include_debris"]: + debris.debris_binned( + gdir, fl_str="model_flowlines" + ) # add debris enhancement factors to flowlines + try: - nfls = gdir.read_pickle('model_flowlines') + nfls = gdir.read_pickle("model_flowlines") except FileNotFoundError as e: - if 'model_flowlines.pkl' in str(e): + if "model_flowlines.pkl" in str(e): tasks.compute_downstream_line(gdir) tasks.compute_downstream_bedshape(gdir) - tasks.init_present_time_glacier(gdir) # adds bins below - nfls = gdir.read_pickle('model_flowlines') + tasks.init_present_time_glacier( + gdir + ) # adds bins below + nfls = gdir.read_pickle("model_flowlines") else: raise # Water Level # Check that water level is within given bounds - cls = gdir.read_pickle('inversion_input')[-1] - th = cls['hgt'][-1] - vmin, vmax = cfg.PARAMS['free_board_marine_terminating'] - water_level = utils.clip_scalar(0, th - vmax, th - vmin) - + cls = gdir.read_pickle("inversion_input")[-1] + th = cls["hgt"][-1] + vmin, vmax = cfg.PARAMS[ + "free_board_marine_terminating" + ] + water_level = utils.clip_scalar( + 0, th - vmax, th - vmin + ) + # No ice dynamics options else: nfls = fls - + # Record initial surface h for overdeepening calculations surface_h_initial = nfls[0].surface_h - + # ------ MODEL WITH EVOLVING AREA ------ # Mass balance model - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=nfls, option_areaconstant=False) + mbmod = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=nfls, + option_areaconstant=False, + ) # Glacier dynamics model - if args.option_dynamics == 'OGGM': + if args.option_dynamics == "OGGM": if debug: - print('OGGM GLACIER DYNAMICS!') - + print("OGGM GLACIER DYNAMICS!") + # new numerical scheme is SemiImplicitModel() but doesn't have frontal ablation yet # FluxBasedModel is old numerical scheme but includes frontal ablation - ev_model = FluxBasedModel(nfls, y0=0, mb_model=mbmod, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) - + ev_model = FluxBasedModel( + nfls, + y0=0, + mb_model=mbmod, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) + if debug: graphics.plot_modeloutput_section(ev_model) plt.show() - try: + try: diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual[-1] = diag.volume_m3[-1] - ev_model.mb_model.glac_wide_area_annual[-1] = diag.area_m2[-1] - + ev_model.mb_model.glac_wide_volume_annual[-1] = ( + diag.volume_m3[-1] + ) + ev_model.mb_model.glac_wide_area_annual[-1] = ( + diag.area_m2[-1] + ) + # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Glacier-wide frontal ablation (m3 w.e.) # - note: diag.calving_m3 is cumulative calving if debug: - print('\n\ndiag.calving_m3:', diag.calving_m3.values) - print('calving_m3_since_y0:', ev_model.calving_m3_since_y0) - calving_m3_annual = ((diag.calving_m3.values[1:] - diag.calving_m3.values[0:-1]) * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + print( + "\n\ndiag.calving_m3:", + diag.calving_m3.values, + ) + print( + "calving_m3_since_y0:", + ev_model.calving_m3_since_y0, + ) + calving_m3_annual = ( + ( + diag.calving_m3.values[1:] + - diag.calving_m3.values[0:-1] + ) + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) for n in np.arange(calving_m3_annual.shape[0]): - ev_model.mb_model.glac_wide_frontalablation[12*n+11] = calving_m3_annual[n] + ev_model.mb_model.glac_wide_frontalablation[ + 12 * n + 11 + ] = calving_m3_annual[n] # Glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) - + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) + if debug: - print('avg calving_m3:', calving_m3_annual.sum() / nyears) - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) - + print( + "avg calving_m3:", + calving_m3_annual.sum() / nyears, + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"][ + "density_ice" + ] + / 1e12 + / nyears, + 4, + ), + ) + except RuntimeError as e: - if 'Glacier exceeds domain boundaries' in repr(e): + if "Glacier exceeds domain boundaries" in repr(e): count_exceed_boundary_errors += 1 successful_run = False - + # LOG FAILURE - fail_domain_fp = (pygem_prms['root'] + '/Output/simulations/fail-exceed_domain/' + reg_str + '/' - + gcm_name + '/') - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_domain_fp += scenario + '/' + fail_domain_fp = ( + pygem_prms["root"] + + "/Output/simulations/fail-exceed_domain/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in [ + "ERA-Interim", + "ERA5", + "COAWST", + ]: + fail_domain_fp += scenario + "/" if not os.path.exists(fail_domain_fp): os.makedirs(fail_domain_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" - with open(fail_domain_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' failed to complete ' + - str(count_exceed_boundary_errors) + ' simulations') + with open( + fail_domain_fp + txt_fn_fail, "w" + ) as text_file: + text_file.write( + glacier_str + + " failed to complete " + + str(count_exceed_boundary_errors) + + " simulations" + ) elif gdir.is_tidewater: if debug: - print('OGGM dynamics failed, using mass redistribution curves') + print( + "OGGM dynamics failed, using mass redistribution curves" + ) # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level, - spinupyears=pygem_prms['climate']['ref_spinupyears'] - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] + * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + spinupyears=pygem_prms["climate"][ + "ref_spinupyears" + ], + ) _, diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values - + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) + # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum( + 0 + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"][ + "density_ice" + ] + / 1e12 + / nyears, + 4, + ), + ) except: if gdir.is_tidewater: if debug: - print('OGGM dynamics failed, using mass redistribution curves') - # Mass redistribution curves glacier dynamics model + print( + "OGGM dynamics failed, using mass redistribution curves" + ) + # Mass redistribution curves glacier dynamics model ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] + * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + water_level=water_level, + ) _, diag = ev_model.run_until_and_store(nyears) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values - + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) + # Record frontal ablation for tidewater glaciers and update total mass balance # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum( + 0 + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) - + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"][ + "density_ice" + ] + / 1e12 + / nyears, + 4, + ), + ) + else: raise - # Mass redistribution model - elif args.option_dynamics == 'MassRedistributionCurves': + # Mass redistribution model + elif args.option_dynamics == "MassRedistributionCurves": if debug: - print('MASS REDISTRIBUTION CURVES!') + print("MASS REDISTRIBUTION CURVES!") ev_model = MassRedistributionCurveModel( - nfls, mb_model=mbmod, y0=0, - glen_a=cfg.PARAMS['glen_a']*glen_a_multiplier, fs=fs, - is_tidewater=gdir.is_tidewater, -# water_level=gdir.get_diagnostics().get('calving_water_level', None) - water_level=water_level - ) + nfls, + mb_model=mbmod, + y0=0, + glen_a=cfg.PARAMS["glen_a"] * glen_a_multiplier, + fs=fs, + is_tidewater=gdir.is_tidewater, + # water_level=gdir.get_diagnostics().get('calving_water_level', None) + water_level=water_level, + ) if debug: - print('New glacier vol', ev_model.volume_m3) + print("New glacier vol", ev_model.volume_m3) graphics.plot_modeloutput_section(ev_model) plt.show() try: _, diag = ev_model.run_until_and_store(nyears) -# print('shape of volume:', ev_model.mb_model.glac_wide_volume_annual.shape, diag.volume_m3.shape) - ev_model.mb_model.glac_wide_volume_annual = diag.volume_m3.values - ev_model.mb_model.glac_wide_area_annual = diag.area_m2.values + # print('shape of volume:', ev_model.mb_model.glac_wide_volume_annual.shape, diag.volume_m3.shape) + ev_model.mb_model.glac_wide_volume_annual = ( + diag.volume_m3.values + ) + ev_model.mb_model.glac_wide_area_annual = ( + diag.area_m2.values + ) # Record frontal ablation for tidewater glaciers and update total mass balance if gdir.is_tidewater: # Update glacier-wide frontal ablation (m3 w.e.) - ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum(0) + ev_model.mb_model.glac_wide_frontalablation = ev_model.mb_model.glac_bin_frontalablation.sum( + 0 + ) # Update glacier-wide total mass balance (m3 w.e.) ev_model.mb_model.glac_wide_massbaltotal = ( - ev_model.mb_model.glac_wide_massbaltotal - ev_model.mb_model.glac_wide_frontalablation) + ev_model.mb_model.glac_wide_massbaltotal + - ev_model.mb_model.glac_wide_frontalablation + ) if debug: - print('avg frontal ablation [Gta]:', - np.round(ev_model.mb_model.glac_wide_frontalablation.sum() / 1e9 / nyears,4)) - print('avg frontal ablation [Gta]:', - np.round(ev_model.calving_m3_since_y0 * pygem_prms['constants']['density_ice'] / 1e12 / nyears,4)) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.mb_model.glac_wide_frontalablation.sum() + / 1e9 + / nyears, + 4, + ), + ) + print( + "avg frontal ablation [Gta]:", + np.round( + ev_model.calving_m3_since_y0 + * pygem_prms["constants"][ + "density_ice" + ] + / 1e12 + / nyears, + 4, + ), + ) except RuntimeError as e: - if 'Glacier exceeds domain boundaries' in repr(e): + if "Glacier exceeds domain boundaries" in repr(e): count_exceed_boundary_errors += 1 successful_run = False - + # LOG FAILURE - fail_domain_fp = (pygem_prms['root'] + '/Output/simulations/fail-exceed_domain/' + reg_str + '/' - + gcm_name + '/') - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_domain_fp += scenario + '/' + fail_domain_fp = ( + pygem_prms["root"] + + "/Output/simulations/fail-exceed_domain/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in [ + "ERA-Interim", + "ERA5", + "COAWST", + ]: + fail_domain_fp += scenario + "/" if not os.path.exists(fail_domain_fp): os.makedirs(fail_domain_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" - with open(fail_domain_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + ' failed to complete ' + - str(count_exceed_boundary_errors) + ' simulations') + with open( + fail_domain_fp + txt_fn_fail, "w" + ) as text_file: + text_file.write( + glacier_str + + " failed to complete " + + str(count_exceed_boundary_errors) + + " simulations" + ) else: raise - - - - + elif args.option_dynamics is None: # Mass balance model ev_model = None diag = xr.Dataset() - mbmod = PyGEMMassBalance(gdir, modelprms, glacier_rgi_table, - fls=fls, option_areaconstant=True) + mbmod = PyGEMMassBalance( + gdir, + modelprms, + glacier_rgi_table, + fls=fls, + option_areaconstant=True, + ) # ----- MODEL RUN WITH CONSTANT GLACIER AREA ----- - years = np.arange(args.gcm_startyear, args.gcm_endyear + 1) + years = np.arange( + args.gcm_startyear, args.gcm_endyear + 1 + ) mb_all = [] for year in years - years[0]: - mb_annual = mbmod.get_annual_mb(nfls[0].surface_h, fls=nfls, fl_id=0, year=year, - debug=True) - mb_mwea = (mb_annual * 365 * 24 * 3600 * pygem_prms['constants']['density_ice'] / - pygem_prms['constants']['density_water']) - glac_wide_mb_mwea = ((mb_mwea * mbmod.glacier_area_initial).sum() / - mbmod.glacier_area_initial.sum()) + mb_annual = mbmod.get_annual_mb( + nfls[0].surface_h, + fls=nfls, + fl_id=0, + year=year, + debug=True, + ) + mb_mwea = ( + mb_annual + * 365 + * 24 + * 3600 + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) + glac_wide_mb_mwea = ( + mb_mwea * mbmod.glacier_area_initial + ).sum() / mbmod.glacier_area_initial.sum() mb_all.append(glac_wide_mb_mwea) - mbmod.glac_wide_area_annual[-1] = mbmod.glac_wide_area_annual[0] - mbmod.glac_wide_volume_annual[-1] = mbmod.glac_wide_volume_annual[0] - diag['area_m2'] = mbmod.glac_wide_area_annual - diag['volume_m3'] = mbmod.glac_wide_volume_annual - diag['volume_bsl_m3'] = 0 - + mbmod.glac_wide_area_annual[-1] = ( + mbmod.glac_wide_area_annual[0] + ) + mbmod.glac_wide_volume_annual[-1] = ( + mbmod.glac_wide_volume_annual[0] + ) + diag["area_m2"] = mbmod.glac_wide_area_annual + diag["volume_m3"] = mbmod.glac_wide_volume_annual + diag["volume_bsl_m3"] = 0 + if debug: - print('iter:', n_iter, 'massbal (mean, std):', np.round(np.mean(mb_all),3), np.round(np.std(mb_all),3), - 'massbal (med):', np.round(np.median(mb_all),3)) - -# mb_em_mwea = run_emulator_mb(modelprms) -# print(' emulator mb:', np.round(mb_em_mwea,3)) -# mb_em_sims.append(mb_em_mwea) - - + print( + "iter:", + n_iter, + "massbal (mean, std):", + np.round(np.mean(mb_all), 3), + np.round(np.std(mb_all), 3), + "massbal (med):", + np.round(np.median(mb_all), 3), + ) + + # mb_em_mwea = run_emulator_mb(modelprms) + # print(' emulator mb:', np.round(mb_em_mwea,3)) + # mb_em_sims.append(mb_em_mwea) + # Record output for successful runs if successful_run: - if args.option_dynamics is not None: if debug: graphics.plot_modeloutput_section(ev_model) - # graphics.plot_modeloutput_map(gdir, model=ev_model) + # graphics.plot_modeloutput_map(gdir, model=ev_model) plt.figure() diag.volume_m3.plot() plt.show() - + # Post-process data to ensure mass is conserved and update accordingly for ignored mass losses # ignored mass losses occur because mass balance model does not know ice thickness and flux divergence - area_initial = mbmod.glac_bin_area_annual[:,0].sum() - mb_mwea_diag = ((diag.volume_m3.values[-1] - diag.volume_m3.values[0]) - / area_initial / nyears * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) - mb_mwea_mbmod = mbmod.glac_wide_massbaltotal.sum() / area_initial / nyears - + area_initial = mbmod.glac_bin_area_annual[ + :, 0 + ].sum() + mb_mwea_diag = ( + ( + diag.volume_m3.values[-1] + - diag.volume_m3.values[0] + ) + / area_initial + / nyears + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) + mb_mwea_mbmod = ( + mbmod.glac_wide_massbaltotal.sum() + / area_initial + / nyears + ) + if debug: - vol_change_diag = diag.volume_m3.values[-1] - diag.volume_m3.values[0] - print(' vol init [Gt]:', np.round(diag.volume_m3.values[0] * 0.9 / 1e9,5)) - print(' vol final [Gt]:', np.round(diag.volume_m3.values[-1] * 0.9 / 1e9,5)) - print(' vol change[Gt]:', np.round(vol_change_diag * 0.9 / 1e9,5)) - print(' mb [mwea]:', np.round(mb_mwea_diag,2)) - print(' mb_mbmod [mwea]:', np.round(mb_mwea_mbmod,2)) - - + vol_change_diag = ( + diag.volume_m3.values[-1] + - diag.volume_m3.values[0] + ) + print( + " vol init [Gt]:", + np.round( + diag.volume_m3.values[0] * 0.9 / 1e9, 5 + ), + ) + print( + " vol final [Gt]:", + np.round( + diag.volume_m3.values[-1] * 0.9 / 1e9, + 5, + ), + ) + print( + " vol change[Gt]:", + np.round(vol_change_diag * 0.9 / 1e9, 5), + ) + print( + " mb [mwea]:", np.round(mb_mwea_diag, 2) + ) + print( + " mb_mbmod [mwea]:", + np.round(mb_mwea_mbmod, 2), + ) + if np.abs(mb_mwea_diag - mb_mwea_mbmod) > 1e-6: - ev_model.mb_model.ensure_mass_conservation(diag) - + ev_model.mb_model.ensure_mass_conservation( + diag + ) + if debug: - print('mass loss [Gt]:', mbmod.glac_wide_massbaltotal.sum() / 1e9) - + print( + "mass loss [Gt]:", + mbmod.glac_wide_massbaltotal.sum() / 1e9, + ) + # RECORD PARAMETERS TO DATASET - output_glac_temp_monthly[:, n_iter] = mbmod.glac_wide_temp - output_glac_prec_monthly[:, n_iter] = mbmod.glac_wide_prec - output_glac_acc_monthly[:, n_iter] = mbmod.glac_wide_acc - output_glac_refreeze_monthly[:, n_iter] = mbmod.glac_wide_refreeze - output_glac_melt_monthly[:, n_iter] = mbmod.glac_wide_melt - output_glac_frontalablation_monthly[:, n_iter] = mbmod.glac_wide_frontalablation - output_glac_massbaltotal_monthly[:, n_iter] = mbmod.glac_wide_massbaltotal - output_glac_runoff_monthly[:, n_iter] = mbmod.glac_wide_runoff - output_glac_snowline_monthly[:, n_iter] = mbmod.glac_wide_snowline - output_glac_area_annual[:, n_iter] = diag.area_m2.values - output_glac_mass_annual[:, n_iter] = diag.volume_m3.values * pygem_prms['constants']['density_ice'] - output_glac_mass_bsl_annual[:, n_iter] = diag.volume_bsl_m3.values * pygem_prms['constants']['density_ice'] - output_glac_mass_change_ignored_annual[:-1, n_iter] = mbmod.glac_wide_volume_change_ignored_annual * pygem_prms['constants']['density_ice'] - output_glac_ELA_annual[:, n_iter] = mbmod.glac_wide_ELA_annual - output_offglac_prec_monthly[:, n_iter] = mbmod.offglac_wide_prec - - output_offglac_refreeze_monthly[:, n_iter] = mbmod.offglac_wide_refreeze - output_offglac_melt_monthly[:, n_iter] = mbmod.offglac_wide_melt - output_offglac_snowpack_monthly[:, n_iter] = mbmod.offglac_wide_snowpack - output_offglac_runoff_monthly[:, n_iter] = mbmod.offglac_wide_runoff + output_glac_temp_monthly[:, n_iter] = ( + mbmod.glac_wide_temp + ) + output_glac_prec_monthly[:, n_iter] = ( + mbmod.glac_wide_prec + ) + output_glac_acc_monthly[:, n_iter] = ( + mbmod.glac_wide_acc + ) + output_glac_refreeze_monthly[:, n_iter] = ( + mbmod.glac_wide_refreeze + ) + output_glac_melt_monthly[:, n_iter] = ( + mbmod.glac_wide_melt + ) + output_glac_frontalablation_monthly[:, n_iter] = ( + mbmod.glac_wide_frontalablation + ) + output_glac_massbaltotal_monthly[:, n_iter] = ( + mbmod.glac_wide_massbaltotal + ) + output_glac_runoff_monthly[:, n_iter] = ( + mbmod.glac_wide_runoff + ) + output_glac_snowline_monthly[:, n_iter] = ( + mbmod.glac_wide_snowline + ) + output_glac_area_annual[:, n_iter] = ( + diag.area_m2.values + ) + output_glac_mass_annual[:, n_iter] = ( + diag.volume_m3.values + * pygem_prms["constants"]["density_ice"] + ) + output_glac_mass_bsl_annual[:, n_iter] = ( + diag.volume_bsl_m3.values + * pygem_prms["constants"]["density_ice"] + ) + output_glac_mass_change_ignored_annual[:-1, n_iter] = ( + mbmod.glac_wide_volume_change_ignored_annual + * pygem_prms["constants"]["density_ice"] + ) + output_glac_ELA_annual[:, n_iter] = ( + mbmod.glac_wide_ELA_annual + ) + output_offglac_prec_monthly[:, n_iter] = ( + mbmod.offglac_wide_prec + ) + + output_offglac_refreeze_monthly[:, n_iter] = ( + mbmod.offglac_wide_refreeze + ) + output_offglac_melt_monthly[:, n_iter] = ( + mbmod.offglac_wide_melt + ) + output_offglac_snowpack_monthly[:, n_iter] = ( + mbmod.offglac_wide_snowpack + ) + output_offglac_runoff_monthly[:, n_iter] = ( + mbmod.offglac_wide_runoff + ) if output_glac_bin_icethickness_annual is None: - output_glac_bin_area_annual_sim = mbmod.glac_bin_area_annual[:,:,np.newaxis] - output_glac_bin_mass_annual_sim = (mbmod.glac_bin_area_annual * - mbmod.glac_bin_icethickness_annual * - pygem_prms['constants']['density_ice'])[:,:,np.newaxis] - output_glac_bin_icethickness_annual_sim = (mbmod.glac_bin_icethickness_annual)[:,:,np.newaxis] + output_glac_bin_area_annual_sim = ( + mbmod.glac_bin_area_annual[:, :, np.newaxis] + ) + output_glac_bin_mass_annual_sim = ( + mbmod.glac_bin_area_annual + * mbmod.glac_bin_icethickness_annual + * pygem_prms["constants"]["density_ice"] + )[:, :, np.newaxis] + output_glac_bin_icethickness_annual_sim = ( + mbmod.glac_bin_icethickness_annual + )[:, :, np.newaxis] # Update the latest thickness and volume if ev_model is not None: - fl_dx_meter = getattr(ev_model.fls[0], 'dx_meter', None) - fl_widths_m = getattr(ev_model.fls[0], 'widths_m', None) - fl_section = getattr(ev_model.fls[0],'section',None) + fl_dx_meter = getattr( + ev_model.fls[0], "dx_meter", None + ) + fl_widths_m = getattr( + ev_model.fls[0], "widths_m", None + ) + fl_section = getattr( + ev_model.fls[0], "section", None + ) else: - fl_dx_meter = getattr(nfls[0], 'dx_meter', None) - fl_widths_m = getattr(nfls[0], 'widths_m', None) - fl_section = getattr(nfls[0],'section',None) - if fl_section is not None and fl_widths_m is not None: + fl_dx_meter = getattr( + nfls[0], "dx_meter", None + ) + fl_widths_m = getattr( + nfls[0], "widths_m", None + ) + fl_section = getattr(nfls[0], "section", None) + if ( + fl_section is not None + and fl_widths_m is not None + ): # thickness icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] - output_glac_bin_icethickness_annual_sim[:,-1,0] = icethickness_t0 + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] + / fl_widths_m[fl_widths_m > 0] + ) + output_glac_bin_icethickness_annual_sim[ + :, -1, 0 + ] = icethickness_t0 # mass - glacier_vol_t0 = fl_widths_m * fl_dx_meter * icethickness_t0 - output_glac_bin_mass_annual_sim[:,-1,0] = glacier_vol_t0 * pygem_prms['constants']['density_ice'] - output_glac_bin_area_annual = output_glac_bin_area_annual_sim - output_glac_bin_mass_annual = output_glac_bin_mass_annual_sim - output_glac_bin_icethickness_annual = output_glac_bin_icethickness_annual_sim - output_glac_bin_massbalclim_annual_sim = np.zeros(mbmod.glac_bin_icethickness_annual.shape) - output_glac_bin_massbalclim_annual_sim[:,:-1] = mbmod.glac_bin_massbalclim_annual - output_glac_bin_massbalclim_annual = output_glac_bin_massbalclim_annual_sim[:,:,np.newaxis] - output_glac_bin_massbalclim_monthly_sim = np.zeros(mbmod.glac_bin_massbalclim.shape) - output_glac_bin_massbalclim_monthly_sim = mbmod.glac_bin_massbalclim - output_glac_bin_massbalclim_monthly = output_glac_bin_massbalclim_monthly_sim[:,:,np.newaxis] + glacier_vol_t0 = ( + fl_widths_m * fl_dx_meter * icethickness_t0 + ) + output_glac_bin_mass_annual_sim[:, -1, 0] = ( + glacier_vol_t0 + * pygem_prms["constants"]["density_ice"] + ) + output_glac_bin_area_annual = ( + output_glac_bin_area_annual_sim + ) + output_glac_bin_mass_annual = ( + output_glac_bin_mass_annual_sim + ) + output_glac_bin_icethickness_annual = ( + output_glac_bin_icethickness_annual_sim + ) + output_glac_bin_massbalclim_annual_sim = np.zeros( + mbmod.glac_bin_icethickness_annual.shape + ) + output_glac_bin_massbalclim_annual_sim[:, :-1] = ( + mbmod.glac_bin_massbalclim_annual + ) + output_glac_bin_massbalclim_annual = ( + output_glac_bin_massbalclim_annual_sim[ + :, :, np.newaxis + ] + ) + output_glac_bin_massbalclim_monthly_sim = np.zeros( + mbmod.glac_bin_massbalclim.shape + ) + output_glac_bin_massbalclim_monthly_sim = ( + mbmod.glac_bin_massbalclim + ) + output_glac_bin_massbalclim_monthly = ( + output_glac_bin_massbalclim_monthly_sim[ + :, :, np.newaxis + ] + ) # accum - output_glac_bin_acc_monthly_sim = np.zeros(mbmod.bin_acc.shape) - output_glac_bin_acc_monthly_sim = mbmod.bin_acc - output_glac_bin_acc_monthly = output_glac_bin_acc_monthly_sim[:,:,np.newaxis] + output_glac_bin_acc_monthly_sim = np.zeros( + mbmod.bin_acc.shape + ) + output_glac_bin_acc_monthly_sim = mbmod.bin_acc + output_glac_bin_acc_monthly = ( + output_glac_bin_acc_monthly_sim[ + :, :, np.newaxis + ] + ) # refreeze - output_glac_bin_refreeze_monthly_sim = np.zeros(mbmod.glac_bin_refreeze.shape) - output_glac_bin_refreeze_monthly_sim = mbmod.glac_bin_refreeze - output_glac_bin_refreeze_monthly = output_glac_bin_refreeze_monthly_sim[:,:,np.newaxis] + output_glac_bin_refreeze_monthly_sim = np.zeros( + mbmod.glac_bin_refreeze.shape + ) + output_glac_bin_refreeze_monthly_sim = ( + mbmod.glac_bin_refreeze + ) + output_glac_bin_refreeze_monthly = ( + output_glac_bin_refreeze_monthly_sim[ + :, :, np.newaxis + ] + ) # melt - output_glac_bin_melt_monthly_sim = np.zeros(mbmod.glac_bin_melt.shape) - output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt - output_glac_bin_melt_monthly = output_glac_bin_melt_monthly_sim[:,:,np.newaxis] + output_glac_bin_melt_monthly_sim = np.zeros( + mbmod.glac_bin_melt.shape + ) + output_glac_bin_melt_monthly_sim = ( + mbmod.glac_bin_melt + ) + output_glac_bin_melt_monthly = ( + output_glac_bin_melt_monthly_sim[ + :, :, np.newaxis + ] + ) else: # Update the latest thickness and volume - output_glac_bin_area_annual_sim = mbmod.glac_bin_area_annual[:,:,np.newaxis] - output_glac_bin_mass_annual_sim = (mbmod.glac_bin_area_annual * - mbmod.glac_bin_icethickness_annual * - pygem_prms['constants']['density_ice'])[:,:,np.newaxis] - output_glac_bin_icethickness_annual_sim = (mbmod.glac_bin_icethickness_annual)[:,:,np.newaxis] + output_glac_bin_area_annual_sim = ( + mbmod.glac_bin_area_annual[:, :, np.newaxis] + ) + output_glac_bin_mass_annual_sim = ( + mbmod.glac_bin_area_annual + * mbmod.glac_bin_icethickness_annual + * pygem_prms["constants"]["density_ice"] + )[:, :, np.newaxis] + output_glac_bin_icethickness_annual_sim = ( + mbmod.glac_bin_icethickness_annual + )[:, :, np.newaxis] if ev_model is not None: - fl_dx_meter = getattr(ev_model.fls[0], 'dx_meter', None) - fl_widths_m = getattr(ev_model.fls[0], 'widths_m', None) - fl_section = getattr(ev_model.fls[0],'section',None) + fl_dx_meter = getattr( + ev_model.fls[0], "dx_meter", None + ) + fl_widths_m = getattr( + ev_model.fls[0], "widths_m", None + ) + fl_section = getattr( + ev_model.fls[0], "section", None + ) else: - fl_dx_meter = getattr(nfls[0], 'dx_meter', None) - fl_widths_m = getattr(nfls[0], 'widths_m', None) - fl_section = getattr(nfls[0],'section',None) - if fl_section is not None and fl_widths_m is not None: + fl_dx_meter = getattr( + nfls[0], "dx_meter", None + ) + fl_widths_m = getattr( + nfls[0], "widths_m", None + ) + fl_section = getattr(nfls[0], "section", None) + if ( + fl_section is not None + and fl_widths_m is not None + ): # thickness icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] - output_glac_bin_icethickness_annual_sim[:,-1,0] = icethickness_t0 + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] + / fl_widths_m[fl_widths_m > 0] + ) + output_glac_bin_icethickness_annual_sim[ + :, -1, 0 + ] = icethickness_t0 # mass - glacier_vol_t0 = fl_widths_m * fl_dx_meter * icethickness_t0 - output_glac_bin_mass_annual_sim[:,-1,0] = glacier_vol_t0 * pygem_prms['constants']['density_ice'] - output_glac_bin_area_annual = np.append(output_glac_bin_area_annual, - output_glac_bin_area_annual_sim, axis=2) - output_glac_bin_mass_annual = np.append(output_glac_bin_mass_annual, - output_glac_bin_mass_annual_sim, axis=2) - output_glac_bin_icethickness_annual = np.append(output_glac_bin_icethickness_annual, - output_glac_bin_icethickness_annual_sim, - axis=2) - output_glac_bin_massbalclim_annual_sim = np.zeros(mbmod.glac_bin_icethickness_annual.shape) - output_glac_bin_massbalclim_annual_sim[:,:-1] = mbmod.glac_bin_massbalclim_annual - output_glac_bin_massbalclim_annual = np.append(output_glac_bin_massbalclim_annual, - output_glac_bin_massbalclim_annual_sim[:,:,np.newaxis], - axis=2) - output_glac_bin_massbalclim_monthly_sim = np.zeros(mbmod.glac_bin_massbalclim.shape) - output_glac_bin_massbalclim_monthly_sim = mbmod.glac_bin_massbalclim - output_glac_bin_massbalclim_monthly = np.append(output_glac_bin_massbalclim_monthly, - output_glac_bin_massbalclim_monthly_sim[:,:,np.newaxis], - axis=2) + glacier_vol_t0 = ( + fl_widths_m * fl_dx_meter * icethickness_t0 + ) + output_glac_bin_mass_annual_sim[:, -1, 0] = ( + glacier_vol_t0 + * pygem_prms["constants"]["density_ice"] + ) + output_glac_bin_area_annual = np.append( + output_glac_bin_area_annual, + output_glac_bin_area_annual_sim, + axis=2, + ) + output_glac_bin_mass_annual = np.append( + output_glac_bin_mass_annual, + output_glac_bin_mass_annual_sim, + axis=2, + ) + output_glac_bin_icethickness_annual = np.append( + output_glac_bin_icethickness_annual, + output_glac_bin_icethickness_annual_sim, + axis=2, + ) + output_glac_bin_massbalclim_annual_sim = np.zeros( + mbmod.glac_bin_icethickness_annual.shape + ) + output_glac_bin_massbalclim_annual_sim[:, :-1] = ( + mbmod.glac_bin_massbalclim_annual + ) + output_glac_bin_massbalclim_annual = np.append( + output_glac_bin_massbalclim_annual, + output_glac_bin_massbalclim_annual_sim[ + :, :, np.newaxis + ], + axis=2, + ) + output_glac_bin_massbalclim_monthly_sim = np.zeros( + mbmod.glac_bin_massbalclim.shape + ) + output_glac_bin_massbalclim_monthly_sim = ( + mbmod.glac_bin_massbalclim + ) + output_glac_bin_massbalclim_monthly = np.append( + output_glac_bin_massbalclim_monthly, + output_glac_bin_massbalclim_monthly_sim[ + :, :, np.newaxis + ], + axis=2, + ) # accum - output_glac_bin_acc_monthly_sim = np.zeros(mbmod.bin_acc.shape) - output_glac_bin_acc_monthly_sim = mbmod.bin_acc - output_glac_bin_acc_monthly = np.append(output_glac_bin_acc_monthly, - output_glac_bin_acc_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_acc_monthly_sim = np.zeros( + mbmod.bin_acc.shape + ) + output_glac_bin_acc_monthly_sim = mbmod.bin_acc + output_glac_bin_acc_monthly = np.append( + output_glac_bin_acc_monthly, + output_glac_bin_acc_monthly_sim[ + :, :, np.newaxis + ], + axis=2, + ) # melt - output_glac_bin_melt_monthly_sim = np.zeros(mbmod.glac_bin_melt.shape) - output_glac_bin_melt_monthly_sim = mbmod.glac_bin_melt - output_glac_bin_melt_monthly = np.append(output_glac_bin_melt_monthly, - output_glac_bin_melt_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_melt_monthly_sim = np.zeros( + mbmod.glac_bin_melt.shape + ) + output_glac_bin_melt_monthly_sim = ( + mbmod.glac_bin_melt + ) + output_glac_bin_melt_monthly = np.append( + output_glac_bin_melt_monthly, + output_glac_bin_melt_monthly_sim[ + :, :, np.newaxis + ], + axis=2, + ) # refreeze - output_glac_bin_refreeze_monthly_sim = np.zeros(mbmod.glac_bin_refreeze.shape) - output_glac_bin_refreeze_monthly_sim = mbmod.glac_bin_refreeze - output_glac_bin_refreeze_monthly = np.append(output_glac_bin_refreeze_monthly, - output_glac_bin_refreeze_monthly_sim[:,:,np.newaxis], - axis=2) + output_glac_bin_refreeze_monthly_sim = np.zeros( + mbmod.glac_bin_refreeze.shape + ) + output_glac_bin_refreeze_monthly_sim = ( + mbmod.glac_bin_refreeze + ) + output_glac_bin_refreeze_monthly = np.append( + output_glac_bin_refreeze_monthly, + output_glac_bin_refreeze_monthly_sim[ + :, :, np.newaxis + ], + axis=2, + ) # ===== Export Results ===== if count_exceed_boundary_errors < nsims: @@ -1054,257 +1935,608 @@ def run(list_packed_vars): # Output statistics if args.export_all_simiters and nsims > 1: # Instantiate dataset - output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, - dates_table=dates_table, - nsims=1, - pygem_version=pygem.__version__, - gcm_name = gcm_name, - scenario = scenario, - realization=realization, - modelprms = modelprms, - ref_startyear = args.ref_startyear, - ref_endyear = ref_endyear, - gcm_startyear = args.gcm_startyear, - gcm_endyear = args.gcm_endyear, - option_calibration = args.option_calibration, - option_bias_adjustment = args.option_bias_adjustment) + output_stats = output.glacierwide_stats( + glacier_rgi_table=glacier_rgi_table, + dates_table=dates_table, + nsims=1, + pygem_version=pygem.__version__, + gcm_name=gcm_name, + scenario=scenario, + realization=realization, + modelprms=modelprms, + ref_startyear=args.ref_startyear, + ref_endyear=ref_endyear, + gcm_startyear=args.gcm_startyear, + gcm_endyear=args.gcm_endyear, + option_calibration=args.option_calibration, + option_bias_adjustment=args.option_bias_adjustment, + ) for n_iter in range(nsims): # pass model params for iteration and update output dataset model params - output_stats.set_modelprms({key: modelprms_all[key][n_iter] for key in modelprms_all}) + output_stats.set_modelprms( + { + key: modelprms_all[key][n_iter] + for key in modelprms_all + } + ) # create and return xarray dataset output_stats.create_xr_ds() output_ds_all_stats = output_stats.get_xr_ds() # fill values - output_ds_all_stats['glac_runoff_monthly'].values[0,:] = output_glac_runoff_monthly[:,n_iter] - output_ds_all_stats['glac_area_annual'].values[0,:] = output_glac_area_annual[:,n_iter] - output_ds_all_stats['glac_mass_annual'].values[0,:] = output_glac_mass_annual[:,n_iter] - output_ds_all_stats['glac_mass_bsl_annual'].values[0,:] = output_glac_mass_bsl_annual[:,n_iter] - output_ds_all_stats['glac_ELA_annual'].values[0,:] = output_glac_ELA_annual[:,n_iter] - output_ds_all_stats['offglac_runoff_monthly'].values[0,:] = output_offglac_runoff_monthly[:,n_iter] + output_ds_all_stats["glac_runoff_monthly"].values[ + 0, : + ] = output_glac_runoff_monthly[:, n_iter] + output_ds_all_stats["glac_area_annual"].values[ + 0, : + ] = output_glac_area_annual[:, n_iter] + output_ds_all_stats["glac_mass_annual"].values[ + 0, : + ] = output_glac_mass_annual[:, n_iter] + output_ds_all_stats["glac_mass_bsl_annual"].values[ + 0, : + ] = output_glac_mass_bsl_annual[:, n_iter] + output_ds_all_stats["glac_ELA_annual"].values[ + 0, : + ] = output_glac_ELA_annual[:, n_iter] + output_ds_all_stats[ + "offglac_runoff_monthly" + ].values[0, :] = output_offglac_runoff_monthly[ + :, n_iter + ] if args.export_extra_vars: - output_ds_all_stats['glac_temp_monthly'].values[0,:] = output_glac_temp_monthly[:,n_iter] + 273.15 - output_ds_all_stats['glac_prec_monthly'].values[0,:] = output_glac_prec_monthly[:,n_iter] - output_ds_all_stats['glac_acc_monthly'].values[0,:] = output_glac_acc_monthly[:,n_iter] - output_ds_all_stats['glac_refreeze_monthly'].values[0,:] = output_glac_refreeze_monthly[:,n_iter] - output_ds_all_stats['glac_melt_monthly'].values[0,:] = output_glac_melt_monthly[:,n_iter] - output_ds_all_stats['glac_frontalablation_monthly'].values[0,:] = ( - output_glac_frontalablation_monthly[:,n_iter]) - output_ds_all_stats['glac_massbaltotal_monthly'].values[0,:] = ( - output_glac_massbaltotal_monthly[:,n_iter]) - output_ds_all_stats['glac_snowline_monthly'].values[0,:] = output_glac_snowline_monthly[:,n_iter] - output_ds_all_stats['glac_mass_change_ignored_annual'].values[0,:] = ( - output_glac_mass_change_ignored_annual[:,n_iter]) - output_ds_all_stats['offglac_prec_monthly'].values[0,:] = output_offglac_prec_monthly[:,n_iter] - output_ds_all_stats['offglac_melt_monthly'].values[0,:] = output_offglac_melt_monthly[:,n_iter] - output_ds_all_stats['offglac_refreeze_monthly'].values[0,:] = output_offglac_refreeze_monthly[:,n_iter] - output_ds_all_stats['offglac_snowpack_monthly'].values[0,:] = output_offglac_snowpack_monthly[:,n_iter] + output_ds_all_stats[ + "glac_temp_monthly" + ].values[0, :] = ( + output_glac_temp_monthly[:, n_iter] + + 273.15 + ) + output_ds_all_stats[ + "glac_prec_monthly" + ].values[0, :] = output_glac_prec_monthly[ + :, n_iter + ] + output_ds_all_stats["glac_acc_monthly"].values[ + 0, : + ] = output_glac_acc_monthly[:, n_iter] + output_ds_all_stats[ + "glac_refreeze_monthly" + ].values[0, :] = output_glac_refreeze_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "glac_melt_monthly" + ].values[0, :] = output_glac_melt_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "glac_frontalablation_monthly" + ].values[ + 0, : + ] = output_glac_frontalablation_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "glac_massbaltotal_monthly" + ].values[ + 0, : + ] = output_glac_massbaltotal_monthly[:, n_iter] + output_ds_all_stats[ + "glac_snowline_monthly" + ].values[0, :] = output_glac_snowline_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "glac_mass_change_ignored_annual" + ].values[ + 0, : + ] = output_glac_mass_change_ignored_annual[ + :, n_iter + ] + output_ds_all_stats[ + "offglac_prec_monthly" + ].values[0, :] = output_offglac_prec_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "offglac_melt_monthly" + ].values[0, :] = output_offglac_melt_monthly[ + :, n_iter + ] + output_ds_all_stats[ + "offglac_refreeze_monthly" + ].values[ + 0, : + ] = output_offglac_refreeze_monthly[:, n_iter] + output_ds_all_stats[ + "offglac_snowpack_monthly" + ].values[ + 0, : + ] = output_offglac_snowpack_monthly[:, n_iter] # export glacierwide stats for iteration - output_stats.save_xr_ds(output_stats.get_fn().replace('SETS',f'set{n_iter}') + 'all.nc') + output_stats.save_xr_ds( + output_stats.get_fn().replace( + "SETS", f"set{n_iter}" + ) + + "all.nc" + ) # instantiate dataset for merged simulations - output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, - dates_table=dates_table, - nsims=nsims, - pygem_version=pygem.__version__, - gcm_name = gcm_name, - scenario = scenario, - realization=realization, - modelprms = modelprms, - ref_startyear = args.ref_startyear, - ref_endyear = ref_endyear, - gcm_startyear = args.gcm_startyear, - gcm_endyear = args.gcm_endyear, - option_calibration = args.option_calibration, - option_bias_adjustment = args.option_bias_adjustment) + output_stats = output.glacierwide_stats( + glacier_rgi_table=glacier_rgi_table, + dates_table=dates_table, + nsims=nsims, + pygem_version=pygem.__version__, + gcm_name=gcm_name, + scenario=scenario, + realization=realization, + modelprms=modelprms, + ref_startyear=args.ref_startyear, + ref_endyear=ref_endyear, + gcm_startyear=args.gcm_startyear, + gcm_endyear=args.gcm_endyear, + option_calibration=args.option_calibration, + option_bias_adjustment=args.option_bias_adjustment, + ) # create and return xarray dataset output_stats.create_xr_ds() output_ds_all_stats = output_stats.get_xr_ds() # get stats from all simulations which will be stored - output_glac_runoff_monthly_stats = calc_stats_array(output_glac_runoff_monthly) - output_glac_area_annual_stats = calc_stats_array(output_glac_area_annual) - output_glac_mass_annual_stats = calc_stats_array(output_glac_mass_annual) - output_glac_mass_bsl_annual_stats = calc_stats_array(output_glac_mass_bsl_annual) - output_glac_ELA_annual_stats = calc_stats_array(output_glac_ELA_annual) - output_offglac_runoff_monthly_stats = calc_stats_array(output_offglac_runoff_monthly) + output_glac_runoff_monthly_stats = calc_stats_array( + output_glac_runoff_monthly + ) + output_glac_area_annual_stats = calc_stats_array( + output_glac_area_annual + ) + output_glac_mass_annual_stats = calc_stats_array( + output_glac_mass_annual + ) + output_glac_mass_bsl_annual_stats = calc_stats_array( + output_glac_mass_bsl_annual + ) + output_glac_ELA_annual_stats = calc_stats_array( + output_glac_ELA_annual + ) + output_offglac_runoff_monthly_stats = calc_stats_array( + output_offglac_runoff_monthly + ) if args.export_extra_vars: - output_glac_temp_monthly_stats = calc_stats_array(output_glac_temp_monthly) - output_glac_prec_monthly_stats = calc_stats_array(output_glac_prec_monthly) - output_glac_acc_monthly_stats = calc_stats_array(output_glac_acc_monthly) - output_glac_refreeze_monthly_stats = calc_stats_array(output_glac_refreeze_monthly) - output_glac_melt_monthly_stats = calc_stats_array(output_glac_melt_monthly) - output_glac_frontalablation_monthly_stats = calc_stats_array(output_glac_frontalablation_monthly) - output_glac_massbaltotal_monthly_stats = calc_stats_array(output_glac_massbaltotal_monthly) - output_glac_snowline_monthly_stats = calc_stats_array(output_glac_snowline_monthly) - output_glac_mass_change_ignored_annual_stats = calc_stats_array(output_glac_mass_change_ignored_annual) - output_offglac_prec_monthly_stats = calc_stats_array(output_offglac_prec_monthly) - output_offglac_melt_monthly_stats = calc_stats_array(output_offglac_melt_monthly) - output_offglac_refreeze_monthly_stats = calc_stats_array(output_offglac_refreeze_monthly) - output_offglac_snowpack_monthly_stats = calc_stats_array(output_offglac_snowpack_monthly) + output_glac_temp_monthly_stats = calc_stats_array( + output_glac_temp_monthly + ) + output_glac_prec_monthly_stats = calc_stats_array( + output_glac_prec_monthly + ) + output_glac_acc_monthly_stats = calc_stats_array( + output_glac_acc_monthly + ) + output_glac_refreeze_monthly_stats = calc_stats_array( + output_glac_refreeze_monthly + ) + output_glac_melt_monthly_stats = calc_stats_array( + output_glac_melt_monthly + ) + output_glac_frontalablation_monthly_stats = ( + calc_stats_array( + output_glac_frontalablation_monthly + ) + ) + output_glac_massbaltotal_monthly_stats = ( + calc_stats_array(output_glac_massbaltotal_monthly) + ) + output_glac_snowline_monthly_stats = calc_stats_array( + output_glac_snowline_monthly + ) + output_glac_mass_change_ignored_annual_stats = ( + calc_stats_array( + output_glac_mass_change_ignored_annual + ) + ) + output_offglac_prec_monthly_stats = calc_stats_array( + output_offglac_prec_monthly + ) + output_offglac_melt_monthly_stats = calc_stats_array( + output_offglac_melt_monthly + ) + output_offglac_refreeze_monthly_stats = ( + calc_stats_array(output_offglac_refreeze_monthly) + ) + output_offglac_snowpack_monthly_stats = ( + calc_stats_array(output_offglac_snowpack_monthly) + ) # output mean/median from all simulations - output_ds_all_stats['glac_runoff_monthly'].values[0,:] = output_glac_runoff_monthly_stats[:,0] - output_ds_all_stats['glac_area_annual'].values[0,:] = output_glac_area_annual_stats[:,0] - output_ds_all_stats['glac_mass_annual'].values[0,:] = output_glac_mass_annual_stats[:,0] - output_ds_all_stats['glac_mass_bsl_annual'].values[0,:] = output_glac_mass_bsl_annual_stats[:,0] - output_ds_all_stats['glac_ELA_annual'].values[0,:] = output_glac_ELA_annual_stats[:,0] - output_ds_all_stats['offglac_runoff_monthly'].values[0,:] = output_offglac_runoff_monthly_stats[:,0] + output_ds_all_stats["glac_runoff_monthly"].values[0, :] = ( + output_glac_runoff_monthly_stats[:, 0] + ) + output_ds_all_stats["glac_area_annual"].values[0, :] = ( + output_glac_area_annual_stats[:, 0] + ) + output_ds_all_stats["glac_mass_annual"].values[0, :] = ( + output_glac_mass_annual_stats[:, 0] + ) + output_ds_all_stats["glac_mass_bsl_annual"].values[ + 0, : + ] = output_glac_mass_bsl_annual_stats[:, 0] + output_ds_all_stats["glac_ELA_annual"].values[0, :] = ( + output_glac_ELA_annual_stats[:, 0] + ) + output_ds_all_stats["offglac_runoff_monthly"].values[ + 0, : + ] = output_offglac_runoff_monthly_stats[:, 0] if args.export_extra_vars: - output_ds_all_stats['glac_temp_monthly'].values[0,:] = output_glac_temp_monthly_stats[:,0] + 273.15 - output_ds_all_stats['glac_prec_monthly'].values[0,:] = output_glac_prec_monthly_stats[:,0] - output_ds_all_stats['glac_acc_monthly'].values[0,:] = output_glac_acc_monthly_stats[:,0] - output_ds_all_stats['glac_refreeze_monthly'].values[0,:] = output_glac_refreeze_monthly_stats[:,0] - output_ds_all_stats['glac_melt_monthly'].values[0,:] = output_glac_melt_monthly_stats[:,0] - output_ds_all_stats['glac_frontalablation_monthly'].values[0,:] = ( - output_glac_frontalablation_monthly_stats[:,0]) - output_ds_all_stats['glac_massbaltotal_monthly'].values[0,:] = ( - output_glac_massbaltotal_monthly_stats[:,0]) - output_ds_all_stats['glac_snowline_monthly'].values[0,:] = output_glac_snowline_monthly_stats[:,0] - output_ds_all_stats['glac_mass_change_ignored_annual'].values[0,:] = ( - output_glac_mass_change_ignored_annual_stats[:,0]) - output_ds_all_stats['offglac_prec_monthly'].values[0,:] = output_offglac_prec_monthly_stats[:,0] - output_ds_all_stats['offglac_melt_monthly'].values[0,:] = output_offglac_melt_monthly_stats[:,0] - output_ds_all_stats['offglac_refreeze_monthly'].values[0,:] = output_offglac_refreeze_monthly_stats[:,0] - output_ds_all_stats['offglac_snowpack_monthly'].values[0,:] = output_offglac_snowpack_monthly_stats[:,0] - + output_ds_all_stats["glac_temp_monthly"].values[ + 0, : + ] = output_glac_temp_monthly_stats[:, 0] + 273.15 + output_ds_all_stats["glac_prec_monthly"].values[ + 0, : + ] = output_glac_prec_monthly_stats[:, 0] + output_ds_all_stats["glac_acc_monthly"].values[ + 0, : + ] = output_glac_acc_monthly_stats[:, 0] + output_ds_all_stats["glac_refreeze_monthly"].values[ + 0, : + ] = output_glac_refreeze_monthly_stats[:, 0] + output_ds_all_stats["glac_melt_monthly"].values[ + 0, : + ] = output_glac_melt_monthly_stats[:, 0] + output_ds_all_stats[ + "glac_frontalablation_monthly" + ].values[ + 0, : + ] = output_glac_frontalablation_monthly_stats[:, 0] + output_ds_all_stats[ + "glac_massbaltotal_monthly" + ].values[ + 0, : + ] = output_glac_massbaltotal_monthly_stats[:, 0] + output_ds_all_stats["glac_snowline_monthly"].values[ + 0, : + ] = output_glac_snowline_monthly_stats[:, 0] + output_ds_all_stats[ + "glac_mass_change_ignored_annual" + ].values[ + 0, : + ] = output_glac_mass_change_ignored_annual_stats[:, 0] + output_ds_all_stats["offglac_prec_monthly"].values[ + 0, : + ] = output_offglac_prec_monthly_stats[:, 0] + output_ds_all_stats["offglac_melt_monthly"].values[ + 0, : + ] = output_offglac_melt_monthly_stats[:, 0] + output_ds_all_stats["offglac_refreeze_monthly"].values[ + 0, : + ] = output_offglac_refreeze_monthly_stats[:, 0] + output_ds_all_stats["offglac_snowpack_monthly"].values[ + 0, : + ] = output_offglac_snowpack_monthly_stats[:, 0] + # output median absolute deviation if nsims > 1: - output_ds_all_stats['glac_runoff_monthly_mad'].values[0,:] = output_glac_runoff_monthly_stats[:,1] - output_ds_all_stats['glac_area_annual_mad'].values[0,:] = output_glac_area_annual_stats[:,1] - output_ds_all_stats['glac_mass_annual_mad'].values[0,:] = output_glac_mass_annual_stats[:,1] - output_ds_all_stats['glac_mass_bsl_annual_mad'].values[0,:] = output_glac_mass_bsl_annual_stats[:,1] - output_ds_all_stats['glac_ELA_annual_mad'].values[0,:] = output_glac_ELA_annual_stats[:,1] - output_ds_all_stats['offglac_runoff_monthly_mad'].values[0,:] = output_offglac_runoff_monthly_stats[:,1] + output_ds_all_stats["glac_runoff_monthly_mad"].values[ + 0, : + ] = output_glac_runoff_monthly_stats[:, 1] + output_ds_all_stats["glac_area_annual_mad"].values[ + 0, : + ] = output_glac_area_annual_stats[:, 1] + output_ds_all_stats["glac_mass_annual_mad"].values[ + 0, : + ] = output_glac_mass_annual_stats[:, 1] + output_ds_all_stats["glac_mass_bsl_annual_mad"].values[ + 0, : + ] = output_glac_mass_bsl_annual_stats[:, 1] + output_ds_all_stats["glac_ELA_annual_mad"].values[ + 0, : + ] = output_glac_ELA_annual_stats[:, 1] + output_ds_all_stats[ + "offglac_runoff_monthly_mad" + ].values[0, :] = output_offglac_runoff_monthly_stats[ + :, 1 + ] if args.export_extra_vars: - output_ds_all_stats['glac_temp_monthly_mad'].values[0,:] = output_glac_temp_monthly_stats[:,1] - output_ds_all_stats['glac_prec_monthly_mad'].values[0,:] = output_glac_prec_monthly_stats[:,1] - output_ds_all_stats['glac_acc_monthly_mad'].values[0,:] = output_glac_acc_monthly_stats[:,1] - output_ds_all_stats['glac_refreeze_monthly_mad'].values[0,:] = output_glac_refreeze_monthly_stats[:,1] - output_ds_all_stats['glac_melt_monthly_mad'].values[0,:] = output_glac_melt_monthly_stats[:,1] - output_ds_all_stats['glac_frontalablation_monthly_mad'].values[0,:] = ( - output_glac_frontalablation_monthly_stats[:,1]) - output_ds_all_stats['glac_massbaltotal_monthly_mad'].values[0,:] = ( - output_glac_massbaltotal_monthly_stats[:,1]) - output_ds_all_stats['glac_snowline_monthly_mad'].values[0,:] = output_glac_snowline_monthly_stats[:,1] - output_ds_all_stats['glac_mass_change_ignored_annual_mad'].values[0,:] = ( - output_glac_mass_change_ignored_annual_stats[:,1]) - output_ds_all_stats['offglac_prec_monthly_mad'].values[0,:] = output_offglac_prec_monthly_stats[:,1] - output_ds_all_stats['offglac_melt_monthly_mad'].values[0,:] = output_offglac_melt_monthly_stats[:,1] - output_ds_all_stats['offglac_refreeze_monthly_mad'].values[0,:] = output_offglac_refreeze_monthly_stats[:,1] - output_ds_all_stats['offglac_snowpack_monthly_mad'].values[0,:] = output_offglac_snowpack_monthly_stats[:,1] + output_ds_all_stats[ + "glac_temp_monthly_mad" + ].values[0, :] = output_glac_temp_monthly_stats[ + :, 1 + ] + output_ds_all_stats[ + "glac_prec_monthly_mad" + ].values[0, :] = output_glac_prec_monthly_stats[ + :, 1 + ] + output_ds_all_stats["glac_acc_monthly_mad"].values[ + 0, : + ] = output_glac_acc_monthly_stats[:, 1] + output_ds_all_stats[ + "glac_refreeze_monthly_mad" + ].values[ + 0, : + ] = output_glac_refreeze_monthly_stats[:, 1] + output_ds_all_stats[ + "glac_melt_monthly_mad" + ].values[0, :] = output_glac_melt_monthly_stats[ + :, 1 + ] + output_ds_all_stats[ + "glac_frontalablation_monthly_mad" + ].values[ + 0, : + ] = output_glac_frontalablation_monthly_stats[:, 1] + output_ds_all_stats[ + "glac_massbaltotal_monthly_mad" + ].values[ + 0, : + ] = output_glac_massbaltotal_monthly_stats[:, 1] + output_ds_all_stats[ + "glac_snowline_monthly_mad" + ].values[ + 0, : + ] = output_glac_snowline_monthly_stats[:, 1] + output_ds_all_stats[ + "glac_mass_change_ignored_annual_mad" + ].values[ + 0, : + ] = output_glac_mass_change_ignored_annual_stats[ + :, 1 + ] + output_ds_all_stats[ + "offglac_prec_monthly_mad" + ].values[0, :] = output_offglac_prec_monthly_stats[ + :, 1 + ] + output_ds_all_stats[ + "offglac_melt_monthly_mad" + ].values[0, :] = output_offglac_melt_monthly_stats[ + :, 1 + ] + output_ds_all_stats[ + "offglac_refreeze_monthly_mad" + ].values[ + 0, : + ] = output_offglac_refreeze_monthly_stats[:, 1] + output_ds_all_stats[ + "offglac_snowpack_monthly_mad" + ].values[ + 0, : + ] = output_offglac_snowpack_monthly_stats[:, 1] # export merged netcdf glacierwide stats - output_stats.save_xr_ds(output_stats.get_fn().replace('SETS',f'{nsims}sets') + 'all.nc') + output_stats.save_xr_ds( + output_stats.get_fn().replace("SETS", f"{nsims}sets") + + "all.nc" + ) # ----- DECADAL ICE THICKNESS STATS FOR OVERDEEPENINGS ----- - if args.export_binned_data and glacier_rgi_table.Area > pygem_prms['sim']['out']['export_binned_area_threshold']: - + if ( + args.export_binned_data + and glacier_rgi_table.Area + > pygem_prms["sim"]["out"][ + "export_binned_area_threshold" + ] + ): # Distance from top of glacier downglacier - output_glac_bin_dist = np.arange(nfls[0].nx) * nfls[0].dx_meter + output_glac_bin_dist = ( + np.arange(nfls[0].nx) * nfls[0].dx_meter + ) if args.export_all_simiters and nsims > 1: # Instantiate dataset - output_binned = output.binned_stats(glacier_rgi_table=glacier_rgi_table, - dates_table=dates_table, - nsims=1, - nbins = surface_h_initial.shape[0], - binned_components = args.export_binned_components, - pygem_version=pygem.__version__, - gcm_name = gcm_name, - scenario = scenario, - realization=realization, - modelprms = modelprms, - ref_startyear = args.ref_startyear, - ref_endyear = ref_endyear, - gcm_startyear = args.gcm_startyear, - gcm_endyear = args.gcm_endyear, - option_calibration = args.option_calibration, - option_bias_adjustment = args.option_bias_adjustment) + output_binned = output.binned_stats( + glacier_rgi_table=glacier_rgi_table, + dates_table=dates_table, + nsims=1, + nbins=surface_h_initial.shape[0], + binned_components=args.export_binned_components, + pygem_version=pygem.__version__, + gcm_name=gcm_name, + scenario=scenario, + realization=realization, + modelprms=modelprms, + ref_startyear=args.ref_startyear, + ref_endyear=ref_endyear, + gcm_startyear=args.gcm_startyear, + gcm_endyear=args.gcm_endyear, + option_calibration=args.option_calibration, + option_bias_adjustment=args.option_bias_adjustment, + ) for n_iter in range(nsims): # pass model params for iteration and update output dataset model params - output_binned.set_modelprms({key: modelprms_all[key][n_iter] for key in modelprms_all}) + output_binned.set_modelprms( + { + key: modelprms_all[key][n_iter] + for key in modelprms_all + } + ) # create and return xarray dataset output_binned.create_xr_ds() - output_ds_binned_stats = output_binned.get_xr_ds() + output_ds_binned_stats = ( + output_binned.get_xr_ds() + ) # fill values - output_ds_binned_stats['bin_distance'].values[0,:] = output_glac_bin_dist - output_ds_binned_stats['bin_surface_h_initial'].values[0,:] = surface_h_initial - output_ds_binned_stats['bin_area_annual'].values[0,:,:] = output_glac_bin_area_annual[:,:,n_iter] - output_ds_binned_stats['bin_mass_annual'].values[0,:,:] = output_glac_bin_mass_annual[:,:,n_iter] - output_ds_binned_stats['bin_thick_annual'].values[0,:,:] = output_glac_bin_icethickness_annual[:,:,n_iter] - output_ds_binned_stats['bin_massbalclim_annual'].values[0,:,:] = output_glac_bin_massbalclim_annual[:,:,n_iter] - output_ds_binned_stats['bin_massbalclim_monthly'].values[0,:,:] = output_glac_bin_massbalclim_monthly[:,:,n_iter] + output_ds_binned_stats["bin_distance"].values[ + 0, : + ] = output_glac_bin_dist + output_ds_binned_stats[ + "bin_surface_h_initial" + ].values[0, :] = surface_h_initial + output_ds_binned_stats[ + "bin_area_annual" + ].values[ + 0, :, : + ] = output_glac_bin_area_annual[:, :, n_iter] + output_ds_binned_stats[ + "bin_mass_annual" + ].values[ + 0, :, : + ] = output_glac_bin_mass_annual[:, :, n_iter] + output_ds_binned_stats[ + "bin_thick_annual" + ].values[ + 0, :, : + ] = output_glac_bin_icethickness_annual[ + :, :, n_iter + ] + output_ds_binned_stats[ + "bin_massbalclim_annual" + ].values[ + 0, :, : + ] = output_glac_bin_massbalclim_annual[ + :, :, n_iter + ] + output_ds_binned_stats[ + "bin_massbalclim_monthly" + ].values[ + 0, :, : + ] = output_glac_bin_massbalclim_monthly[ + :, :, n_iter + ] if args.export_binned_components: - output_ds_binned_stats['bin_accumulation_monthly'].values[0,:,:] = output_glac_bin_acc_monthly[:,:,n_iter] - output_ds_binned_stats['bin_melt_monthly'].values[0,:,:] = output_glac_bin_melt_monthly[:,:,n_iter] - output_ds_binned_stats['bin_refreeze_monthly'].values[0,:,:] = output_glac_bin_refreeze_monthly[:,:,n_iter] + output_ds_binned_stats[ + "bin_accumulation_monthly" + ].values[ + 0, :, : + ] = output_glac_bin_acc_monthly[ + :, :, n_iter + ] + output_ds_binned_stats[ + "bin_melt_monthly" + ].values[ + 0, :, : + ] = output_glac_bin_melt_monthly[ + :, :, n_iter + ] + output_ds_binned_stats[ + "bin_refreeze_monthly" + ].values[ + 0, :, : + ] = output_glac_bin_refreeze_monthly[ + :, :, n_iter + ] # export binned stats for iteration - output_binned.save_xr_ds(output_binned.get_fn().replace('SETS',f'set{n_iter}') + 'binned.nc') + output_binned.save_xr_ds( + output_binned.get_fn().replace( + "SETS", f"set{n_iter}" + ) + + "binned.nc" + ) # instantiate dataset for merged simulations - output_binned = output.binned_stats(glacier_rgi_table=glacier_rgi_table, - dates_table=dates_table, - nsims=nsims, - nbins = surface_h_initial.shape[0], - binned_components = args.export_binned_components, - pygem_version=pygem.__version__, - gcm_name = gcm_name, - scenario = scenario, - realization=realization, - modelprms = modelprms, - ref_startyear = args.ref_startyear, - ref_endyear = ref_endyear, - gcm_startyear = args.gcm_startyear, - gcm_endyear = args.gcm_endyear, - option_calibration = args.option_calibration, - option_bias_adjustment = args.option_bias_adjustment) + output_binned = output.binned_stats( + glacier_rgi_table=glacier_rgi_table, + dates_table=dates_table, + nsims=nsims, + nbins=surface_h_initial.shape[0], + binned_components=args.export_binned_components, + pygem_version=pygem.__version__, + gcm_name=gcm_name, + scenario=scenario, + realization=realization, + modelprms=modelprms, + ref_startyear=args.ref_startyear, + ref_endyear=ref_endyear, + gcm_startyear=args.gcm_startyear, + gcm_endyear=args.gcm_endyear, + option_calibration=args.option_calibration, + option_bias_adjustment=args.option_bias_adjustment, + ) # create and return xarray dataset output_binned.create_xr_ds() output_ds_binned_stats = output_binned.get_xr_ds() # populate dataset with stats from each variable of interest - output_ds_binned_stats['bin_distance'].values = output_glac_bin_dist[np.newaxis, :] - output_ds_binned_stats['bin_surface_h_initial'].values = surface_h_initial[np.newaxis, :] - output_ds_binned_stats['bin_area_annual'].values = ( - np.median(output_glac_bin_area_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_mass_annual'].values = ( - np.median(output_glac_bin_mass_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_thick_annual'].values = ( - np.median(output_glac_bin_icethickness_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_massbalclim_annual'].values = ( - np.median(output_glac_bin_massbalclim_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_massbalclim_monthly'].values = ( - np.median(output_glac_bin_massbalclim_monthly, axis=2)[np.newaxis,:,:]) + output_ds_binned_stats[ + "bin_distance" + ].values = output_glac_bin_dist[np.newaxis, :] + output_ds_binned_stats[ + "bin_surface_h_initial" + ].values = surface_h_initial[np.newaxis, :] + output_ds_binned_stats[ + "bin_area_annual" + ].values = np.median( + output_glac_bin_area_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_mass_annual" + ].values = np.median( + output_glac_bin_mass_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_thick_annual" + ].values = np.median( + output_glac_bin_icethickness_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_massbalclim_annual" + ].values = np.median( + output_glac_bin_massbalclim_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_massbalclim_monthly" + ].values = np.median( + output_glac_bin_massbalclim_monthly, axis=2 + )[np.newaxis, :, :] if args.export_binned_components: - output_ds_binned_stats['bin_accumulation_monthly'].values = ( - np.median(output_glac_bin_acc_monthly, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_melt_monthly'].values = ( - np.median(output_glac_bin_melt_monthly, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_refreeze_monthly'].values = ( - np.median(output_glac_bin_refreeze_monthly, axis=2)[np.newaxis,:,:]) + output_ds_binned_stats[ + "bin_accumulation_monthly" + ].values = np.median( + output_glac_bin_acc_monthly, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_melt_monthly" + ].values = np.median( + output_glac_bin_melt_monthly, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_refreeze_monthly" + ].values = np.median( + output_glac_bin_refreeze_monthly, axis=2 + )[np.newaxis, :, :] if nsims > 1: - output_ds_binned_stats['bin_mass_annual_mad'].values = ( - median_abs_deviation(output_glac_bin_mass_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_thick_annual_mad'].values = ( - median_abs_deviation(output_glac_bin_icethickness_annual, axis=2)[np.newaxis,:,:]) - output_ds_binned_stats['bin_massbalclim_annual_mad'].values = ( - median_abs_deviation(output_glac_bin_massbalclim_annual, axis=2)[np.newaxis,:,:]) - + output_ds_binned_stats[ + "bin_mass_annual_mad" + ].values = median_abs_deviation( + output_glac_bin_mass_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_thick_annual_mad" + ].values = median_abs_deviation( + output_glac_bin_icethickness_annual, axis=2 + )[np.newaxis, :, :] + output_ds_binned_stats[ + "bin_massbalclim_annual_mad" + ].values = median_abs_deviation( + output_glac_bin_massbalclim_annual, axis=2 + )[np.newaxis, :, :] + # export merged netcdf glacierwide stats - output_binned.save_xr_ds(output_binned.get_fn().replace('SETS',f'{nsims}sets') + 'binned.nc') + output_binned.save_xr_ds( + output_binned.get_fn().replace( + "SETS", f"{nsims}sets" + ) + + "binned.nc" + ) except Exception as err: # LOG FAILURE - fail_fp = pygem_prms['root'] + '/Output/simulations/failed/' + reg_str + '/' + gcm_name + '/' - if gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - fail_fp += scenario + '/' + fail_fp = ( + pygem_prms["root"] + + "/Output/simulations/failed/" + + reg_str + + "/" + + gcm_name + + "/" + ) + if gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + fail_fp += scenario + "/" if not os.path.exists(fail_fp): os.makedirs(fail_fp, exist_ok=True) txt_fn_fail = glacier_str + "-sim_failed.txt" with open(fail_fp + txt_fn_fail, "w") as text_file: - text_file.write(glacier_str + f' failed to complete simulation: {err}') + text_file.write( + glacier_str + f" failed to complete simulation: {err}" + ) # Global variables for Spyder development if args.ncores == 1: @@ -1312,17 +2544,21 @@ def run(list_packed_vars): main_vars = inspect.currentframe().f_locals -#%% PARALLEL PROCESSING +# %% PARALLEL PROCESSING def main(): time_start = time.time() parser = getparser() args = parser.parse_args() # date range check try: - assert args.ref_startyear < args.ref_endyear, f"ref_startyear [{args.ref_startyear}] must be less than ref_endyear [{args.ref_endyear}]" - assert args.gcm_startyear < args.gcm_endyear, f"gcm_startyear [{args.gcm_startyear}] must be less than gcm_endyear [{args.gcm_endyear}]" + assert args.ref_startyear < args.ref_endyear, ( + f"ref_startyear [{args.ref_startyear}] must be less than ref_endyear [{args.ref_endyear}]" + ) + assert args.gcm_startyear < args.gcm_endyear, ( + f"gcm_startyear [{args.gcm_startyear}] must be less than gcm_endyear [{args.gcm_endyear}]" + ) except AssertionError as err: - print('error: ', err) + print("error: ", err) sys.exit(1) # RGI glacier number if args.rgi_glac_number: @@ -1331,14 +2567,18 @@ def main(): glac_no = [float(g) for g in glac_no] glac_no = [f"{g:.5f}" if g >= 10 else f"0{g:.5f}" for g in glac_no] elif args.rgi_glac_number_fn is not None: - with open(args.rgi_glac_number_fn, 'r') as f: + with open(args.rgi_glac_number_fn, "r") as f: glac_no = json.load(f) else: main_glac_rgi_all = modelsetup.selectglaciersrgitable( - rgi_regionsO1=args.rgi_region01, rgi_regionsO2=args.rgi_region02, - include_landterm=pygem_prms['setup']['include_landterm'], include_laketerm=pygem_prms['setup']['include_laketerm'], - include_tidewater=pygem_prms['setup']['include_tidewater'], min_glac_area_km2=pygem_prms['setup']['min_glac_area_km2']) - glac_no = list(main_glac_rgi_all['rgino_str'].values) + rgi_regionsO1=args.rgi_region01, + rgi_regionsO2=args.rgi_region02, + include_landterm=pygem_prms["setup"]["include_landterm"], + include_laketerm=pygem_prms["setup"]["include_laketerm"], + include_tidewater=pygem_prms["setup"]["include_tidewater"], + min_glac_area_km2=pygem_prms["setup"]["min_glac_area_km2"], + ) + glac_no = list(main_glac_rgi_all["rgino_str"].values) # Number of cores for parallel processing if args.ncores > 1: @@ -1347,7 +2587,9 @@ def main(): num_cores = 1 # Glacier number lists to pass for parallel processing - glac_no_lsts = modelsetup.split_list(glac_no, n=num_cores, option_ordered=args.option_ordered) + glac_no_lsts = modelsetup.split_list( + glac_no, n=num_cores, option_ordered=args.option_ordered + ) # Read GCM names from argument parser gcm_name = args.gcm_list_fn @@ -1358,21 +2600,21 @@ def main(): gcm_list = [args.ref_gcm_name] scenario = args.scenario else: - with open(args.gcm_list_fn, 'r') as gcm_fn: + with open(args.gcm_list_fn, "r") as gcm_fn: gcm_list = gcm_fn.read().splitlines() - scenario = os.path.basename(args.gcm_list_fn).split('_')[1] - print('Found %d gcms to process'%(len(gcm_list))) - + scenario = os.path.basename(args.gcm_list_fn).split("_")[1] + print("Found %d gcms to process" % (len(gcm_list))) + # Read realizations from argument parser if args.realization is not None: realizations = [args.realization] elif args.realization_list is not None: - with open(args.realization_list, 'r') as real_fn: + with open(args.realization_list, "r") as real_fn: realizations = list(real_fn.read().splitlines()) - print('Found %d realizations to process'%(len(realizations))) + print("Found %d realizations to process" % (len(realizations))) else: realizations = None - + # Producing realization or realization list. Best to convert them into the same format! # Then pass this as a list or None. # If passing this through the list_packed_vars, then don't go back and get from arg parser again! @@ -1380,31 +2622,36 @@ def main(): # Loop through all GCMs for gcm_name in gcm_list: if args.scenario is None: - print('Processing:', gcm_name) - elif not args.scenario is None: - print('Processing:', gcm_name, scenario) + print("Processing:", gcm_name) + elif args.scenario is not None: + print("Processing:", gcm_name, scenario) # Pack variables for multiprocessing - list_packed_vars = [] + list_packed_vars = [] if realizations is not None: for realization in realizations: for count, glac_no_lst in enumerate(glac_no_lsts): - list_packed_vars.append([count, glac_no_lst, gcm_name, realization]) + list_packed_vars.append( + [count, glac_no_lst, gcm_name, realization] + ) else: for count, glac_no_lst in enumerate(glac_no_lsts): - list_packed_vars.append([count, glac_no_lst, gcm_name, realizations]) - - print('Processing with ' + str(num_cores) + ' cores...') + list_packed_vars.append( + [count, glac_no_lst, gcm_name, realizations] + ) + + print("Processing with " + str(num_cores) + " cores...") # Parallel processing if num_cores > 1: with multiprocessing.Pool(num_cores) as p: - p.map(run,list_packed_vars) + p.map(run, list_packed_vars) # If not in parallel, then only should be one loop else: # Loop through the chunks and export bias adjustments for n in range(len(list_packed_vars)): run(list_packed_vars[n]) - print('Total processing time:', time.time()-time_start, 's') + print("Total processing time:", time.time() - time_start, "s") + if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/pygem/class_climate.py b/pygem/class_climate.py index 10c28935..69d75ae3 100755 --- a/pygem/class_climate.py +++ b/pygem/class_climate.py @@ -7,20 +7,26 @@ class of climate data and functions associated with manipulating the dataset to be in the proper format """ + import os + +import numpy as np + # External libraries import pandas as pd -import numpy as np import xarray as xr + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file -class GCM(): + +class GCM: """ Global climate model data properties and functions used to automatically retrieve data. - + Attributes ---------- name : str @@ -30,178 +36,319 @@ class GCM(): realization : str realization from large ensemble (example: '1011.001' or '1301.020') """ - def __init__(self, - name=str(), - scenario=str(), - realization=None): + + def __init__(self, name=str(), scenario=str(), realization=None): """ Add variable name and specific properties associated with each gcm. """ - - if pygem_prms['rgi']['rgi_lon_colname'] not in ['CenLon_360']: - assert 1==0, 'Longitude does not use 360 degrees. Check how negative values are handled!' - + + if pygem_prms["rgi"]["rgi_lon_colname"] not in ["CenLon_360"]: + assert 1 == 0, ( + "Longitude does not use 360 degrees. Check how negative values are handled!" + ) + # Source of climate data self.name = name - + # If multiple realizations from each model+scenario are being used, - # then self.realization = realization. - # Otherwise, the realization attribute is not considered for single + # then self.realization = realization. + # Otherwise, the realization attribute is not considered for single # realization model+scenario simulations. if realization is not None: self.realization = realization - + # Set parameters for CESM2 Large Ensemble - if self.name == 'smbb.f09_g17.LE2': + if self.name == "smbb.f09_g17.LE2": # Standardized CESM2 Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + scenario + '_' + name + '-' + realization + '.cam.h0.1980-2100.nc' - self.prec_fn = self.prec_vn + '_mon_' + scenario + '_' + name + '-' + realization + '.cam.h0.1980-2100.nc' - self.elev_fn = self.elev_vn + '_fx_' + scenario + '_' + name + '.cam.h0.nc' + self.temp_fn = ( + self.temp_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + ".cam.h0.1980-2100.nc" + ) + self.prec_fn = ( + self.prec_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + ".cam.h0.1980-2100.nc" + ) + self.elev_fn = ( + self.elev_vn + + "_fx_" + + scenario + + "_" + + name + + ".cam.h0.nc" + ) # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario + pygem_prms['climate']['paths']['cesm2_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cesm2_relpath'] + scenario + pygem_prms['climate']['paths']['cesm2_fp_fx_ending'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cesm2_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cesm2_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cesm2_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cesm2_fp_fx_ending"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario - + # Set parameters for GFDL SPEAR Large Ensemble - elif self.name == 'GFDL-SPEAR-MED': + elif self.name == "GFDL-SPEAR-MED": # Standardized GFDL SPEAR Large Ensemble format (GCM/SSP) # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'zsurf' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "zsurf" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + scenario + '_' + name + '-' + realization + 'i1p1f1_gr3_1980-2100.nc' - self.prec_fn = self.prec_vn + '_mon_' + scenario + '_' + name + '-' + realization + 'i1p1f1_gr3_1980-2100.nc' - self.elev_fn = self.elev_vn + '_fx_' + scenario + '_' + name + '.nc' + self.temp_fn = ( + self.temp_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + "i1p1f1_gr3_1980-2100.nc" + ) + self.prec_fn = ( + self.prec_vn + + "_mon_" + + scenario + + "_" + + name + + "-" + + realization + + "i1p1f1_gr3_1980-2100.nc" + ) + self.elev_fn = ( + self.elev_vn + "_fx_" + scenario + "_" + name + ".nc" + ) # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario + pygem_prms['climate']['paths']['gfdl_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['gfdl_relpath'] + scenario + pygem_prms['climate']['paths']['gfdl_fp_fx_ending'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["gfdl_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["gfdl_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["gfdl_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["gfdl_fp_fx_ending"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario - + else: self.realization = [] - + # Set parameters for ERA5, ERA-Interim, and CMIP5 netcdf files - if self.name == 'ERA5': + if self.name == "ERA5": # Variable names - self.temp_vn = 't2m' - self.tempstd_vn = 't2m_std' - self.prec_vn = 'tp' - self.elev_vn = 'z' - self.lat_vn = 'latitude' - self.lon_vn = 'longitude' - self.time_vn = 'time' - self.lr_vn = 'lapserate' + self.temp_vn = "t2m" + self.tempstd_vn = "t2m_std" + self.prec_vn = "tp" + self.elev_vn = "z" + self.lat_vn = "latitude" + self.lon_vn = "longitude" + self.time_vn = "time" + self.lr_vn = "lapserate" # Variable filenames - self.temp_fn = pygem_prms['climate']['paths']['era5_temp_fn'] - self.tempstd_fn = pygem_prms['climate']['paths']['era5_tempstd_fn'] - self.prec_fn = pygem_prms['climate']['paths']['era5_prec_fn'] - self.elev_fn = pygem_prms['climate']['paths']['era5_elev_fn'] - self.lr_fn = pygem_prms['climate']['paths']['era5_lr_fn'] + self.temp_fn = pygem_prms["climate"]["paths"]["era5_temp_fn"] + self.tempstd_fn = pygem_prms["climate"]["paths"][ + "era5_tempstd_fn" + ] + self.prec_fn = pygem_prms["climate"]["paths"]["era5_prec_fn"] + self.elev_fn = pygem_prms["climate"]["paths"]["era5_elev_fn"] + self.lr_fn = pygem_prms["climate"]["paths"]["era5_lr_fn"] # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['era5_relpath'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['era5_relpath'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["era5_relpath"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["era5_relpath"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] - - elif self.name == 'ERA-Interim': + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + + elif self.name == "ERA-Interim": # Variable names - self.temp_vn = 't2m' - self.prec_vn = 'tp' - self.elev_vn = 'z' - self.lat_vn = 'latitude' - self.lon_vn = 'longitude' - self.time_vn = 'time' - self.lr_vn = 'lapserate' + self.temp_vn = "t2m" + self.prec_vn = "tp" + self.elev_vn = "z" + self.lat_vn = "latitude" + self.lon_vn = "longitude" + self.time_vn = "time" + self.lr_vn = "lapserate" # Variable filenames - self.temp_fn = pygem_prms['climate']['paths']['eraint_temp_fn'] - self.prec_fn = pygem_prms['climate']['paths']['eraint_prec_fn'] - self.elev_fn = pygem_prms['climate']['paths']['eraint_elev_fn'] - self.lr_fn = pygem_prms['climate']['paths']['eraint_lr_fn'] + self.temp_fn = pygem_prms["climate"]["paths"]["eraint_temp_fn"] + self.prec_fn = pygem_prms["climate"]["paths"]["eraint_prec_fn"] + self.elev_fn = pygem_prms["climate"]["paths"]["eraint_elev_fn"] + self.lr_fn = pygem_prms["climate"]["paths"]["eraint_lr_fn"] # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['eraint_relpath'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['eraint_relpath'] + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["eraint_relpath"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["eraint_relpath"] + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] - + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] + # Standardized CMIP5 format (GCM/RCP) - elif 'rcp' in scenario: + elif "rcp" in scenario: # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = self.temp_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' - self.prec_fn = self.prec_vn + '_mon_' + name + '_' + scenario + '_r1i1p1_native.nc' - self.elev_fn = self.elev_vn + '_fx_' + name + '_' + scenario + '_r0i0p0.nc' + self.temp_fn = ( + self.temp_vn + + "_mon_" + + name + + "_" + + scenario + + "_r1i1p1_native.nc" + ) + self.prec_fn = ( + self.prec_vn + + "_mon_" + + name + + "_" + + scenario + + "_r1i1p1_native.nc" + ) + self.elev_fn = ( + self.elev_vn + + "_fx_" + + name + + "_" + + scenario + + "_r0i0p0.nc" + ) # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario + pygem_prms['climate']['paths']['cmip5_fp_var_ending'] - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + scenario + pygem_prms['climate']['paths']['cmip5_fp_fx_ending'] - if not os.path.exists(self.var_fp) and os.path.exists(pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/'): - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' - if not os.path.exists(self.fx_fp) and os.path.exists(pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/'): - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip5_relpath'] + name + '/' + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cmip5_fp_var_ending"] + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + scenario + + pygem_prms["climate"]["paths"]["cmip5_fp_fx_ending"] + ) + if not os.path.exists(self.var_fp) and os.path.exists( + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ): + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ) + if not os.path.exists(self.fx_fp) and os.path.exists( + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ): + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip5_relpath"] + + name + + "/" + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario - + # Standardized CMIP6 format (GCM/SSP) - elif 'ssp' in scenario: + elif "ssp" in scenario: # Variable names - self.temp_vn = 'tas' - self.prec_vn = 'pr' - self.elev_vn = 'orog' - self.lat_vn = 'lat' - self.lon_vn = 'lon' - self.time_vn = 'time' + self.temp_vn = "tas" + self.prec_vn = "pr" + self.elev_vn = "orog" + self.lat_vn = "lat" + self.lon_vn = "lon" + self.time_vn = "time" # Variable filenames - self.temp_fn = name + '_' + scenario + '_r1i1p1f1_' + self.temp_vn + '.nc' - self.prec_fn = name + '_' + scenario + '_r1i1p1f1_' + self.prec_vn + '.nc' - self.elev_fn = name + '_' + self.elev_vn + '.nc' + self.temp_fn = ( + name + "_" + scenario + "_r1i1p1f1_" + self.temp_vn + ".nc" + ) + self.prec_fn = ( + name + "_" + scenario + "_r1i1p1f1_" + self.prec_vn + ".nc" + ) + self.elev_fn = name + "_" + self.elev_vn + ".nc" # Variable filepaths - self.var_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip6_relpath'] + name + '/' - self.fx_fp = pygem_prms['root'] + pygem_prms['climate']['paths']['cmip6_relpath'] + name + '/' + self.var_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip6_relpath"] + + name + + "/" + ) + self.fx_fp = ( + pygem_prms["root"] + + pygem_prms["climate"]["paths"]["cmip6_relpath"] + + name + + "/" + ) # Extra information - self.timestep = pygem_prms['time']['timestep'] - self.rgi_lat_colname=pygem_prms['rgi']['rgi_lat_colname'] - self.rgi_lon_colname=pygem_prms['rgi']['rgi_lon_colname'] + self.timestep = pygem_prms["time"]["timestep"] + self.rgi_lat_colname = pygem_prms["rgi"]["rgi_lat_colname"] + self.rgi_lon_colname = pygem_prms["rgi"]["rgi_lon_colname"] self.scenario = scenario - - + def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): """ Import time invariant (constant) variables and extract nearest neighbor. - + Note: cmip5 data used surface height, while ERA-Interim data is geopotential - + Parameters ---------- filename : str @@ -210,7 +357,7 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): variable name main_glac_rgi : pandas dataframe dataframe containing relevant rgi glacier information - + Returns ------- glac_variable : numpy array @@ -220,63 +367,94 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): data = xr.open_dataset(self.fx_fp + filename) glac_variable = np.zeros(main_glac_rgi.shape[0]) # If time dimension included, then set the time index (required for ERA Interim, but not for CMIP5 or COAWST) - if 'time' in data[vn].coords: + if "time" in data[vn].coords: time_idx = 0 # ERA Interim has only 1 value of time, so index is 0 # Find Nearest Neighbor - if self.name == 'COAWST': + if self.name == "COAWST": for glac in range(main_glac_rgi.shape[0]): - latlon_dist = (((data[self.lat_vn].values - main_glac_rgi[self.rgi_lat_colname].values[glac])**2 + - (data[self.lon_vn].values - main_glac_rgi[self.rgi_lon_colname].values[glac])**2)**0.5) - latlon_nearidx = [x[0] for x in np.where(latlon_dist == latlon_dist.min())] + latlon_dist = ( + ( + data[self.lat_vn].values + - main_glac_rgi[self.rgi_lat_colname].values[glac] + ) + ** 2 + + ( + data[self.lon_vn].values + - main_glac_rgi[self.rgi_lon_colname].values[glac] + ) + ** 2 + ) ** 0.5 + latlon_nearidx = [ + x[0] for x in np.where(latlon_dist == latlon_dist.min()) + ] lat_nearidx = latlon_nearidx[0] lon_nearidx = latlon_nearidx[1] - glac_variable[glac] = ( - data[vn][latlon_nearidx[0], latlon_nearidx[1]].values) + glac_variable[glac] = data[vn][ + latlon_nearidx[0], latlon_nearidx[1] + ].values else: # argmin() finds the minimum distance between the glacier lat/lon and the GCM pixel - lat_nearidx = (np.abs(main_glac_rgi[self.rgi_lat_colname].values[:,np.newaxis] - - data.variables[self.lat_vn][:].values).argmin(axis=1)) - lon_nearidx = (np.abs(main_glac_rgi[self.rgi_lon_colname].values[:,np.newaxis] - - data.variables[self.lon_vn][:].values).argmin(axis=1)) - + lat_nearidx = np.abs( + main_glac_rgi[self.rgi_lat_colname].values[:, np.newaxis] + - data.variables[self.lat_vn][:].values + ).argmin(axis=1) + lon_nearidx = np.abs( + main_glac_rgi[self.rgi_lon_colname].values[:, np.newaxis] + - data.variables[self.lon_vn][:].values + ).argmin(axis=1) + latlon_nearidx = list(zip(lat_nearidx, lon_nearidx)) latlon_nearidx_unique = list(set(latlon_nearidx)) - + glac_variable_dict = {} for latlon in latlon_nearidx_unique: try: - glac_variable_dict[latlon] = data[vn][time_idx, latlon[0], latlon[1]].values + glac_variable_dict[latlon] = data[vn][ + time_idx, latlon[0], latlon[1] + ].values except: - glac_variable_dict[latlon] = data[vn][latlon[0], latlon[1]].values - - glac_variable = np.array([glac_variable_dict[x] for x in latlon_nearidx]) - + glac_variable_dict[latlon] = data[vn][ + latlon[0], latlon[1] + ].values + + glac_variable = np.array( + [glac_variable_dict[x] for x in latlon_nearidx] + ) + # Correct units if necessary (CMIP5 already in m a.s.l., ERA Interim is geopotential [m2 s-2]) if vn == self.elev_vn: # If the variable has units associated with geopotential, then convert to m.a.s.l (ERA Interim) - if 'units' in data[vn].attrs and (data[vn].attrs['units'] == 'm**2 s**-2'): + if "units" in data[vn].attrs and ( + data[vn].attrs["units"] == "m**2 s**-2" + ): # Convert m2 s-2 to m by dividing by gravity (ERA Interim states to use 9.80665) glac_variable = glac_variable / 9.80665 # Elseif units already in m.a.s.l., then continue - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': + elif "units" in data[vn].attrs and data[vn].attrs["units"] == "m": pass # Otherwise, provide warning else: - print('Check units of elevation from GCM is m.') - + print("Check units of elevation from GCM is m.") + return glac_variable - - def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_table, realizations=['r1i1p1f1','r4i1p1f1']): + def importGCMvarnearestneighbor_xarray( + self, + filename, + vn, + main_glac_rgi, + dates_table, + realizations=["r1i1p1f1", "r4i1p1f1"], + ): """ Import time series of variables and extract nearest neighbor. - + Note: "NG" refers to a homogenized "new generation" of products from ETH-Zurich. - The function is setup to select netcdf data using the dimensions: time, latitude, longitude (in that - order). Prior to running the script, the user must check that this is the correct order of the dimensions + The function is setup to select netcdf data using the dimensions: time, latitude, longitude (in that + order). Prior to running the script, the user must check that this is the correct order of the dimensions and the user should open the netcdf file to determine the names of each dimension as they may vary. - + Parameters ---------- filename : str @@ -287,7 +465,7 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ dataframe containing relevant rgi glacier information dates_table: pandas dataframe dataframe containing dates of model run - + Returns ------- glac_variable_series : numpy array @@ -298,112 +476,196 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ """ # Import netcdf file if not os.path.exists(self.var_fp + filename): - if os.path.exists(self.var_fp + filename.replace('r1i1p1f1','r4i1p1f1')): - filename = filename.replace('r1i1p1f1','r4i1p1f1') - if os.path.exists(self.var_fp + filename.replace('_native','')): - filename = filename.replace('_native','') - + if os.path.exists( + self.var_fp + filename.replace("r1i1p1f1", "r4i1p1f1") + ): + filename = filename.replace("r1i1p1f1", "r4i1p1f1") + if os.path.exists(self.var_fp + filename.replace("_native", "")): + filename = filename.replace("_native", "") + data = xr.open_dataset(self.var_fp + filename) - glac_variable_series = np.zeros((main_glac_rgi.shape[0],dates_table.shape[0])) - + glac_variable_series = np.zeros( + (main_glac_rgi.shape[0], dates_table.shape[0]) + ) + # Check GCM provides required years of data - years_check = pd.Series(data['time']).apply(lambda x: int(x.strftime('%Y'))) - assert years_check.max() >= dates_table.year.max(), self.name + ' does not provide data out to ' + str(dates_table.year.max()) - assert years_check.min() <= dates_table.year.min(), self.name + ' does not provide data back to ' + str(dates_table.year.min()) - + years_check = pd.Series(data["time"]).apply( + lambda x: int(x.strftime("%Y")) + ) + assert years_check.max() >= dates_table.year.max(), ( + self.name + + " does not provide data out to " + + str(dates_table.year.max()) + ) + assert years_check.min() <= dates_table.year.min(), ( + self.name + + " does not provide data back to " + + str(dates_table.year.min()) + ) + # Determine the correct time indices - if self.timestep == 'monthly': - start_idx = (np.where(pd.Series(data[self.time_vn]).apply(lambda x: x.strftime('%Y-%m')) == - dates_table['date'].apply(lambda x: x.strftime('%Y-%m'))[0]))[0][0] - end_idx = (np.where(pd.Series(data[self.time_vn]).apply(lambda x: x.strftime('%Y-%m')) == - dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m'))[dates_table.shape[0] - 1]))[0][0] + if self.timestep == "monthly": + start_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m") + ) + == dates_table["date"].apply( + lambda x: x.strftime("%Y-%m") + )[0] + ) + )[0][0] + end_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m") + ) + == dates_table["date"].apply( + lambda x: x.strftime("%Y-%m") + )[dates_table.shape[0] - 1] + ) + )[0][0] # np.where finds the index position where to values are equal # pd.Series(data.variables[gcm_time_varname]) creates a pandas series of the time variable associated with # the netcdf - # .apply(lambda x: x.strftime('%Y-%m')) converts the timestamp to a string with YYYY-MM to enable the + # .apply(lambda x: x.strftime('%Y-%m')) converts the timestamp to a string with YYYY-MM to enable the # comparison - # > different climate dta can have different date formats, so this standardization for comparison is + # > different climate dta can have different date formats, so this standardization for comparison is # important # ex. monthly data may provide date on 1st of month or middle of month, so YYYY-MM-DD would not work # The same processing is done for the dates_table['date'] to facilitate the comparison # [0] is used to access the first date # dates_table.shape[0] - 1 is used to access the last date - # The final indexing [0][0] is used to access the value, which is inside of an array containing extraneous + # The final indexing [0][0] is used to access the value, which is inside of an array containing extraneous # information - elif self.timestep == 'daily': - start_idx = (np.where(pd.Series(data[self.time_vn]) - .apply(lambda x: x.strftime('%Y-%m-%d')) == dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m-%d'))[0]))[0][0] - end_idx = (np.where(pd.Series(data[self.time_vn]) - .apply(lambda x: x.strftime('%Y-%m-%d')) == dates_table['date'] - .apply(lambda x: x.strftime('%Y-%m-%d'))[dates_table.shape[0] - 1]))[0][0] + elif self.timestep == "daily": + start_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + == dates_table["date"].apply( + lambda x: x.strftime("%Y-%m-%d") + )[0] + ) + )[0][0] + end_idx = ( + np.where( + pd.Series(data[self.time_vn]).apply( + lambda x: x.strftime("%Y-%m-%d") + ) + == dates_table["date"].apply( + lambda x: x.strftime("%Y-%m-%d") + )[dates_table.shape[0] - 1] + ) + )[0][0] # Extract the time series - time_series = pd.Series(data[self.time_vn][start_idx:end_idx+1]) + time_series = pd.Series(data[self.time_vn][start_idx : end_idx + 1]) # Find Nearest Neighbor - if self.name == 'COAWST': + if self.name == "COAWST": for glac in range(main_glac_rgi.shape[0]): - latlon_dist = (((data[self.lat_vn].values - main_glac_rgi[self.rgi_lat_colname].values[glac])**2 + - (data[self.lon_vn].values - main_glac_rgi[self.rgi_lon_colname].values[glac])**2)**0.5) - latlon_nearidx = [x[0] for x in np.where(latlon_dist == latlon_dist.min())] + latlon_dist = ( + ( + data[self.lat_vn].values + - main_glac_rgi[self.rgi_lat_colname].values[glac] + ) + ** 2 + + ( + data[self.lon_vn].values + - main_glac_rgi[self.rgi_lon_colname].values[glac] + ) + ** 2 + ) ** 0.5 + latlon_nearidx = [ + x[0] for x in np.where(latlon_dist == latlon_dist.min()) + ] lat_nearidx = latlon_nearidx[0] lon_nearidx = latlon_nearidx[1] - glac_variable_series[glac,:] = ( - data[vn][start_idx:end_idx+1, latlon_nearidx[0], latlon_nearidx[1]].values) + glac_variable_series[glac, :] = data[vn][ + start_idx : end_idx + 1, + latlon_nearidx[0], + latlon_nearidx[1], + ].values else: - # argmin() finds the minimum distance between the glacier lat/lon and the GCM pixel; .values is used to + # argmin() finds the minimum distance between the glacier lat/lon and the GCM pixel; .values is used to # extract the position's value as opposed to having an array - lat_nearidx = (np.abs(main_glac_rgi[self.rgi_lat_colname].values[:,np.newaxis] - - data.variables[self.lat_vn][:].values).argmin(axis=1)) - lon_nearidx = (np.abs(main_glac_rgi[self.rgi_lon_colname].values[:,np.newaxis] - - data.variables[self.lon_vn][:].values).argmin(axis=1)) + lat_nearidx = np.abs( + main_glac_rgi[self.rgi_lat_colname].values[:, np.newaxis] + - data.variables[self.lat_vn][:].values + ).argmin(axis=1) + lon_nearidx = np.abs( + main_glac_rgi[self.rgi_lon_colname].values[:, np.newaxis] + - data.variables[self.lon_vn][:].values + ).argmin(axis=1) # Find unique latitude/longitudes latlon_nearidx = list(zip(lat_nearidx, lon_nearidx)) latlon_nearidx_unique = list(set(latlon_nearidx)) # Create dictionary of time series for each unique latitude/longitude glac_variable_dict = {} - for latlon in latlon_nearidx_unique: - if 'expver' in data.keys(): + for latlon in latlon_nearidx_unique: + if "expver" in data.keys(): expver_idx = 0 - glac_variable_dict[latlon] = data[vn][start_idx:end_idx+1, expver_idx, latlon[0], latlon[1]].values + glac_variable_dict[latlon] = data[vn][ + start_idx : end_idx + 1, + expver_idx, + latlon[0], + latlon[1], + ].values else: - glac_variable_dict[latlon] = data[vn][start_idx:end_idx+1, latlon[0], latlon[1]].values - + glac_variable_dict[latlon] = data[vn][ + start_idx : end_idx + 1, latlon[0], latlon[1] + ].values + # Convert to series - glac_variable_series = np.array([glac_variable_dict[x] for x in latlon_nearidx]) + glac_variable_series = np.array( + [glac_variable_dict[x] for x in latlon_nearidx] + ) # Perform corrections to the data if necessary # Surface air temperature corrections - if vn in ['tas', 't2m', 'T2']: - if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'K': + if vn in ["tas", "t2m", "T2"]: + if "units" in data[vn].attrs and data[vn].attrs["units"] == "K": # Convert from K to deg C glac_variable_series = glac_variable_series - 273.15 else: - print('Check units of air temperature from GCM is degrees C.') - elif vn in ['t2m_std']: - if 'units' in data[vn].attrs and data[vn].attrs['units'] not in ['C', 'K']: - print('Check units of air temperature standard deviation from GCM is degrees C or K') + print("Check units of air temperature from GCM is degrees C.") + elif vn in ["t2m_std"]: + if "units" in data[vn].attrs and data[vn].attrs["units"] not in [ + "C", + "K", + ]: + print( + "Check units of air temperature standard deviation from GCM is degrees C or K" + ) # Precipitation corrections # If the variable is precipitation - elif vn in ['pr', 'tp', 'TOTPRECIP']: + elif vn in ["pr", "tp", "TOTPRECIP"]: # If the variable has units and those units are meters (ERA Interim) - if 'units' in data[vn].attrs and data[vn].attrs['units'] == 'm': + if "units" in data[vn].attrs and data[vn].attrs["units"] == "m": pass # Elseif the variable has units and those units are kg m-2 s-1 (CMIP5/CMIP6) - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'kg m-2 s-1': + elif ( + "units" in data[vn].attrs + and data[vn].attrs["units"] == "kg m-2 s-1" + ): # Convert from kg m-2 s-1 to m day-1 - glac_variable_series = glac_variable_series/1000*3600*24 + glac_variable_series = glac_variable_series / 1000 * 3600 * 24 # (1 kg m-2 s-1) * (1 m3/1000 kg) * (3600 s / hr) * (24 hr / day) = (m day-1) # Elseif the variable has units and those units are mm (COAWST) - elif 'units' in data[vn].attrs and data[vn].attrs['units'] == 'mm': - glac_variable_series = glac_variable_series/1000 + elif "units" in data[vn].attrs and data[vn].attrs["units"] == "mm": + glac_variable_series = glac_variable_series / 1000 # Else check the variables units else: - print('Check units of precipitation from GCM is meters per day.') - if self.timestep == 'monthly' and self.name != 'COAWST': + print( + "Check units of precipitation from GCM is meters per day." + ) + if self.timestep == "monthly" and self.name != "COAWST": # Convert from meters per day to meters per month (COAWST data already 'monthly accumulated precipitation') - if 'daysinmonth' in dates_table.columns: - glac_variable_series = glac_variable_series * dates_table['daysinmonth'].values[np.newaxis,:] + if "daysinmonth" in dates_table.columns: + glac_variable_series = ( + glac_variable_series + * dates_table["daysinmonth"].values[np.newaxis, :] + ) elif vn != self.lr_vn: - print('Check units of air temperature or precipitation') + print("Check units of air temperature or precipitation") return glac_variable_series, time_series diff --git a/pygem/gcmbiasadj.py b/pygem/gcmbiasadj.py index 52ebce0f..927b5282 100755 --- a/pygem/gcmbiasadj.py +++ b/pygem/gcmbiasadj.py @@ -7,58 +7,84 @@ Run bias adjustments a given climate dataset """ + # Built-in libraries -import os -import sys import math # External libraries import numpy as np from scipy.ndimage import uniform_filter from scipy.stats import percentileofscore + # load pygem config import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() -#%% FUNCTIONS + +# %% FUNCTIONS def annual_avg_2darray(x): """ Annual average of dataset, where columns are a monthly timeseries (temperature) """ - return x.reshape(-1,12).mean(1).reshape(x.shape[0],int(x.shape[1]/12)) + return x.reshape(-1, 12).mean(1).reshape(x.shape[0], int(x.shape[1] / 12)) def annual_sum_2darray(x): """ Annual sum of dataset, where columns are a monthly timeseries (precipitation) """ - return x.reshape(-1,12).sum(1).reshape(x.shape[0],int(x.shape[1]/12)) + return x.reshape(-1, 12).sum(1).reshape(x.shape[0], int(x.shape[1] / 12)) def monthly_avg_2darray(x): """ Monthly average for a given 2d dataset where columns are monthly timeseries """ - return x.reshape(-1,12).transpose().reshape(-1,int(x.shape[1]/12)).mean(1).reshape(12,-1).transpose() + return ( + x.reshape(-1, 12) + .transpose() + .reshape(-1, int(x.shape[1] / 12)) + .mean(1) + .reshape(12, -1) + .transpose() + ) def monthly_std_2darray(x): """ Monthly standard deviation for a given 2d dataset where columns are monthly timeseries """ - return x.reshape(-1,12).transpose().reshape(-1,int(x.shape[1]/12)).std(1).reshape(12,-1).transpose() + return ( + x.reshape(-1, 12) + .transpose() + .reshape(-1, int(x.shape[1] / 12)) + .std(1) + .reshape(12, -1) + .transpose() + ) + - -def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0, debug=False): +def temp_biasadj_HH2015( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, + debug=False, +): """ Huss and Hock (2015) temperature bias correction based on mean and interannual variability - + Note: the mean over the reference period will only equal the mean of the gcm for the same time period when the GCM time series is run for the same period, i.e., due to the 25-year moving average, the mean gcm temps from 2000-2019 will differ if using a reference period of 2000-2020 to bias adjust gcm temps from 2000-2100. - + Parameters ---------- ref_temp : np.array @@ -69,7 +95,7 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab dates table for reference time period dates_table : pd.DataFrame dates_table for GCM time period - + Returns ------- gcm_temp_biasadj : np.array @@ -78,88 +104,132 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab new gcm elevation is the elevation of the reference climate dataset """ # GCM subset to agree with reference time period to calculate bias corrections - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - gcm_subset_idx_end = np.where(dates_table.date.values == dates_table_ref.date.values[-1])[0][0] - gcm_temp_subset = gcm_temp[:,gcm_subset_idx_start:gcm_subset_idx_end+1] + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + gcm_subset_idx_end = np.where( + dates_table.date.values == dates_table_ref.date.values[-1] + )[0][0] + gcm_temp_subset = gcm_temp[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ] # Remove spinup years, so adjustment performed over calibration period - ref_temp_nospinup = ref_temp[:,ref_spinupyears*12:] - gcm_temp_nospinup = gcm_temp_subset[:,gcm_spinupyears*12:] - + ref_temp_nospinup = ref_temp[:, ref_spinupyears * 12 :] + gcm_temp_nospinup = gcm_temp_subset[:, gcm_spinupyears * 12 :] + # Roll months so they are aligned with simulation months - roll_amt = -1*(12 - gcm_subset_idx_start%12) + roll_amt = -1 * (12 - gcm_subset_idx_start % 12) if roll_amt == -12: - roll_amt = 0 - + roll_amt = 0 + # Mean monthly temperature - ref_temp_monthly_avg = np.roll(monthly_avg_2darray(ref_temp_nospinup), roll_amt, axis=1) - gcm_temp_monthly_avg = np.roll(monthly_avg_2darray(gcm_temp_nospinup), roll_amt, axis=1) + ref_temp_monthly_avg = np.roll( + monthly_avg_2darray(ref_temp_nospinup), roll_amt, axis=1 + ) + gcm_temp_monthly_avg = np.roll( + monthly_avg_2darray(gcm_temp_nospinup), roll_amt, axis=1 + ) # Standard deviation monthly temperature - ref_temp_monthly_std = np.roll(monthly_std_2darray(ref_temp_nospinup), roll_amt, axis=1) - gcm_temp_monthly_std = np.roll(monthly_std_2darray(gcm_temp_nospinup), roll_amt, axis=1) + ref_temp_monthly_std = np.roll( + monthly_std_2darray(ref_temp_nospinup), roll_amt, axis=1 + ) + gcm_temp_monthly_std = np.roll( + monthly_std_2darray(gcm_temp_nospinup), roll_amt, axis=1 + ) # Monthly bias adjustment (additive) gcm_temp_monthly_adj = ref_temp_monthly_avg - gcm_temp_monthly_avg # Monthly variability variability_monthly_std = ref_temp_monthly_std / gcm_temp_monthly_std - + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1981-2000) if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_temp = gcm_temp[:,sim_idx_start:] + bc_temp = gcm_temp[:, sim_idx_start:] # Monthly temperature bias adjusted according to monthly average # This is where the actual bias adjustment of temperature values occurs. # All steps before this are preliminary steps (e.g., formatting, # determining additive factor and std adjustment). - t_mt = bc_temp + np.tile(gcm_temp_monthly_adj, int(bc_temp.shape[1]/12)) - + t_mt = bc_temp + np.tile(gcm_temp_monthly_adj, int(bc_temp.shape[1] / 12)) + # Mean monthly temperature bias adjusted according to monthly average # t_m25avg is the avg monthly temp in a 25-year period around the given year N = 25 t_m_Navg = np.zeros(t_mt.shape) - for month in range(0,12): - t_m_subset = t_mt[:,month::12] + for month in range(0, 12): + t_m_subset = t_mt[:, month::12] # Uniform filter computes running average and uses 'reflects' values at borders - t_m_Navg_subset = uniform_filter(t_m_subset,size=(1,N)) - t_m_Navg[:,month::12] = t_m_Navg_subset + t_m_Navg_subset = uniform_filter(t_m_subset, size=(1, N)) + t_m_Navg[:, month::12] = t_m_Navg_subset + + gcm_temp_biasadj = t_m_Navg + (t_mt - t_m_Navg) * np.tile( + variability_monthly_std, int(bc_temp.shape[1] / 12) + ) - gcm_temp_biasadj = t_m_Navg + (t_mt - t_m_Navg) * np.tile(variability_monthly_std, int(bc_temp.shape[1]/12)) - # Update elevation gcm_elev_biasadj = ref_elev - + # Assert that mean temperatures for all the glaciers must be more-or-less equal - gcm_temp_biasadj_subset = ( - gcm_temp_biasadj[:,gcm_subset_idx_start:gcm_subset_idx_end+1][:,ref_spinupyears*12:]) + gcm_temp_biasadj_subset = gcm_temp_biasadj[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ][:, ref_spinupyears * 12 :] if gcm_startyear == ref_startyear: if debug: - print((np.mean(gcm_temp_biasadj_subset, axis=1) - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) - assert np.max(np.abs(np.mean(gcm_temp_biasadj_subset, axis=1) - - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) < 1, ( - 'Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree') + print( + ( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) + assert ( + np.max( + np.abs( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) + < 1 + ), ( + "Error with gcm temperature bias adjustment: mean ref and gcm temps differ by more than 1 degree" + ) else: if debug: - print((np.mean(gcm_temp_biasadj_subset, axis=1) - np.mean(ref_temp[:,ref_spinupyears*12:], axis=1))) - + print( + ( + np.mean(gcm_temp_biasadj_subset, axis=1) + - np.mean(ref_temp[:, ref_spinupyears * 12 :], axis=1) + ) + ) + return gcm_temp_biasadj, gcm_elev_biasadj -def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0): +def prec_biasadj_HH2015( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, +): """ Huss and Hock (2015) precipitation bias correction based on mean (multiplicative) - + Parameters ---------- ref_prec : np.array @@ -172,66 +242,98 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab dates_table for GCM time period gcm_elev_biasadj : float new gcm elevation is the elevation of the reference climate dataset - + Returns ------- gcm_prec_biasadj : np.array GCM precipitation bias corrected to the reference climate dataset according to Huss and Hock (2015) """ # GCM subset to agree with reference time period to calculate bias corrections - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - gcm_subset_idx_end = np.where(dates_table.date.values == dates_table_ref.date.values[-1])[0][0] - gcm_prec_subset = gcm_prec[:,gcm_subset_idx_start:gcm_subset_idx_end+1] - + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + gcm_subset_idx_end = np.where( + dates_table.date.values == dates_table_ref.date.values[-1] + )[0][0] + gcm_prec_subset = gcm_prec[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ] + # Remove spinup years, so adjustment performed over calibration period - ref_prec_nospinup = ref_prec[:,ref_spinupyears*12:] - gcm_prec_nospinup = gcm_prec_subset[:,gcm_spinupyears*12:] - + ref_prec_nospinup = ref_prec[:, ref_spinupyears * 12 :] + gcm_prec_nospinup = gcm_prec_subset[:, gcm_spinupyears * 12 :] + # Roll months so they are aligned with simulation months - roll_amt = -1*(12 - gcm_subset_idx_start%12) - + roll_amt = -1 * (12 - gcm_subset_idx_start % 12) + # PRECIPITATION BIAS CORRECTIONS # Monthly mean precipitation - ref_prec_monthly_avg = np.roll(monthly_avg_2darray(ref_prec_nospinup), roll_amt, axis=1) - gcm_prec_monthly_avg = np.roll(monthly_avg_2darray(gcm_prec_nospinup), roll_amt, axis=1) + ref_prec_monthly_avg = np.roll( + monthly_avg_2darray(ref_prec_nospinup), roll_amt, axis=1 + ) + gcm_prec_monthly_avg = np.roll( + monthly_avg_2darray(gcm_prec_nospinup), roll_amt, axis=1 + ) bias_adj_prec_monthly = ref_prec_monthly_avg / gcm_prec_monthly_avg - - # if/else statement for whether or not the full GCM period is the same as the simulation period + + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1985-2000) if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] - + bc_prec = gcm_prec[:, sim_idx_start:] + # Bias adjusted precipitation accounting for differences in monthly mean - gcm_prec_biasadj = bc_prec * np.tile(bias_adj_prec_monthly, int(bc_prec.shape[1]/12)) - + gcm_prec_biasadj = bc_prec * np.tile( + bias_adj_prec_monthly, int(bc_prec.shape[1] / 12) + ) + # Update elevation gcm_elev_biasadj = ref_elev - + # Assertion that bias adjustment does not drastically modify the precipitation and are reasonable - gcm_prec_biasadj_subset = ( - gcm_prec_biasadj[:,gcm_subset_idx_start:gcm_subset_idx_end+1][:,gcm_spinupyears*12:]) - gcm_prec_biasadj_frac = gcm_prec_biasadj_subset.sum(axis=1) / ref_prec_nospinup.sum(axis=1) - assert np.min(gcm_prec_biasadj_frac) > 0.5 and np.max(gcm_prec_biasadj_frac) < 2, ( - 'Error with gcm precipitation bias adjustment: total ref and gcm prec differ by more than factor of 2') - assert gcm_prec_biasadj.max() <= 10, 'gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified' - assert gcm_prec_biasadj.min() >= 0, 'gcm_prec_adj is producing a negative precipitation value' - + gcm_prec_biasadj_subset = gcm_prec_biasadj[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ][:, gcm_spinupyears * 12 :] + gcm_prec_biasadj_frac = gcm_prec_biasadj_subset.sum( + axis=1 + ) / ref_prec_nospinup.sum(axis=1) + assert ( + np.min(gcm_prec_biasadj_frac) > 0.5 + and np.max(gcm_prec_biasadj_frac) < 2 + ), ( + "Error with gcm precipitation bias adjustment: total ref and gcm prec differ by more than factor of 2" + ) + assert gcm_prec_biasadj.max() <= 10, ( + "gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified" + ) + assert gcm_prec_biasadj.min() >= 0, ( + "gcm_prec_adj is producing a negative precipitation value" + ) + return gcm_prec_biasadj, gcm_elev_biasadj -def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0): +def prec_biasadj_opt1( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, +): """ Precipitation bias correction based on mean with limited maximum - + Parameters ---------- ref_prec : np.array @@ -242,7 +344,7 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table dates table for reference time period dates_table : pd.DataFrame dates_table for GCM time period - + Returns ------- gcm_prec_biasadj : np.array @@ -251,96 +353,155 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table new gcm elevation is the elevation of the reference climate dataset """ # GCM subset to agree with reference time period to calculate bias corrections - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - gcm_subset_idx_end = np.where(dates_table.date.values == dates_table_ref.date.values[-1])[0][0] - gcm_prec_subset = gcm_prec[:,gcm_subset_idx_start:gcm_subset_idx_end+1] - + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + gcm_subset_idx_end = np.where( + dates_table.date.values == dates_table_ref.date.values[-1] + )[0][0] + gcm_prec_subset = gcm_prec[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ] + # Remove spinup years, so adjustment performed over calibration period - ref_prec_nospinup = ref_prec[:,ref_spinupyears*12:] - gcm_prec_nospinup = gcm_prec_subset[:,gcm_spinupyears*12:] - + ref_prec_nospinup = ref_prec[:, ref_spinupyears * 12 :] + gcm_prec_nospinup = gcm_prec_subset[:, gcm_spinupyears * 12 :] + # Roll months so they are aligned with simulation months - roll_amt = -1*(12 - gcm_subset_idx_start%12) - + roll_amt = -1 * (12 - gcm_subset_idx_start % 12) + # PRECIPITATION BIAS CORRECTIONS # Monthly mean precipitation - ref_prec_monthly_avg = np.roll(monthly_avg_2darray(ref_prec_nospinup), roll_amt, axis=1) - gcm_prec_monthly_avg = np.roll(monthly_avg_2darray(gcm_prec_nospinup), roll_amt, axis=1) + ref_prec_monthly_avg = np.roll( + monthly_avg_2darray(ref_prec_nospinup), roll_amt, axis=1 + ) + gcm_prec_monthly_avg = np.roll( + monthly_avg_2darray(gcm_prec_nospinup), roll_amt, axis=1 + ) bias_adj_prec_monthly = ref_prec_monthly_avg / gcm_prec_monthly_avg - - # if/else statement for whether or not the full GCM period is the same as the simulation period + + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1985-2000) if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] - + bc_prec = gcm_prec[:, sim_idx_start:] + # Bias adjusted precipitation accounting for differences in monthly mean - gcm_prec_biasadj_raw = bc_prec * np.tile(bias_adj_prec_monthly, int(bc_prec.shape[1]/12)) - + gcm_prec_biasadj_raw = bc_prec * np.tile( + bias_adj_prec_monthly, int(bc_prec.shape[1] / 12) + ) + # Adjust variance based on zscore and reference standard deviation - ref_prec_monthly_std = np.roll(monthly_std_2darray(ref_prec_nospinup), roll_amt, axis=1) - gcm_prec_biasadj_raw_monthly_avg = monthly_avg_2darray(gcm_prec_biasadj_raw[:,0:ref_prec.shape[1]]) - gcm_prec_biasadj_raw_monthly_std = monthly_std_2darray(gcm_prec_biasadj_raw[:,0:ref_prec.shape[1]]) + ref_prec_monthly_std = np.roll( + monthly_std_2darray(ref_prec_nospinup), roll_amt, axis=1 + ) + gcm_prec_biasadj_raw_monthly_avg = monthly_avg_2darray( + gcm_prec_biasadj_raw[:, 0 : ref_prec.shape[1]] + ) + gcm_prec_biasadj_raw_monthly_std = monthly_std_2darray( + gcm_prec_biasadj_raw[:, 0 : ref_prec.shape[1]] + ) # Calculate value compared to mean and standard deviation gcm_prec_biasadj_zscore = ( - (gcm_prec_biasadj_raw - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1]/12))) / - np.tile(gcm_prec_biasadj_raw_monthly_std, int(bc_prec.shape[1]/12))) - gcm_prec_biasadj = ( - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1]/12)) + - gcm_prec_biasadj_zscore * np.tile(ref_prec_monthly_std, int(bc_prec.shape[1]/12))) + gcm_prec_biasadj_raw + - np.tile(gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1] / 12)) + ) / np.tile(gcm_prec_biasadj_raw_monthly_std, int(bc_prec.shape[1] / 12)) + gcm_prec_biasadj = np.tile( + gcm_prec_biasadj_raw_monthly_avg, int(bc_prec.shape[1] / 12) + ) + gcm_prec_biasadj_zscore * np.tile( + ref_prec_monthly_std, int(bc_prec.shape[1] / 12) + ) gcm_prec_biasadj[gcm_prec_biasadj < 0] = 0 - + # Identify outliers using reference's monthly maximum adjusted for future increases - ref_prec_monthly_max = np.roll((ref_prec_nospinup.reshape(-1,12).transpose() - .reshape(-1,int(ref_prec_nospinup.shape[1]/12)).max(1).reshape(12,-1).transpose()), - roll_amt, axis=1) - gcm_prec_max_check = np.tile(ref_prec_monthly_max, int(gcm_prec_biasadj.shape[1]/12)) + ref_prec_monthly_max = np.roll( + ( + ref_prec_nospinup.reshape(-1, 12) + .transpose() + .reshape(-1, int(ref_prec_nospinup.shape[1] / 12)) + .max(1) + .reshape(12, -1) + .transpose() + ), + roll_amt, + axis=1, + ) + gcm_prec_max_check = np.tile( + ref_prec_monthly_max, int(gcm_prec_biasadj.shape[1] / 12) + ) # For wetter years in future, adjust monthly max by the annual increase in precipitation gcm_prec_annual = annual_sum_2darray(bc_prec) - gcm_prec_annual_norm = gcm_prec_annual / gcm_prec_annual.mean(1)[:,np.newaxis] - gcm_prec_annual_norm_repeated = np.repeat(gcm_prec_annual_norm, 12).reshape(gcm_prec_biasadj.shape) + gcm_prec_annual_norm = ( + gcm_prec_annual / gcm_prec_annual.mean(1)[:, np.newaxis] + ) + gcm_prec_annual_norm_repeated = np.repeat( + gcm_prec_annual_norm, 12 + ).reshape(gcm_prec_biasadj.shape) gcm_prec_max_check_adj = gcm_prec_max_check * gcm_prec_annual_norm_repeated gcm_prec_max_check_adj[gcm_prec_max_check_adj < gcm_prec_max_check] = ( - gcm_prec_max_check[gcm_prec_max_check_adj < gcm_prec_max_check]) - + gcm_prec_max_check[gcm_prec_max_check_adj < gcm_prec_max_check] + ) + # Replace outliers with monthly mean adjusted for the normalized annual variation - outlier_replacement = (gcm_prec_annual_norm_repeated * - np.tile(ref_prec_monthly_avg, int(gcm_prec_biasadj.shape[1]/12))) + outlier_replacement = gcm_prec_annual_norm_repeated * np.tile( + ref_prec_monthly_avg, int(gcm_prec_biasadj.shape[1] / 12) + ) gcm_prec_biasadj[gcm_prec_biasadj > gcm_prec_max_check_adj] = ( - outlier_replacement[gcm_prec_biasadj > gcm_prec_max_check_adj]) - + outlier_replacement[gcm_prec_biasadj > gcm_prec_max_check_adj] + ) + # Update elevation gcm_elev_biasadj = ref_elev - + # Assertion that bias adjustment does not drastically modify the precipitation and are reasonable - gcm_prec_biasadj_subset = ( - gcm_prec_biasadj[:,gcm_subset_idx_start:gcm_subset_idx_end+1][:,gcm_spinupyears*12:]) - gcm_prec_biasadj_frac = gcm_prec_biasadj_subset.sum(axis=1) / ref_prec_nospinup.sum(axis=1) - assert np.min(gcm_prec_biasadj_frac) > 0.5 and np.max(gcm_prec_biasadj_frac) < 2, ( - 'Error with gcm precipitation bias adjustment: total ref and gcm prec differ by more than factor of 2') - assert gcm_prec_biasadj.max() <= 10, 'gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified' - assert gcm_prec_biasadj.min() >= 0, 'gcm_prec_adj is producing a negative precipitation value' - + gcm_prec_biasadj_subset = gcm_prec_biasadj[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ][:, gcm_spinupyears * 12 :] + gcm_prec_biasadj_frac = gcm_prec_biasadj_subset.sum( + axis=1 + ) / ref_prec_nospinup.sum(axis=1) + assert ( + np.min(gcm_prec_biasadj_frac) > 0.5 + and np.max(gcm_prec_biasadj_frac) < 2 + ), ( + "Error with gcm precipitation bias adjustment: total ref and gcm prec differ by more than factor of 2" + ) + assert gcm_prec_biasadj.max() <= 10, ( + "gcm_prec_adj (precipitation bias adjustment) too high, needs to be modified" + ) + assert gcm_prec_biasadj.min() >= 0, ( + "gcm_prec_adj is producing a negative precipitation value" + ) + return gcm_prec_biasadj, gcm_elev_biasadj - -def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0): + +def temp_biasadj_QDM( + ref_temp, + ref_elev, + gcm_temp, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, +): """ Cannon et al. (2015) temperature bias correction based on quantile delta mapping Also see Lader et al. (2017) for further documentation - + Perform a quantile delta mapping bias-correction procedure on temperature. - + This function operates by multiplying reference temperature by a ratio of - the projected and future gcm temperature at the same percentiles + the projected and future gcm temperature at the same percentiles (e.g., ref_temp * gcm_projected/gcm_historic, with all values at same percentile). Quantile delta mapping is generally viewed as more capable of capturing climatic extemes at the lowest and highest quantiles (e.g., 0.01% and 99.9%) @@ -348,7 +509,7 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, using only reference and historic climate data, requiring extrapolations for projected values lying outside the reference and historic datasets). See Cannon et al. (2015) Sections 2 and 3 for further explanation. - + Parameters ---------- ref_temp : pandas dataframe @@ -358,7 +519,7 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, dates_table_ref : pd.DataFrame dates table for reference time period dates_table : pd.DataFrame - dates_table for GCM time period + dates_table for GCM time period Returns ------- @@ -368,84 +529,107 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, new gcm elevation is the elevation of the reference climate dataset """ # GCM historic subset to agree with reference time period to enable QDM bias correction - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - gcm_subset_idx_end = np.where(dates_table.date.values == dates_table_ref.date.values[-1])[0][0] - gcm_temp_historic = gcm_temp[:,gcm_subset_idx_start:gcm_subset_idx_end+1] + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + gcm_subset_idx_end = np.where( + dates_table.date.values == dates_table_ref.date.values[-1] + )[0][0] + gcm_temp_historic = gcm_temp[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ] # Remove spinup years, so adjustment performed over calibration period - ref_temp_nospinup = ref_temp[:,ref_spinupyears*12:] + 273.15 - gcm_temp_nospinup = gcm_temp_historic[:,gcm_spinupyears*12:] + 273.15 - + ref_temp_nospinup = ref_temp[:, ref_spinupyears * 12 :] + 273.15 + gcm_temp_nospinup = gcm_temp_historic[:, gcm_spinupyears * 12 :] + 273.15 + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1981-2000) if gcm_startyear == ref_startyear: bc_temp = gcm_temp else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_temp = gcm_temp[:,sim_idx_start:] - + bc_temp = gcm_temp[:, sim_idx_start:] + # create an empty array for the bias-corrected GCM data # gcm_temp_biasadj = np.zeros(bc_temp.size) - loop_years = 20 # number of years used for each bias-correction period - loop_months = loop_years * 12 # number of months used for each bias-correction period - + loop_years = 20 # number of years used for each bias-correction period + loop_months = ( + loop_years * 12 + ) # number of months used for each bias-correction period + # convert to Kelvin to better handle Celsius values around 0) bc_temp = bc_temp + 273.15 # bc_temp = bc_temp[0] - all_gcm_temp_biasadj =[] # empty list for all glaciers - + all_gcm_temp_biasadj = [] # empty list for all glaciers + for j in range(0, len(bc_temp)): - gcm_temp_biasadj = [] # empty list for bias-corrected data - bc_loops = len(bc_temp[j])/loop_months # determine number of loops needed for bias-correction - + gcm_temp_biasadj = [] # empty list for bias-corrected data + bc_loops = ( + len(bc_temp[j]) / loop_months + ) # determine number of loops needed for bias-correction + # loop through however many times are required to bias-correct the entire time period # using smaller time periods (typically 20-30 years) to better capture the # quantiles and extremes at different points in the future - for i in range(0, math.ceil(bc_loops)): - bc_temp_loop = bc_temp[j][i*loop_months:(i+1)*loop_months] + for i in range(0, math.ceil(bc_loops)): + bc_temp_loop = bc_temp[j][i * loop_months : (i + 1) * loop_months] bc_temp_loop_corrected = np.zeros(bc_temp_loop.size) - - # now loop through each individual value within the time period for bias correction + + # now loop through each individual value within the time period for bias correction for ival, projected_value in enumerate(bc_temp_loop): - percentile = percentileofscore(bc_temp_loop, projected_value) - bias_correction_factor = np.percentile(ref_temp_nospinup, percentile)/np.percentile(gcm_temp_nospinup, percentile) - bc_temp_loop_corrected[ival] = projected_value * bias_correction_factor - # append the values from each time period to a list + percentile = percentileofscore(bc_temp_loop, projected_value) + bias_correction_factor = np.percentile( + ref_temp_nospinup, percentile + ) / np.percentile(gcm_temp_nospinup, percentile) + bc_temp_loop_corrected[ival] = ( + projected_value * bias_correction_factor + ) + # append the values from each time period to a list gcm_temp_biasadj.append(bc_temp_loop_corrected) gcm_temp_biasadj = np.concatenate(gcm_temp_biasadj, axis=0) # convert back to Celsius for simulation gcm_temp_biasadj = gcm_temp_biasadj - 273.15 # gcm_temp_biasadj = np.array([gcm_temp_biasadj.tolist()]) - all_gcm_temp_biasadj.append(gcm_temp_biasadj) + all_gcm_temp_biasadj.append(gcm_temp_biasadj) # print(all_gcm_temp_biasadj) - + gcm_temp_biasadj = np.array(all_gcm_temp_biasadj) # print(gcm_temp_biasadj[0]) # print(gcm_temp_biasadj[1]) # print(gcm_temp_biasadj) - + # Update elevation gcm_elev_biasadj = ref_elev - + return gcm_temp_biasadj, gcm_elev_biasadj - -def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, gcm_startyear, ref_startyear, - ref_spinupyears=0, gcm_spinupyears=0): + +def prec_biasadj_QDM( + ref_prec, + ref_elev, + gcm_prec, + dates_table_ref, + dates_table, + gcm_startyear, + ref_startyear, + ref_spinupyears=0, + gcm_spinupyears=0, +): """ Cannon et al. (2015) precipitation bias correction based on quantile delta mapping Also see Lader et al. (2017) another use case - + Perform a quantile delta mapping bias-correction procedure on precipitation. - + This function operates by multiplying reference precipitation by a ratio of - the projected and future gcm precipitations at the same percentiles + the projected and future gcm precipitations at the same percentiles (e.g., ref_prec * gcm_projected/gcm_historic, with all values at same percentile). Quantile delta mapping is generally viewed as more capable of capturing climatic extemes at the lowest and highest quantiles (e.g., 0.01% and 99.9%) @@ -453,7 +637,7 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, using only reference and historic climate data, requiring extrapolations for projected values lying outside the reference and historic datasets). See Cannon et al. (2015) Sections 2 and 3 for further explanation. - + Parameters ---------- ref_prec : pandas dataframe @@ -463,7 +647,7 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, dates_table_ref : pd.DataFrame dates table for reference time period dates_table : pd.DataFrame - dates_table for GCM time period + dates_table for GCM time period Returns ------- @@ -472,71 +656,87 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, gcm_elev_biasadj : float new gcm elevation is the elevation of the reference climate dataset """ - + # GCM historic subset to agree with reference time period to enable QDM bias correction - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - gcm_subset_idx_end = np.where(dates_table.date.values == dates_table_ref.date.values[-1])[0][0] - gcm_prec_historic = gcm_prec[:,gcm_subset_idx_start:gcm_subset_idx_end+1] + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + gcm_subset_idx_end = np.where( + dates_table.date.values == dates_table_ref.date.values[-1] + )[0][0] + gcm_prec_historic = gcm_prec[ + :, gcm_subset_idx_start : gcm_subset_idx_end + 1 + ] # Remove spinup years, so adjustment performed over calibration period - ref_prec_nospinup = ref_prec[:,ref_spinupyears*12:] - gcm_prec_nospinup = gcm_prec_historic[:,gcm_spinupyears*12:] - + ref_prec_nospinup = ref_prec[:, ref_spinupyears * 12 :] + gcm_prec_nospinup = gcm_prec_historic[:, gcm_spinupyears * 12 :] + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1981-2000) if gcm_startyear == ref_startyear: bc_prec = gcm_prec else: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - bc_prec = gcm_prec[:,sim_idx_start:] - + bc_prec = gcm_prec[:, sim_idx_start:] + # create an empty array for the bias-corrected GCM data # gcm_prec_biasadj = np.zeros(bc_prec.size) - loop_years = 20 # number of years used for each bias-correction period - loop_months = loop_years * 12 # number of months used for each bias-correction period - + loop_years = 20 # number of years used for each bias-correction period + loop_months = ( + loop_years * 12 + ) # number of months used for each bias-correction period + # bc_prec = bc_prec[0] - all_gcm_prec_biasadj =[] # empty list for all glaciers - + all_gcm_prec_biasadj = [] # empty list for all glaciers + for j in range(0, len(bc_prec)): - gcm_prec_biasadj = [] # empty list for bias-corrected data - bc_loops = len(bc_prec[j])/loop_months # determine number of loops needed for bias-correction - + gcm_prec_biasadj = [] # empty list for bias-corrected data + bc_loops = ( + len(bc_prec[j]) / loop_months + ) # determine number of loops needed for bias-correction + # loop through however many times are required to bias-correct the entire time period # using smaller time periods (typically 20-30 years) to better capture the # quantiles and extremes at different points in the future for i in range(0, math.ceil(bc_loops)): - bc_prec_loop = bc_prec[j][i*loop_months:(i+1)*loop_months] + bc_prec_loop = bc_prec[j][i * loop_months : (i + 1) * loop_months] bc_prec_loop_corrected = np.zeros(bc_prec_loop.size) - + # now loop through each individual value within the time period for bias correction for ival, projected_value in enumerate(bc_prec_loop): - percentile = percentileofscore(bc_prec_loop, projected_value) - bias_correction_factor = np.percentile(ref_prec_nospinup, percentile)/np.percentile(gcm_prec_nospinup, percentile) - bc_prec_loop_corrected[ival] = projected_value * bias_correction_factor + percentile = percentileofscore(bc_prec_loop, projected_value) + bias_correction_factor = np.percentile( + ref_prec_nospinup, percentile + ) / np.percentile(gcm_prec_nospinup, percentile) + bc_prec_loop_corrected[ival] = ( + projected_value * bias_correction_factor + ) # append the values from each time period to a list gcm_prec_biasadj.append(bc_prec_loop_corrected) - + gcm_prec_biasadj = np.concatenate(gcm_prec_biasadj, axis=0) # gcm_prec_biasadj = np.array([gcm_prec_biasadj.tolist()]) all_gcm_prec_biasadj.append(gcm_prec_biasadj) - + gcm_prec_biasadj = np.array(all_gcm_prec_biasadj) - + # Update elevation gcm_elev_biasadj = ref_elev - - return gcm_prec_biasadj, gcm_elev_biasadj - -def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_startyear, ref_startyear): - """ Monthly average array from reference data rolled to ensure proper months - + return gcm_prec_biasadj, gcm_elev_biasadj + + +def monthly_avg_array_rolled( + ref_array, dates_table_ref, dates_table, gcm_startyear, ref_startyear +): + """Monthly average array from reference data rolled to ensure proper months + Parameters ---------- ref_array : np.array @@ -545,29 +745,33 @@ def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_starty dates table for reference time period dates_table : pd.DataFrame dates_table for GCM time period - + Returns ------- gcm_array : np.array gcm climate data based on monthly average of reference data """ # GCM subset to agree with reference time period to calculate bias corrections - gcm_subset_idx_start = np.where(dates_table.date.values == dates_table_ref.date.values[0])[0][0] - + gcm_subset_idx_start = np.where( + dates_table.date.values == dates_table_ref.date.values[0] + )[0][0] + # Roll months so they are aligned with simulation months - roll_amt = -1*(12 - gcm_subset_idx_start%12) - ref_array_monthly_avg = np.roll(monthly_avg_2darray(ref_array), roll_amt, axis=1) - gcm_array = np.tile(ref_array_monthly_avg, int(dates_table.shape[0]/12)) - + roll_amt = -1 * (12 - gcm_subset_idx_start % 12) + ref_array_monthly_avg = np.roll( + monthly_avg_2darray(ref_array), roll_amt, axis=1 + ) + gcm_array = np.tile(ref_array_monthly_avg, int(dates_table.shape[0] / 12)) + # if/else statement for whether or not the full GCM period is the same as the simulation period # create GCM subset for applying bias-correction (e.g., 2000-2100), # that does not include the earlier reference years (e.g., 1981-2000) if gcm_startyear != ref_startyear: - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - dates_cn = 'wateryear' + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + dates_cn = "wateryear" else: - dates_cn = 'year' + dates_cn = "year" sim_idx_start = dates_table[dates_cn].to_list().index(gcm_startyear) - gcm_array = gcm_array[:,sim_idx_start:] - - return gcm_array \ No newline at end of file + gcm_array = gcm_array[:, sim_idx_start:] + + return gcm_array diff --git a/pygem/glacierdynamics.py b/pygem/glacierdynamics.py index 6b6b024f..2f80dc82 100755 --- a/pygem/glacierdynamics.py +++ b/pygem/glacierdynamics.py @@ -5,43 +5,54 @@ Distrubted under the MIT lisence """ + from collections import OrderedDict from time import gmtime, strftime import numpy as np -#import pandas as pd -#import netCDF4 -import xarray as xr -from oggm import cfg, utils +# import pandas as pd +# import netCDF4 +import xarray as xr +from oggm import __version__, cfg, utils from oggm.core.flowline import FlowlineModel from oggm.exceptions import InvalidParamsError -from oggm import __version__ + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file cfg.initialize() -#%% +# %% class MassRedistributionCurveModel(FlowlineModel): """Glacier geometry updated using mass redistribution curves; also known as the "delta-h method" This uses mass redistribution curves from Huss et al. (2010) to update the glacier geometry """ - def __init__(self, flowlines, mb_model=None, y0=0., - glen_a=None, fs=0., is_tidewater=False, water_level=0, -# calving_k=0, - inplace=False, - debug=True, - option_areaconstant=False, spinupyears=0, - constantarea_years=0, - **kwargs): - """ Instanciate the model. - + def __init__( + self, + flowlines, + mb_model=None, + y0=0.0, + glen_a=None, + fs=0.0, + is_tidewater=False, + water_level=0, + # calving_k=0, + inplace=False, + debug=True, + option_areaconstant=False, + spinupyears=0, + constantarea_years=0, + **kwargs, + ): + """Instanciate the model. + Parameters ---------- flowlines : list @@ -65,8 +76,14 @@ def __init__(self, flowlines, mb_model=None, y0=0., raise an error when the glacier grows bigger than the domain boundaries """ - super(MassRedistributionCurveModel, self).__init__(flowlines, mb_model=mb_model, y0=y0, inplace=inplace, - mb_elev_feedback='annual', **kwargs) + super(MassRedistributionCurveModel, self).__init__( + flowlines, + mb_model=mb_model, + y0=y0, + inplace=inplace, + mb_elev_feedback="annual", + **kwargs, + ) self.option_areaconstant = option_areaconstant self.constantarea_years = constantarea_years self.spinupyears = spinupyears @@ -75,21 +92,22 @@ def __init__(self, flowlines, mb_model=None, y0=0., self.is_tidewater = is_tidewater self.water_level = water_level -# widths_t0 = flowlines[0].widths_m -# area_v1 = widths_t0 * flowlines[0].dx_meter -# print('area v1:', area_v1.sum()) -# area_v2 = np.copy(area_v1) -# area_v2[flowlines[0].thick == 0] = 0 -# print('area v2:', area_v2.sum()) - + # widths_t0 = flowlines[0].widths_m + # area_v1 = widths_t0 * flowlines[0].dx_meter + # print('area v1:', area_v1.sum()) + # area_v2 = np.copy(area_v1) + # area_v2[flowlines[0].thick == 0] = 0 + # print('area v2:', area_v2.sum()) + # HERE IS THE STUFF TO RECORD FOR EACH FLOWLINE! if self.is_tidewater: - self.calving_k = cfg.PARAMS['calving_k'] - self.calving_m3_since_y0 = 0. # total calving since time y0 - - assert len(flowlines) == 1, 'MassRedistributionCurveModel is not set up for multiple flowlines' - - + self.calving_k = cfg.PARAMS["calving_k"] + self.calving_m3_since_y0 = 0.0 # total calving since time y0 + + assert len(flowlines) == 1, ( + "MassRedistributionCurveModel is not set up for multiple flowlines" + ) + def run_until(self, y1, run_single_year=False): """Runs the model from the current year up to a given year date y1. @@ -101,8 +119,8 @@ def run_until(self, y1, run_single_year=False): ---------- y1 : float Upper time span for how long the model should run - """ - + """ + # We force timesteps to yearly timesteps if run_single_year: self.updategeometry(y1) @@ -110,21 +128,23 @@ def run_until(self, y1, run_single_year=False): years = np.arange(self.yr, y1) for year in years: self.updategeometry(year) - + # Check for domain bounds if self.check_for_boundaries: if self.fls[-1].thick[-1] > 10: - raise RuntimeError('Glacier exceeds domain boundaries, ' - 'at year: {}'.format(self.yr)) + raise RuntimeError( + "Glacier exceeds domain boundaries, at year: {}".format( + self.yr + ) + ) # Check for NaNs for fl in self.fls: if np.any(~np.isfinite(fl.thick)): - raise FloatingPointError('NaN in numerical solution.') - - + raise FloatingPointError("NaN in numerical solution.") - def run_until_and_store(self, y1, run_path=None, diag_path=None, - store_monthly_step=None): + def run_until_and_store( + self, y1, run_path=None, diag_path=None, store_monthly_step=None + ): """Runs the model and returns intermediate steps in xarray datasets. This function repeatedly calls FlowlineModel.run_until for either @@ -157,29 +177,33 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, """ if int(y1) != y1: - raise InvalidParamsError('run_until_and_store only accepts ' - 'integer year dates.') + raise InvalidParamsError( + "run_until_and_store only accepts integer year dates." + ) if not self.mb_model.hemisphere: - raise InvalidParamsError('run_until_and_store needs a ' - 'mass-balance model with an unambiguous ' - 'hemisphere.') + raise InvalidParamsError( + "run_until_and_store needs a " + "mass-balance model with an unambiguous " + "hemisphere." + ) # time - yearly_time = np.arange(np.floor(self.yr), np.floor(y1)+1) + yearly_time = np.arange(np.floor(self.yr), np.floor(y1) + 1) if store_monthly_step is None: - store_monthly_step = self.mb_step == 'monthly' + store_monthly_step = self.mb_step == "monthly" if store_monthly_step: monthly_time = utils.monthly_timeseries(self.yr, y1) else: - monthly_time = np.arange(np.floor(self.yr), np.floor(y1)+1) + monthly_time = np.arange(np.floor(self.yr), np.floor(y1) + 1) - sm = cfg.PARAMS['hydro_month_' + self.mb_model.hemisphere] + sm = cfg.PARAMS["hydro_month_" + self.mb_model.hemisphere] yrs, months = utils.floatyear_to_date(monthly_time) - cyrs, cmonths = utils.hydrodate_to_calendardate(yrs, months, - start_month=sm) + cyrs, cmonths = utils.hydrodate_to_calendardate( + yrs, months, start_month=sm + ) # init output if run_path is not None: @@ -197,74 +221,75 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, diag_ds = xr.Dataset() # Global attributes - diag_ds.attrs['description'] = 'OGGM model output' - diag_ds.attrs['oggm_version'] = __version__ - diag_ds.attrs['calendar'] = '365-day no leap' - diag_ds.attrs['creation_date'] = strftime("%Y-%m-%d %H:%M:%S", - gmtime()) - diag_ds.attrs['hemisphere'] = self.mb_model.hemisphere - diag_ds.attrs['water_level'] = self.water_level + diag_ds.attrs["description"] = "OGGM model output" + diag_ds.attrs["oggm_version"] = __version__ + diag_ds.attrs["calendar"] = "365-day no leap" + diag_ds.attrs["creation_date"] = strftime( + "%Y-%m-%d %H:%M:%S", gmtime() + ) + diag_ds.attrs["hemisphere"] = self.mb_model.hemisphere + diag_ds.attrs["water_level"] = self.water_level # Coordinates - diag_ds.coords['time'] = ('time', monthly_time) - diag_ds.coords['hydro_year'] = ('time', yrs) - diag_ds.coords['hydro_month'] = ('time', months) - diag_ds.coords['calendar_year'] = ('time', cyrs) - diag_ds.coords['calendar_month'] = ('time', cmonths) - - diag_ds['time'].attrs['description'] = 'Floating hydrological year' - diag_ds['hydro_year'].attrs['description'] = 'Hydrological year' - diag_ds['hydro_month'].attrs['description'] = 'Hydrological month' - diag_ds['calendar_year'].attrs['description'] = 'Calendar year' - diag_ds['calendar_month'].attrs['description'] = 'Calendar month' + diag_ds.coords["time"] = ("time", monthly_time) + diag_ds.coords["hydro_year"] = ("time", yrs) + diag_ds.coords["hydro_month"] = ("time", months) + diag_ds.coords["calendar_year"] = ("time", cyrs) + diag_ds.coords["calendar_month"] = ("time", cmonths) + + diag_ds["time"].attrs["description"] = "Floating hydrological year" + diag_ds["hydro_year"].attrs["description"] = "Hydrological year" + diag_ds["hydro_month"].attrs["description"] = "Hydrological month" + diag_ds["calendar_year"].attrs["description"] = "Calendar year" + diag_ds["calendar_month"].attrs["description"] = "Calendar month" # Variables and attributes - diag_ds['volume_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_m3'].attrs['description'] = 'Total glacier volume' - diag_ds['volume_m3'].attrs['unit'] = 'm 3' - diag_ds['volume_bsl_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_bsl_m3'].attrs['description'] = ('Glacier volume ' - 'below ' - 'sea-level') - diag_ds['volume_bsl_m3'].attrs['unit'] = 'm 3' - diag_ds['volume_bwl_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['volume_bwl_m3'].attrs['description'] = ('Glacier volume ' - 'below ') - diag_ds['volume_bwl_m3'].attrs['unit'] = 'm 3' - - diag_ds['area_m2'] = ('time', np.zeros(nm) * np.nan) - diag_ds['area_m2'].attrs['description'] = 'Total glacier area' - diag_ds['area_m2'].attrs['unit'] = 'm 2' - diag_ds['length_m'] = ('time', np.zeros(nm) * np.nan) - diag_ds['length_m'].attrs['description'] = 'Glacier length' - diag_ds['length_m'].attrs['unit'] = 'm 3' - diag_ds['ela_m'] = ('time', np.zeros(nm) * np.nan) - diag_ds['ela_m'].attrs['description'] = ('Annual Equilibrium Line ' - 'Altitude (ELA)') - diag_ds['ela_m'].attrs['unit'] = 'm a.s.l' + diag_ds["volume_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_m3"].attrs["description"] = "Total glacier volume" + diag_ds["volume_m3"].attrs["unit"] = "m 3" + diag_ds["volume_bsl_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_bsl_m3"].attrs["description"] = ( + "Glacier volume below sea-level" + ) + diag_ds["volume_bsl_m3"].attrs["unit"] = "m 3" + diag_ds["volume_bwl_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["volume_bwl_m3"].attrs["description"] = "Glacier volume below " + diag_ds["volume_bwl_m3"].attrs["unit"] = "m 3" + + diag_ds["area_m2"] = ("time", np.zeros(nm) * np.nan) + diag_ds["area_m2"].attrs["description"] = "Total glacier area" + diag_ds["area_m2"].attrs["unit"] = "m 2" + diag_ds["length_m"] = ("time", np.zeros(nm) * np.nan) + diag_ds["length_m"].attrs["description"] = "Glacier length" + diag_ds["length_m"].attrs["unit"] = "m 3" + diag_ds["ela_m"] = ("time", np.zeros(nm) * np.nan) + diag_ds["ela_m"].attrs["description"] = ( + "Annual Equilibrium Line Altitude (ELA)" + ) + diag_ds["ela_m"].attrs["unit"] = "m a.s.l" if self.is_tidewater: - diag_ds['calving_m3'] = ('time', np.zeros(nm) * np.nan) - diag_ds['calving_m3'].attrs['description'] = ('Total accumulated ' - 'calving flux') - diag_ds['calving_m3'].attrs['unit'] = 'm 3' - diag_ds['calving_rate_myr'] = ('time', np.zeros(nm) * np.nan) - diag_ds['calving_rate_myr'].attrs['description'] = 'Calving rate' - diag_ds['calving_rate_myr'].attrs['unit'] = 'm yr-1' + diag_ds["calving_m3"] = ("time", np.zeros(nm) * np.nan) + diag_ds["calving_m3"].attrs["description"] = ( + "Total accumulated calving flux" + ) + diag_ds["calving_m3"].attrs["unit"] = "m 3" + diag_ds["calving_rate_myr"] = ("time", np.zeros(nm) * np.nan) + diag_ds["calving_rate_myr"].attrs["description"] = "Calving rate" + diag_ds["calving_rate_myr"].attrs["unit"] = "m yr-1" # Run j = 0 for i, (yr, mo) in enumerate(zip(yearly_time[:-1], months[:-1])): - # Record initial parameters if i == 0: - diag_ds['volume_m3'].data[i] = self.volume_m3 - diag_ds['area_m2'].data[i] = self.area_m2 - diag_ds['length_m'].data[i] = self.length_m - + diag_ds["volume_m3"].data[i] = self.volume_m3 + diag_ds["area_m2"].data[i] = self.area_m2 + diag_ds["length_m"].data[i] = self.length_m + if self.is_tidewater: - diag_ds['volume_bsl_m3'].data[i] = self.volume_bsl_m3 - diag_ds['volume_bwl_m3'].data[i] = self.volume_bwl_m3 - + diag_ds["volume_bsl_m3"].data[i] = self.volume_bsl_m3 + diag_ds["volume_bwl_m3"].data[i] = self.volume_bwl_m3 + self.run_until(yr, run_single_year=True) # Model run if mo == 1: @@ -278,192 +303,258 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, pass j += 1 # Diagnostics - diag_ds['volume_m3'].data[i+1] = self.volume_m3 - diag_ds['area_m2'].data[i+1] = self.area_m2 - diag_ds['length_m'].data[i+1] = self.length_m + diag_ds["volume_m3"].data[i + 1] = self.volume_m3 + diag_ds["area_m2"].data[i + 1] = self.area_m2 + diag_ds["length_m"].data[i + 1] = self.length_m if self.is_tidewater: - diag_ds['calving_m3'].data[i+1] = self.calving_m3_since_y0 - diag_ds['calving_rate_myr'].data[i+1] = self.calving_rate_myr - diag_ds['volume_bsl_m3'].data[i+1] = self.volume_bsl_m3 - diag_ds['volume_bwl_m3'].data[i+1] = self.volume_bwl_m3 + diag_ds["calving_m3"].data[i + 1] = self.calving_m3_since_y0 + diag_ds["calving_rate_myr"].data[i + 1] = self.calving_rate_myr + diag_ds["volume_bsl_m3"].data[i + 1] = self.volume_bsl_m3 + diag_ds["volume_bwl_m3"].data[i + 1] = self.volume_bwl_m3 # to datasets run_ds = [] - for (s, w, b) in zip(sects, widths, bucket): + for s, w, b in zip(sects, widths, bucket): ds = xr.Dataset() - ds.attrs['description'] = 'OGGM model output' - ds.attrs['oggm_version'] = __version__ - ds.attrs['calendar'] = '365-day no leap' - ds.attrs['creation_date'] = strftime("%Y-%m-%d %H:%M:%S", - gmtime()) - ds.coords['time'] = yearly_time - ds['time'].attrs['description'] = 'Floating hydrological year' - varcoords = OrderedDict(time=('time', yearly_time), - year=('time', yearly_time)) - ds['ts_section'] = xr.DataArray(s, dims=('time', 'x'), - coords=varcoords) - ds['ts_width_m'] = xr.DataArray(w, dims=('time', 'x'), - coords=varcoords) + ds.attrs["description"] = "OGGM model output" + ds.attrs["oggm_version"] = __version__ + ds.attrs["calendar"] = "365-day no leap" + ds.attrs["creation_date"] = strftime("%Y-%m-%d %H:%M:%S", gmtime()) + ds.coords["time"] = yearly_time + ds["time"].attrs["description"] = "Floating hydrological year" + varcoords = OrderedDict( + time=("time", yearly_time), year=("time", yearly_time) + ) + ds["ts_section"] = xr.DataArray( + s, dims=("time", "x"), coords=varcoords + ) + ds["ts_width_m"] = xr.DataArray( + w, dims=("time", "x"), coords=varcoords + ) if self.is_tidewater: - ds['ts_calving_bucket_m3'] = xr.DataArray(b, dims=('time', ), - coords=varcoords) + ds["ts_calving_bucket_m3"] = xr.DataArray( + b, dims=("time",), coords=varcoords + ) run_ds.append(ds) # write output? if run_path is not None: - encode = {'ts_section': {'zlib': True, 'complevel': 5}, - 'ts_width_m': {'zlib': True, 'complevel': 5}, - } + encode = { + "ts_section": {"zlib": True, "complevel": 5}, + "ts_width_m": {"zlib": True, "complevel": 5}, + } for i, ds in enumerate(run_ds): - ds.to_netcdf(run_path, 'a', group='fl_{}'.format(i), - encoding=encode) + ds.to_netcdf( + run_path, "a", group="fl_{}".format(i), encoding=encode + ) # Add other diagnostics - diag_ds.to_netcdf(run_path, 'a') + diag_ds.to_netcdf(run_path, "a") if diag_path is not None: diag_ds.to_netcdf(diag_path) return run_ds, diag_ds - - + def updategeometry(self, year, debug=False): """Update geometry for a given year""" - + if debug: - print('year:', year) - + print("year:", year) + # Loop over flowlines for fl_id, fl in enumerate(self.fls): - # Flowline state heights = self.fls[fl_id].surface_h.copy() section_t0 = self.fls[fl_id].section.copy() thick_t0 = self.fls[fl_id].thick.copy() width_t0 = self.fls[fl_id].widths_m.copy() - + # CONSTANT AREAS # Mass redistribution ignored for calibration and spinup years (glacier properties constant) - if (self.option_areaconstant) or (year < self.spinupyears) or (year < self.constantarea_years): + if ( + (self.option_areaconstant) + or (year < self.spinupyears) + or (year < self.constantarea_years) + ): # run mass balance - glac_bin_massbalclim_annual = self.mb_model.get_annual_mb(heights, fls=self.fls, fl_id=fl_id, - year=year, debug=False) + glac_bin_massbalclim_annual = self.mb_model.get_annual_mb( + heights, fls=self.fls, fl_id=fl_id, year=year, debug=False + ) # MASS REDISTRIBUTION else: # FRONTAL ABLATION if self.is_tidewater: # Frontal ablation (m3 ice) - fa_m3 = self._get_annual_frontalablation(heights, fls=self.fls, fl_id=fl_id, - year=year, debug=False) + fa_m3 = self._get_annual_frontalablation( + heights, + fls=self.fls, + fl_id=fl_id, + year=year, + debug=False, + ) if debug: - print('fa_m3_init:', fa_m3) - vol_init = (self.fls[fl_id].section * fl.dx_meter).sum() - print(' volume init:', np.round(vol_init)) - print(' volume final:', np.round(vol_init-fa_m3)) + print("fa_m3_init:", fa_m3) + vol_init = ( + self.fls[fl_id].section * fl.dx_meter + ).sum() + print(" volume init:", np.round(vol_init)) + print(" volume final:", np.round(vol_init - fa_m3)) # First, remove volume lost to frontal ablation # changes to _t0 not _t1, since t1 will be done in the mass redistribution - - glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] + + glac_idx_bsl = np.where( + (thick_t0 > 0) & (fl.bed_h < self.water_level) + )[0] while fa_m3 > 0 and len(glac_idx_bsl) > 0: if debug: - print('fa_m3_remaining:', fa_m3) + print("fa_m3_remaining:", fa_m3) # OGGM code -# glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] + # glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] last_idx = glac_idx_bsl[-1] - + if debug: - print('before:', np.round(self.fls[fl_id].section[last_idx],0), - np.round(self.fls[fl_id].thick[last_idx],0), - np.round(heights[last_idx],0)) - + print( + "before:", + np.round(self.fls[fl_id].section[last_idx], 0), + np.round(self.fls[fl_id].thick[last_idx], 0), + np.round(heights[last_idx], 0), + ) + vol_last = section_t0[last_idx] * fl.dx_meter - + # If frontal ablation more than bin volume, remove entire bin if fa_m3 > vol_last: # Record frontal ablation (m3 w.e.) in mass balance model for output - self.mb_model.glac_bin_frontalablation[last_idx,int(12*(year+1)-1)] = ( - vol_last * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + self.mb_model.glac_bin_frontalablation[ + last_idx, int(12 * (year + 1) - 1) + ] = ( + vol_last + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # Update ice thickness and section area section_t0[last_idx] = 0 - self.fls[fl_id].section = section_t0 + self.fls[fl_id].section = section_t0 # Remove volume from frontal ablation "bucket" fa_m3 -= vol_last - + # Otherwise, remove ice from the section else: # Update section to remove frontal ablation - section_t0[last_idx] = section_t0[last_idx] - fa_m3 / fl.dx_meter - self.fls[fl_id].section = section_t0 + section_t0[last_idx] = ( + section_t0[last_idx] - fa_m3 / fl.dx_meter + ) + self.fls[fl_id].section = section_t0 # Record frontal ablation(m3 w.e.) - self.mb_model.glac_bin_frontalablation[last_idx,int(12*(year+1)-1)] = ( - fa_m3 * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + self.mb_model.glac_bin_frontalablation[ + last_idx, int(12 * (year + 1) - 1) + ] = ( + fa_m3 + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # Frontal ablation bucket now empty fa_m3 = 0 - + # Update flowline heights = self.fls[fl_id].surface_h.copy() section_t0 = self.fls[fl_id].section.copy() thick_t0 = self.fls[fl_id].thick.copy() width_t0 = self.fls[fl_id].widths_m.copy() - + if debug: - print('after:', np.round(self.fls[fl_id].section[last_idx],0), - np.round(self.fls[fl_id].thick[last_idx],0), - np.round(heights[last_idx],0)) - print(' vol final:', (self.fls[fl_id].section * fl.dx_meter).sum()) - - glac_idx_bsl = np.where((thick_t0 > 0) & (fl.bed_h < self.water_level))[0] - - + print( + "after:", + np.round(self.fls[fl_id].section[last_idx], 0), + np.round(self.fls[fl_id].thick[last_idx], 0), + np.round(heights[last_idx], 0), + ) + print( + " vol final:", + (self.fls[fl_id].section * fl.dx_meter).sum(), + ) + + glac_idx_bsl = np.where( + (thick_t0 > 0) & (fl.bed_h < self.water_level) + )[0] + # Redistribute mass if glacier was not fully removed by frontal ablation if len(section_t0.nonzero()[0]) > 0: # Mass redistribution according to Huss empirical curves # Annual glacier mass balance [m ice s-1] - glac_bin_massbalclim_annual = self.mb_model.get_annual_mb(heights, fls=self.fls, fl_id=fl_id, - year=year, debug=False) - sec_in_year = (self.mb_model.dates_table.loc[12*year:12*(year+1)-1,'daysinmonth'].values.sum() - * 24 * 3600) - -# print(' volume change [m3]:', (glac_bin_massbalclim_annual * sec_in_year * -# (width_t0 * fl.dx_meter)).sum()) -# print(glac_bin_masssbalclim_annual) -# print(sec_in_year) -# print(width_t0.sum()) -# print(fl.dx_meter) -# print(width_t0 * fl.dx_meter) - -# # Debugging block -# debug_years = [71] -# if year in debug_years: -# print(year, glac_bin_massbalclim_annual) -# print('section t0:', section_t0) -# print('thick_t0:', thick_t0) -# print('width_t0:', width_t0) -# print(self.glac_idx_initial[fl_id]) -# print('heights:', heights) - - self._massredistributionHuss(section_t0, thick_t0, width_t0, glac_bin_massbalclim_annual, - self.glac_idx_initial[fl_id], heights, sec_in_year=sec_in_year) - + glac_bin_massbalclim_annual = self.mb_model.get_annual_mb( + heights, + fls=self.fls, + fl_id=fl_id, + year=year, + debug=False, + ) + sec_in_year = ( + self.mb_model.dates_table.loc[ + 12 * year : 12 * (year + 1) - 1, "daysinmonth" + ].values.sum() + * 24 + * 3600 + ) + + # print(' volume change [m3]:', (glac_bin_massbalclim_annual * sec_in_year * + # (width_t0 * fl.dx_meter)).sum()) + # print(glac_bin_masssbalclim_annual) + # print(sec_in_year) + # print(width_t0.sum()) + # print(fl.dx_meter) + # print(width_t0 * fl.dx_meter) + + # # Debugging block + # debug_years = [71] + # if year in debug_years: + # print(year, glac_bin_massbalclim_annual) + # print('section t0:', section_t0) + # print('thick_t0:', thick_t0) + # print('width_t0:', width_t0) + # print(self.glac_idx_initial[fl_id]) + # print('heights:', heights) + + self._massredistributionHuss( + section_t0, + thick_t0, + width_t0, + glac_bin_massbalclim_annual, + self.glac_idx_initial[fl_id], + heights, + sec_in_year=sec_in_year, + ) + # Record glacier properties (volume [m3], area [m2], thickness [m], width [km]) # record the next year's properties as well # 'year + 1' used so the glacier properties are consistent with mass balance computations - year = int(year) # required to ensure proper indexing with run_until_and_store (10/21/2020) + year = int( + year + ) # required to ensure proper indexing with run_until_and_store (10/21/2020) glacier_area = fl.widths_m * fl.dx_meter glacier_area[fl.thick == 0] = 0 - self.mb_model.glac_bin_area_annual[:,year+1] = glacier_area - self.mb_model.glac_bin_icethickness_annual[:,year+1] = fl.thick - self.mb_model.glac_bin_width_annual[:,year+1] = fl.widths_m - self.mb_model.glac_wide_area_annual[year+1] = glacier_area.sum() - self.mb_model.glac_wide_volume_annual[year+1] = (fl.section * fl.dx_meter).sum() - - - #%% ----- FRONTAL ABLATION ----- - def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, calving_k=None, debug=False - ): + self.mb_model.glac_bin_area_annual[:, year + 1] = glacier_area + self.mb_model.glac_bin_icethickness_annual[:, year + 1] = fl.thick + self.mb_model.glac_bin_width_annual[:, year + 1] = fl.widths_m + self.mb_model.glac_wide_area_annual[year + 1] = glacier_area.sum() + self.mb_model.glac_wide_volume_annual[year + 1] = ( + fl.section * fl.dx_meter + ).sum() + + # %% ----- FRONTAL ABLATION ----- + def _get_annual_frontalablation( + self, + heights, + year=None, + fls=None, + fl_id=None, + calving_k=None, + debug=False, + ): """Calculate frontal ablation for a given year - + Returns frontal ablation (m3 ice) Parameters @@ -477,12 +568,14 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter - fl_widths_m = getattr(fl, 'widths_m', None) - fl_section = getattr(fl,'section',None) + fl_widths_m = getattr(fl, "widths_m", None) + fl_section = getattr(fl, "section", None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + ) else: icethickness_t0 = None @@ -498,57 +591,80 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, q_calving = 0 if glacier_area_t0.sum() > 0: try: - last_above_wl = np.nonzero((fl.surface_h > self.water_level) & - (fl.thick > 0))[0][-1] + last_above_wl = np.nonzero( + (fl.surface_h > self.water_level) & (fl.thick > 0) + )[0][-1] except: - last_above_wl = np.nonzero((fl.bed_h <= self.water_level) & - (fl.thick > 0))[0][-1] - - if not last_above_wl is None: - if (fl.bed_h[last_above_wl] < self.water_level): + last_above_wl = np.nonzero( + (fl.bed_h <= self.water_level) & (fl.thick > 0) + )[0][-1] + + if last_above_wl is not None: + if fl.bed_h[last_above_wl] < self.water_level: # Volume [m3] and bed elevation [masl] of each bin if debug: - print('\nyear:', year, '\n sea level:', self.water_level, 'bed elev:', np.round(fl.bed_h[last_above_wl], 2)) - print(' estimate frontal ablation') - print(' min elevation:', fl.surface_h[last_above_wl]) - + print( + "\nyear:", + year, + "\n sea level:", + self.water_level, + "bed elev:", + np.round(fl.bed_h[last_above_wl], 2), + ) + print(" estimate frontal ablation") + print(" min elevation:", fl.surface_h[last_above_wl]) + # --- The rest is for calving only --- - self.calving_rate_myr = 0. - + self.calving_rate_myr = 0.0 + # OK, we're really calving section = fl.section - + # Calving law h = fl.thick[last_above_wl] d = h - (fl.surface_h[last_above_wl] - self.water_level) k = self.calving_k q_calving = k * d * h * fl.widths_m[last_above_wl] - + # Max frontal ablation is removing all bins with bed below water level - glac_idx_bsl = np.where((fl.thick > 0) & (fl.bed_h < self.water_level))[0] + glac_idx_bsl = np.where( + (fl.thick > 0) & (fl.bed_h < self.water_level) + )[0] q_calving_max = np.sum(section[glac_idx_bsl]) * fl.dx_meter - - if q_calving > q_calving_max + pygem_prms['constants']['tolerance']: + + if ( + q_calving + > q_calving_max + pygem_prms["constants"]["tolerance"] + ): q_calving = q_calving_max - + # Add to the bucket and the diagnostics self.calving_m3_since_y0 += q_calving self.calving_rate_myr = q_calving / section[last_above_wl] return q_calving - - #%%%% ====== START OF MASS REDISTRIBUTION CURVE - def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massbalclim_annual, - glac_idx_initial, heights, debug=False, hindcast=0, sec_in_year=365*24*3600): + # %%%% ====== START OF MASS REDISTRIBUTION CURVE + def _massredistributionHuss( + self, + section_t0, + thick_t0, + width_t0, + glac_bin_massbalclim_annual, + glac_idx_initial, + heights, + debug=False, + hindcast=0, + sec_in_year=365 * 24 * 3600, + ): """ Mass redistribution according to empirical equations from Huss and Hock (2015) accounting for retreat/advance. - glac_idx_initial is required to ensure that the glacier does not advance to area where glacier did not exist + glac_idx_initial is required to ensure that the glacier does not advance to area where glacier did not exist before (e.g., retreat and advance over a vertical cliff) - + Note: since OGGM uses the DEM, heights along the flowline do not necessarily decrease, i.e., there can be - overdeepenings along the flowlines that occur as the glacier retreats. This is problematic for 'adding' a bin - downstream in cases of glacier advance because you'd be moving new ice to a higher elevation. To avoid this + overdeepenings along the flowlines that occur as the glacier retreats. This is problematic for 'adding' a bin + downstream in cases of glacier advance because you'd be moving new ice to a higher elevation. To avoid this unrealistic case, in the event that this would occur, the overdeepening will simply fill up with ice first until it reaches an elevation where it would put new ice into a downstream bin. @@ -566,287 +682,437 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb Initial glacier indices debug : Boolean option to turn on print statements for development or debugging of code (default False) - + Returns ------- Updates the flowlines automatically, so does not return anything - """ + """ # Glacier area [m2] glacier_area_t0 = width_t0 * self.fls[0].dx_meter glacier_area_t0[thick_t0 == 0] = 0 - + # Annual glacier-wide volume change [m3] # units: [m ice / s] * [s] * [m2] = m3 ice - glacier_volumechange = (glac_bin_massbalclim_annual * sec_in_year * glacier_area_t0).sum() - + glacier_volumechange = ( + glac_bin_massbalclim_annual * sec_in_year * glacier_area_t0 + ).sum() + # For hindcast simulations, volume change is the opposite if hindcast == 1: glacier_volumechange = -1 * glacier_volumechange - + if debug: - print('\nDebugging Mass Redistribution Huss function\n') - print('glacier volume change:', glacier_volumechange) - + print("\nDebugging Mass Redistribution Huss function\n") + print("glacier volume change:", glacier_volumechange) + # If volume loss is more than the glacier volume, melt everything and stop here - glacier_volume_total = (self.fls[0].section * self.fls[0].dx_meter).sum() + glacier_volume_total = ( + self.fls[0].section * self.fls[0].dx_meter + ).sum() if (glacier_volume_total + glacier_volumechange) < 0: # Set all to zero and return self.fls[0].section *= 0 - return - + return + # Otherwise, redistribute mass loss/gains across the glacier - # Determine where glacier exists + # Determine where glacier exists glac_idx_t0 = self.fls[0].thick.nonzero()[0] - + # Compute ice thickness [m ice], glacier area [m2], ice thickness change [m ice] after redistribution icethickness_change, glacier_volumechange_remaining = ( - self._massredistributioncurveHuss(section_t0, thick_t0, width_t0, glac_idx_t0, - glacier_volumechange, glac_bin_massbalclim_annual, - heights, debug=False)) + self._massredistributioncurveHuss( + section_t0, + thick_t0, + width_t0, + glac_idx_t0, + glacier_volumechange, + glac_bin_massbalclim_annual, + heights, + debug=False, + ) + ) if debug: - print('\nmax icethickness change:', np.round(icethickness_change.max(),3), - '\nmin icethickness change:', np.round(icethickness_change.min(),3), - '\nvolume remaining:', glacier_volumechange_remaining) + print( + "\nmax icethickness change:", + np.round(icethickness_change.max(), 3), + "\nmin icethickness change:", + np.round(icethickness_change.min(), 3), + "\nvolume remaining:", + glacier_volumechange_remaining, + ) nloop = 0 # Glacier retreat # if glacier retreats (ice thickness == 0), volume change needs to be redistributed over glacier again while glacier_volumechange_remaining < 0: - if debug: - print('\n\nGlacier retreating (loop ' + str(nloop) + '):') - + print("\n\nGlacier retreating (loop " + str(nloop) + "):") + section_t0_retreated = self.fls[0].section.copy() thick_t0_retreated = self.fls[0].thick.copy() width_t0_retreated = self.fls[0].widths_m.copy() - glacier_volumechange_remaining_retreated = glacier_volumechange_remaining.copy() - glac_idx_t0_retreated = thick_t0_retreated.nonzero()[0] - glacier_area_t0_retreated = width_t0_retreated * self.fls[0].dx_meter + glacier_volumechange_remaining_retreated = ( + glacier_volumechange_remaining.copy() + ) + glac_idx_t0_retreated = thick_t0_retreated.nonzero()[0] + glacier_area_t0_retreated = ( + width_t0_retreated * self.fls[0].dx_meter + ) glacier_area_t0_retreated[thick_t0 == 0] = 0 - # Set climatic mass balance for the case when there are less than 3 bins + # Set climatic mass balance for the case when there are less than 3 bins # distribute the remaining glacier volume change over the entire glacier (remaining bins) massbalclim_retreat = np.zeros(thick_t0_retreated.shape) - massbalclim_retreat[glac_idx_t0_retreated] = (glacier_volumechange_remaining / - glacier_area_t0_retreated.sum() / sec_in_year) - # Mass redistribution + massbalclim_retreat[glac_idx_t0_retreated] = ( + glacier_volumechange_remaining + / glacier_area_t0_retreated.sum() + / sec_in_year + ) + # Mass redistribution # apply mass redistribution using Huss' empirical geometry change equations icethickness_change, glacier_volumechange_remaining = ( self._massredistributioncurveHuss( - section_t0_retreated, thick_t0_retreated, width_t0_retreated, glac_idx_t0_retreated, - glacier_volumechange_remaining_retreated, massbalclim_retreat, heights, debug=False)) + section_t0_retreated, + thick_t0_retreated, + width_t0_retreated, + glac_idx_t0_retreated, + glacier_volumechange_remaining_retreated, + massbalclim_retreat, + heights, + debug=False, + ) + ) # Avoid rounding errors that get loop stuck if abs(glacier_volumechange_remaining) < 1: glacier_volumechange_remaining = 0 - + if debug: - print('ice thickness change:', icethickness_change) - print('\nmax icethickness change:', np.round(icethickness_change.max(),3), - '\nmin icethickness change:', np.round(icethickness_change.min(),3), - '\nvolume remaining:', glacier_volumechange_remaining) + print("ice thickness change:", icethickness_change) + print( + "\nmax icethickness change:", + np.round(icethickness_change.max(), 3), + "\nmin icethickness change:", + np.round(icethickness_change.min(), 3), + "\nvolume remaining:", + glacier_volumechange_remaining, + ) nloop += 1 - # Glacier advances + # Glacier advances # based on ice thickness change exceeding threshold # Overview: # 1. Add new bin and fill it up to a maximum of terminus average ice thickness # 2. If additional volume after adding new bin, then redistribute mass gain across all bins again, # i.e., increase the ice thickness and width # 3. Repeat adding a new bin and redistributing the mass until no addiitonal volume is left - while (icethickness_change > pygem_prms['sim']['icethickness_advancethreshold']).any() == True: + while ( + icethickness_change + > pygem_prms["sim"]["icethickness_advancethreshold"] + ).any() == True: if debug: - print('advancing glacier') - + print("advancing glacier") + # Record glacier area and ice thickness before advance corrections applied section_t0_raw = self.fls[0].section.copy() thick_t0_raw = self.fls[0].thick.copy() width_t0_raw = self.fls[0].widths_m.copy() glacier_area_t0_raw = width_t0_raw * self.fls[0].dx_meter - + if debug: - print('\n\nthickness t0:', thick_t0_raw) - print('glacier area t0:', glacier_area_t0_raw) - print('width_t0_raw:', width_t0_raw,'\n\n') - + print("\n\nthickness t0:", thick_t0_raw) + print("glacier area t0:", glacier_area_t0_raw) + print("width_t0_raw:", width_t0_raw, "\n\n") + # Index bins that are advancing - icethickness_change[icethickness_change <= pygem_prms['sim']['icethickness_advancethreshold']] = 0 + icethickness_change[ + icethickness_change + <= pygem_prms["sim"]["icethickness_advancethreshold"] + ] = 0 glac_idx_advance = icethickness_change.nonzero()[0] - + # Update ice thickness based on maximum advance threshold [m ice] - self.fls[0].thick[glac_idx_advance] = (self.fls[0].thick[glac_idx_advance] - - (icethickness_change[glac_idx_advance] - pygem_prms['sim']['icethickness_advancethreshold'])) - glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter - + self.fls[0].thick[glac_idx_advance] = self.fls[0].thick[ + glac_idx_advance + ] - ( + icethickness_change[glac_idx_advance] + - pygem_prms["sim"]["icethickness_advancethreshold"] + ) + glacier_area_t1 = ( + self.fls[0].widths_m.copy() * self.fls[0].dx_meter + ) + # Advance volume [m3] - advance_volume = ((glacier_area_t0_raw[glac_idx_advance] * thick_t0_raw[glac_idx_advance]).sum() - - (glacier_area_t1[glac_idx_advance] * self.fls[0].thick[glac_idx_advance]).sum()) + advance_volume = ( + glacier_area_t0_raw[glac_idx_advance] + * thick_t0_raw[glac_idx_advance] + ).sum() - ( + glacier_area_t1[glac_idx_advance] + * self.fls[0].thick[glac_idx_advance] + ).sum() if debug: - print('advance volume [m3]:', advance_volume) + print("advance volume [m3]:", advance_volume) # Set the cross sectional area of the next bin advance_section = advance_volume / self.fls[0].dx_meter - + # Index of bin to add glac_idx_t0 = self.fls[0].thick.nonzero()[0] min_elev = self.fls[0].surface_h[glac_idx_t0].min() - + # ------------------- glac_idx_t0_term = np.where(self.fls[0].surface_h == min_elev)[0] - + # Check if last bin is below sea-level and if it is, then fill it up if self.fls[0].surface_h[glac_idx_t0_term] < self.water_level: - # Check that not additional bin is not higher than others if len(glac_idx_t0) > 2: elev_sorted = np.sort(self.fls[0].surface_h[glac_idx_t0]) - elev_term = elev_sorted[1] - abs(elev_sorted[2] - elev_sorted[1]) + elev_term = elev_sorted[1] - abs( + elev_sorted[2] - elev_sorted[1] + ) else: elev_term = self.water_level - + if debug: print(self.fls[0].surface_h[glac_idx_t0]) - print(glac_idx_t0_term, 'height:', self.fls[0].surface_h[glac_idx_t0_term], - 'thickness:', self.fls[0].thick[glac_idx_t0_term]) - print(np.where(self.fls[0].surface_h[glac_idx_t0] > self.fls[0].surface_h[glac_idx_t0_term])[0]) - print('advance section:', advance_section) - + print( + glac_idx_t0_term, + "height:", + self.fls[0].surface_h[glac_idx_t0_term], + "thickness:", + self.fls[0].thick[glac_idx_t0_term], + ) + print( + np.where( + self.fls[0].surface_h[glac_idx_t0] + > self.fls[0].surface_h[glac_idx_t0_term] + )[0] + ) + print("advance section:", advance_section) + thick_prior = np.copy(self.fls[0].thick) section_updated = np.copy(self.fls[0].section) - section_updated[glac_idx_t0_term] = section_updated[glac_idx_t0_term] + advance_section - + section_updated[glac_idx_t0_term] = ( + section_updated[glac_idx_t0_term] + advance_section + ) + if debug: - print(self.fls[0].section[glac_idx_t0_term], self.fls[0].surface_h[glac_idx_t0_term], self.fls[0].thick[glac_idx_t0_term]) - + print( + self.fls[0].section[glac_idx_t0_term], + self.fls[0].surface_h[glac_idx_t0_term], + self.fls[0].thick[glac_idx_t0_term], + ) + self.fls[0].section = section_updated - + # Set advance volume to zero advance_volume = 0 icethickness_change = self.fls[0].thick - thick_prior if debug: - print(self.fls[0].section[glac_idx_t0_term], self.fls[0].surface_h[glac_idx_t0_term], self.fls[0].thick[glac_idx_t0_term]) - - print('surface_h:', self.fls[0].surface_h[glac_idx_t0], - '\nmax term elev:', elev_term) - print('icethickness_change:', icethickness_change) + print( + self.fls[0].section[glac_idx_t0_term], + self.fls[0].surface_h[glac_idx_t0_term], + self.fls[0].thick[glac_idx_t0_term], + ) + print( + "surface_h:", + self.fls[0].surface_h[glac_idx_t0], + "\nmax term elev:", + elev_term, + ) + print("icethickness_change:", icethickness_change) if self.fls[0].surface_h[glac_idx_t0_term] > elev_term: - # Record parameters to calculate advance_volume if necessary section_t0_raw = self.fls[0].section.copy() thick_t0_raw = self.fls[0].thick.copy() width_t0_raw = self.fls[0].widths_m.copy() glacier_area_t0_raw = width_t0_raw * self.fls[0].dx_meter - - thick_reduction = self.fls[0].surface_h[glac_idx_t0_term] - elev_term - + + thick_reduction = ( + self.fls[0].surface_h[glac_idx_t0_term] - elev_term + ) + if debug: - print('thick_reduction:', thick_reduction) - print('----\nprior to correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term]) - - self.fls[0].thick[glac_idx_t0_term] = (self.fls[0].thick[glac_idx_t0_term] - thick_reduction) - glacier_area_t1 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter - + print("thick_reduction:", thick_reduction) + print( + "----\nprior to correction:", + self.fls[0].thick[glac_idx_t0_term], + self.fls[0].section[glac_idx_t0_term], + ) + + self.fls[0].thick[glac_idx_t0_term] = ( + self.fls[0].thick[glac_idx_t0_term] - thick_reduction + ) + glacier_area_t1 = ( + self.fls[0].widths_m.copy() * self.fls[0].dx_meter + ) + # Advance volume [m3] - advance_volume = ((glacier_area_t0_raw[glac_idx_t0_term] * thick_t0_raw[glac_idx_t0_term]).sum() - - (glacier_area_t1[glac_idx_t0_term] * self.fls[0].thick[glac_idx_t0_term]).sum()) - + advance_volume = ( + glacier_area_t0_raw[glac_idx_t0_term] + * thick_t0_raw[glac_idx_t0_term] + ).sum() - ( + glacier_area_t1[glac_idx_t0_term] + * self.fls[0].thick[glac_idx_t0_term] + ).sum() + if debug: - print('post correction:', self.fls[0].thick[glac_idx_t0_term], self.fls[0].section[glac_idx_t0_term]) - print('surface_h:', self.fls[0].surface_h[glac_idx_t0]) - print('advance_volume:', advance_volume) - print('icethickness_change:', icethickness_change) - + print( + "post correction:", + self.fls[0].thick[glac_idx_t0_term], + self.fls[0].section[glac_idx_t0_term], + ) + print("surface_h:", self.fls[0].surface_h[glac_idx_t0]) + print("advance_volume:", advance_volume) + print("icethickness_change:", icethickness_change) + # Set icethickness change of terminus to 0 to avoid while loop issues icethickness_change[glac_idx_t0_term] = 0 - if advance_volume > 0: - glac_idx_bin2add = ( - np.where(self.fls[0].surface_h == - self.fls[0].surface_h[np.where(self.fls[0].surface_h < min_elev)[0]].max())[0][0]) + glac_idx_bin2add = np.where( + self.fls[0].surface_h + == self.fls[0] + .surface_h[np.where(self.fls[0].surface_h < min_elev)[0]] + .max() + )[0][0] section_2add = self.fls[0].section.copy() section_2add[glac_idx_bin2add] = advance_section - self.fls[0].section = section_2add - + self.fls[0].section = section_2add + # Advance characteristics # Indices that define the glacier terminus - glac_idx_terminus = ( - glac_idx_t0[(heights[glac_idx_t0] - heights[glac_idx_t0].min()) / - (heights[glac_idx_t0].max() - heights[glac_idx_t0].min()) * 100 - < pygem_prms['sim']['terminus_percentage']]) + glac_idx_terminus = glac_idx_t0[ + (heights[glac_idx_t0] - heights[glac_idx_t0].min()) + / (heights[glac_idx_t0].max() - heights[glac_idx_t0].min()) + * 100 + < pygem_prms["sim"]["terminus_percentage"] + ] # For glaciers with so few bands that the terminus is not identified (ex. <= 4 bands for 20% threshold), # then use the information from all the bands if glac_idx_terminus.shape[0] <= 1: glac_idx_terminus = glac_idx_t0.copy() - + if debug: - print('glacier index terminus:',glac_idx_terminus) - + print("glacier index terminus:", glac_idx_terminus) + # Average area of glacier terminus [m2] # exclude the bin at the terminus, since this bin may need to be filled first try: - minelev_idx = np.where(heights == heights[glac_idx_terminus].min())[0][0] + minelev_idx = np.where( + heights == heights[glac_idx_terminus].min() + )[0][0] glac_idx_terminus_removemin = list(glac_idx_terminus) glac_idx_terminus_removemin.remove(minelev_idx) - terminus_thickness_avg = np.mean(self.fls[0].thick[glac_idx_terminus_removemin]) - except: - glac_idx_terminus_initial = ( - glac_idx_initial[(heights[glac_idx_initial] - heights[glac_idx_initial].min()) / - (heights[glac_idx_initial].max() - heights[glac_idx_initial].min()) * 100 - < pygem_prms['sim']['terminus_percentage']]) + terminus_thickness_avg = np.mean( + self.fls[0].thick[glac_idx_terminus_removemin] + ) + except: + glac_idx_terminus_initial = glac_idx_initial[ + ( + heights[glac_idx_initial] + - heights[glac_idx_initial].min() + ) + / ( + heights[glac_idx_initial].max() + - heights[glac_idx_initial].min() + ) + * 100 + < pygem_prms["sim"]["terminus_percentage"] + ] if glac_idx_terminus_initial.shape[0] <= 1: glac_idx_terminus_initial = glac_idx_initial.copy() - - minelev_idx = np.where(heights == heights[glac_idx_terminus_initial].min())[0][0] - glac_idx_terminus_removemin = list(glac_idx_terminus_initial) + + minelev_idx = np.where( + heights == heights[glac_idx_terminus_initial].min() + )[0][0] + glac_idx_terminus_removemin = list( + glac_idx_terminus_initial + ) glac_idx_terminus_removemin.remove(minelev_idx) - terminus_thickness_avg = np.mean(self.fls[0].thick[glac_idx_terminus_removemin]) - + terminus_thickness_avg = np.mean( + self.fls[0].thick[glac_idx_terminus_removemin] + ) + # If last bin exceeds terminus thickness average then fill up the bin to average and redistribute mass - if self.fls[0].thick[glac_idx_bin2add] > terminus_thickness_avg: - self.fls[0].thick[glac_idx_bin2add] = terminus_thickness_avg + if ( + self.fls[0].thick[glac_idx_bin2add] + > terminus_thickness_avg + ): + self.fls[0].thick[glac_idx_bin2add] = ( + terminus_thickness_avg + ) # Redistribute remaining mass - volume_added2bin = self.fls[0].section[glac_idx_bin2add] * self.fls[0].dx_meter + volume_added2bin = ( + self.fls[0].section[glac_idx_bin2add] + * self.fls[0].dx_meter + ) advance_volume -= volume_added2bin # With remaining advance volume, add a bin or redistribute over existing bins if no bins left if advance_volume > 0: # Indices for additional bins below the terminus glac_idx_t1 = np.where(glacier_area_t1 > 0)[0] - below_glac_idx = np.where(heights < heights[glac_idx_t1].min())[0] + below_glac_idx = np.where( + heights < heights[glac_idx_t1].min() + )[0] # if no more bins below, then distribute volume over the glacier without further adjustments - # this occurs with OGGM flowlines when the terminus is in an overdeepening, so we just fill up + # this occurs with OGGM flowlines when the terminus is in an overdeepening, so we just fill up # the overdeepening if len(below_glac_idx) == 0: # Revert to the initial section, which also updates the thickness and width automatically self.fls[0].section = section_t0_raw - + # set icethickness change and advance_volume to 0 to break the loop icethickness_change[icethickness_change > 0] = 0 advance_volume = 0 - + # otherwise, redistribute mass else: glac_idx_t0 = self.fls[0].thick.nonzero()[0] - glacier_area_t0 = self.fls[0].widths_m.copy() * self.fls[0].dx_meter - glac_bin_massbalclim_annual = np.zeros(self.fls[0].thick.shape) - glac_bin_massbalclim_annual[glac_idx_t0] = (glacier_volumechange_remaining / - glacier_area_t0.sum() / sec_in_year) + glacier_area_t0 = ( + self.fls[0].widths_m.copy() * self.fls[0].dx_meter + ) + glac_bin_massbalclim_annual = np.zeros( + self.fls[0].thick.shape + ) + glac_bin_massbalclim_annual[glac_idx_t0] = ( + glacier_volumechange_remaining + / glacier_area_t0.sum() + / sec_in_year + ) icethickness_change, glacier_volumechange_remaining = ( self._massredistributioncurveHuss( - self.fls[0].section.copy(), self.fls[0].thick.copy(), self.fls[0].widths_m.copy(), - glac_idx_t0, advance_volume, glac_bin_massbalclim_annual, heights, debug=False)) - - - def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_t0, glacier_volumechange, - massbalclim_annual, heights, debug=False): + self.fls[0].section.copy(), + self.fls[0].thick.copy(), + self.fls[0].widths_m.copy(), + glac_idx_t0, + advance_volume, + glac_bin_massbalclim_annual, + heights, + debug=False, + ) + ) + + def _massredistributioncurveHuss( + self, + section_t0, + thick_t0, + width_t0, + glac_idx_t0, + glacier_volumechange, + massbalclim_annual, + heights, + debug=False, + ): """ Apply the mass redistribution curves from Huss and Hock (2015). This is paired with massredistributionHuss, which takes into consideration retreat and advance. - + Parameters ---------- section_t0 : np.ndarray @@ -867,16 +1133,16 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ Ice thickness change [m] for each elevation bin glacier_volumechange_remaining : float Glacier volume change remaining [m3 ice]; occurs if there is less ice than melt in a bin, i.e., retreat - """ - + """ + if debug: - print('\nDebugging mass redistribution curve Huss\n') + print("\nDebugging mass redistribution curve Huss\n") - # Apply Huss redistribution if there are at least 3 elevation bands; otherwise, use the mass balance + # Apply Huss redistribution if there are at least 3 elevation bands; otherwise, use the mass balance # Glacier area used to select parameters glacier_area_t0 = width_t0 * self.fls[0].dx_meter glacier_area_t0[thick_t0 == 0] = 0 - + # Apply mass redistribution curve if glac_idx_t0.shape[0] > 3: # Select the factors for the normalized ice thickness change curve based on glacier area @@ -891,57 +1157,73 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ icethicknesschange_norm = np.zeros(glacier_area_t0.shape) # Normalized elevation range [-] # (max elevation - bin elevation) / (max_elevation - min_elevation) - elevrange_norm[glacier_area_t0 > 0] = ((heights[glac_idx_t0].max() - heights[glac_idx_t0]) / - (heights[glac_idx_t0].max() - heights[glac_idx_t0].min())) - + elevrange_norm[glacier_area_t0 > 0] = ( + heights[glac_idx_t0].max() - heights[glac_idx_t0] + ) / (heights[glac_idx_t0].max() - heights[glac_idx_t0].min()) + # using indices as opposed to elevations automatically skips bins on the glacier that have no area # such that the normalization is done only on bins where the glacier lies # Normalized ice thickness change [-] - icethicknesschange_norm[glacier_area_t0 > 0] = ((elevrange_norm[glacier_area_t0 > 0] + a)**gamma + - b*(elevrange_norm[glacier_area_t0 > 0] + a) + c) + icethicknesschange_norm[glacier_area_t0 > 0] = ( + (elevrange_norm[glacier_area_t0 > 0] + a) ** gamma + + b * (elevrange_norm[glacier_area_t0 > 0] + a) + + c + ) # delta_h = (h_n + a)**gamma + b*(h_n + a) + c # indexing is faster here # limit the icethicknesschange_norm to between 0 - 1 (ends of fxns not exactly 0 and 1) icethicknesschange_norm[icethicknesschange_norm > 1] = 1 icethicknesschange_norm[icethicknesschange_norm < 0] = 0 - # Huss' ice thickness scaling factor, fs_huss [m ice] + # Huss' ice thickness scaling factor, fs_huss [m ice] # units: m3 / (m2 * [-]) * (1000 m / 1 km) = m ice - fs_huss = glacier_volumechange / (glacier_area_t0 * icethicknesschange_norm).sum() + fs_huss = ( + glacier_volumechange + / (glacier_area_t0 * icethicknesschange_norm).sum() + ) if debug: - print('fs_huss:', fs_huss) + print("fs_huss:", fs_huss) # Volume change [m3 ice] - bin_volumechange = icethicknesschange_norm * fs_huss * glacier_area_t0 - + bin_volumechange = ( + icethicknesschange_norm * fs_huss * glacier_area_t0 + ) + # Otherwise, compute volume change in each bin based on the climatic mass balance else: bin_volumechange = massbalclim_annual * glacier_area_t0 - + if debug: - print('-----\n') + print("-----\n") vol_before = section_t0 * self.fls[0].dx_meter - # Update cross sectional area (updating thickness does not conserve mass in OGGM!) + # Update cross sectional area (updating thickness does not conserve mass in OGGM!) # volume change divided by length (dx); units m2 section_change = bin_volumechange / self.fls[0].dx_meter - self.fls[0].section = utils.clip_min(self.fls[0].section + section_change, 0) + self.fls[0].section = utils.clip_min( + self.fls[0].section + section_change, 0 + ) # Ice thickness change [m ice] icethickness_change = self.fls[0].thick - thick_t0 # Glacier volume vol_after = self.fls[0].section * self.fls[0].dx_meter - + if debug: - print('vol_chg_wanted:', bin_volumechange.sum()) - print('vol_chg:', (vol_after.sum() - vol_before.sum())) - print('\n-----') - + print("vol_chg_wanted:", bin_volumechange.sum()) + print("vol_chg:", (vol_after.sum() - vol_before.sum())) + print("\n-----") + # Compute the remaining volume change - bin_volumechange_remaining = (bin_volumechange - (self.fls[0].section * self.fls[0].dx_meter - - section_t0 * self.fls[0].dx_meter)) + bin_volumechange_remaining = bin_volumechange - ( + self.fls[0].section * self.fls[0].dx_meter + - section_t0 * self.fls[0].dx_meter + ) # remove values below tolerance to avoid rounding errors - bin_volumechange_remaining[abs(bin_volumechange_remaining) < pygem_prms['constants']['tolerance']] = 0 + bin_volumechange_remaining[ + abs(bin_volumechange_remaining) + < pygem_prms["constants"]["tolerance"] + ] = 0 # Glacier volume change remaining - if less than zero, then needed for retreat - glacier_volumechange_remaining = bin_volumechange_remaining.sum() - + glacier_volumechange_remaining = bin_volumechange_remaining.sum() + if debug: print(glacier_volumechange_remaining) diff --git a/pygem/massbalance.py b/pygem/massbalance.py index 15532c2a..2a3f1fcd 100644 --- a/pygem/massbalance.py +++ b/pygem/massbalance.py @@ -5,17 +5,22 @@ Distrubted under the MIT lisence """ + # External libraries import numpy as np + # Local libraries from oggm.core.massbalance import MassBalanceModel -from pygem.utils._funcs import annualweightedmean_array + # Local libraries import pygem.setup.config as config +from pygem.utils._funcs import annualweightedmean_array + # Read the config pygem_prms = config.read_config() # This reads the configuration file -#%% + +# %% class PyGEMMassBalance(MassBalanceModel): """Mass-balance computed from the Python Glacier Evolution Model. @@ -23,15 +28,25 @@ class PyGEMMassBalance(MassBalanceModel): This class implements the MassBalanceModel interface so that the dynamical model can use it. """ - def __init__(self, gdir, modelprms, glacier_rgi_table, - option_areaconstant=False, hindcast=pygem_prms['climate']['hindcast'], frontalablation_k=None, - debug=pygem_prms['debug']['mb'], debug_refreeze=pygem_prms['debug']['refreeze'], - fls=None, fl_id=0, - heights=None, repeat_period=False, - inversion_filter=False, - ignore_debris=False - ): - """ Initialize. + + def __init__( + self, + gdir, + modelprms, + glacier_rgi_table, + option_areaconstant=False, + hindcast=pygem_prms["climate"]["hindcast"], + frontalablation_k=None, + debug=pygem_prms["debug"]["mb"], + debug_refreeze=pygem_prms["debug"]["refreeze"], + fls=None, + fl_id=0, + heights=None, + repeat_period=False, + inversion_filter=False, + ignore_debris=False, + ): + """Initialize. Parameters ---------- @@ -51,7 +66,7 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, switch to run the model in reverse or not (may be irrelevant after converting to OGGM's setup) """ if debug: - print('\n\nDEBUGGING MASS BALANCE FUNCTION\n\n') + print("\n\nDEBUGGING MASS BALANCE FUNCTION\n\n") self.debug_refreeze = debug_refreeze self.inversion_filter = inversion_filter @@ -63,11 +78,15 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, self.modelprms = modelprms self.glacier_rgi_table = glacier_rgi_table self.is_tidewater = gdir.is_tidewater - self.icethickness_initial = getattr(fls[fl_id], 'thick', None) + self.icethickness_initial = getattr(fls[fl_id], "thick", None) self.width_initial = fls[fl_id].widths_m self.glacier_area_initial = fls[fl_id].widths_m * fls[fl_id].dx_meter self.heights = fls[fl_id].surface_h - if pygem_prms['mb']['include_debris'] and not ignore_debris and not gdir.is_tidewater: + if ( + pygem_prms["mb"]["include_debris"] + and not ignore_debris + and not gdir.is_tidewater + ): try: self.debris_ed = fls[fl_id].debris_ed except: @@ -79,14 +98,14 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, # Climate data self.dates_table = gdir.dates_table - self.glacier_gcm_temp = gdir.historical_climate['temp'] - self.glacier_gcm_tempstd = gdir.historical_climate['tempstd'] - self.glacier_gcm_prec = gdir.historical_climate['prec'] - self.glacier_gcm_elev = gdir.historical_climate['elev'] - self.glacier_gcm_lrgcm = gdir.historical_climate['lr'] - self.glacier_gcm_lrglac = gdir.historical_climate['lr'] - - if pygem_prms['climate']['hindcast'] == True: + self.glacier_gcm_temp = gdir.historical_climate["temp"] + self.glacier_gcm_tempstd = gdir.historical_climate["tempstd"] + self.glacier_gcm_prec = gdir.historical_climate["prec"] + self.glacier_gcm_elev = gdir.historical_climate["elev"] + self.glacier_gcm_lrgcm = gdir.historical_climate["lr"] + self.glacier_gcm_lrglac = gdir.historical_climate["lr"] + + if pygem_prms["climate"]["hindcast"] == True: self.glacier_gcm_prec = self.glacier_gcm_prec[::-1] self.glacier_gcm_temp = self.glacier_gcm_temp[::-1] self.glacier_gcm_lrgcm = self.glacier_gcm_lrgcm[::-1] @@ -96,35 +115,39 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, # Variables to store (consider storing in xarray) nbins = self.glacier_area_initial.shape[0] - + self.nmonths = self.glacier_gcm_temp.shape[0] self.nyears = int(self.dates_table.shape[0] / 12) - self.bin_temp = np.zeros((nbins,self.nmonths)) - self.bin_prec = np.zeros((nbins,self.nmonths)) - self.bin_acc = np.zeros((nbins,self.nmonths)) - self.bin_refreezepotential = np.zeros((nbins,self.nmonths)) - self.bin_refreeze = np.zeros((nbins,self.nmonths)) - self.bin_meltglac = np.zeros((nbins,self.nmonths)) - self.bin_meltsnow = np.zeros((nbins,self.nmonths)) - self.bin_melt = np.zeros((nbins,self.nmonths)) - self.bin_snowpack = np.zeros((nbins,self.nmonths)) - self.snowpack_remaining = np.zeros((nbins,self.nmonths)) - self.glac_bin_refreeze = np.zeros((nbins,self.nmonths)) - self.glac_bin_melt = np.zeros((nbins,self.nmonths)) - self.glac_bin_frontalablation = np.zeros((nbins,self.nmonths)) - self.glac_bin_snowpack = np.zeros((nbins,self.nmonths)) - self.glac_bin_massbalclim = np.zeros((nbins,self.nmonths)) - self.glac_bin_massbalclim_annual = np.zeros((nbins,self.nyears)) - self.glac_bin_surfacetype_annual = np.zeros((nbins,self.nyears+1)) - self.glac_bin_area_annual = np.zeros((nbins,self.nyears+1)) - self.glac_bin_icethickness_annual = np.zeros((nbins,self.nyears+1)) # Needed for MassRedistributionCurves - self.glac_bin_width_annual = np.zeros((nbins,self.nyears+1)) # Needed for MassRedistributionCurves - self.offglac_bin_prec = np.zeros((nbins,self.nmonths)) - self.offglac_bin_melt = np.zeros((nbins,self.nmonths)) - self.offglac_bin_refreeze = np.zeros((nbins,self.nmonths)) - self.offglac_bin_snowpack = np.zeros((nbins,self.nmonths)) - self.offglac_bin_area_annual = np.zeros((nbins,self.nyears+1)) + self.bin_temp = np.zeros((nbins, self.nmonths)) + self.bin_prec = np.zeros((nbins, self.nmonths)) + self.bin_acc = np.zeros((nbins, self.nmonths)) + self.bin_refreezepotential = np.zeros((nbins, self.nmonths)) + self.bin_refreeze = np.zeros((nbins, self.nmonths)) + self.bin_meltglac = np.zeros((nbins, self.nmonths)) + self.bin_meltsnow = np.zeros((nbins, self.nmonths)) + self.bin_melt = np.zeros((nbins, self.nmonths)) + self.bin_snowpack = np.zeros((nbins, self.nmonths)) + self.snowpack_remaining = np.zeros((nbins, self.nmonths)) + self.glac_bin_refreeze = np.zeros((nbins, self.nmonths)) + self.glac_bin_melt = np.zeros((nbins, self.nmonths)) + self.glac_bin_frontalablation = np.zeros((nbins, self.nmonths)) + self.glac_bin_snowpack = np.zeros((nbins, self.nmonths)) + self.glac_bin_massbalclim = np.zeros((nbins, self.nmonths)) + self.glac_bin_massbalclim_annual = np.zeros((nbins, self.nyears)) + self.glac_bin_surfacetype_annual = np.zeros((nbins, self.nyears + 1)) + self.glac_bin_area_annual = np.zeros((nbins, self.nyears + 1)) + self.glac_bin_icethickness_annual = np.zeros( + (nbins, self.nyears + 1) + ) # Needed for MassRedistributionCurves + self.glac_bin_width_annual = np.zeros( + (nbins, self.nyears + 1) + ) # Needed for MassRedistributionCurves + self.offglac_bin_prec = np.zeros((nbins, self.nmonths)) + self.offglac_bin_melt = np.zeros((nbins, self.nmonths)) + self.offglac_bin_refreeze = np.zeros((nbins, self.nmonths)) + self.offglac_bin_snowpack = np.zeros((nbins, self.nmonths)) + self.offglac_bin_area_annual = np.zeros((nbins, self.nyears + 1)) self.glac_wide_temp = np.zeros(self.nmonths) self.glac_wide_prec = np.zeros(self.nmonths) self.glac_wide_acc = np.zeros(self.nmonths) @@ -134,49 +157,85 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, self.glac_wide_massbaltotal = np.zeros(self.nmonths) self.glac_wide_runoff = np.zeros(self.nmonths) self.glac_wide_snowline = np.zeros(self.nmonths) - self.glac_wide_area_annual = np.zeros(self.nyears+1) - self.glac_wide_volume_annual = np.zeros(self.nyears+1) + self.glac_wide_area_annual = np.zeros(self.nyears + 1) + self.glac_wide_volume_annual = np.zeros(self.nyears + 1) self.glac_wide_volume_change_ignored_annual = np.zeros(self.nyears) - self.glac_wide_ELA_annual = np.zeros(self.nyears+1) + self.glac_wide_ELA_annual = np.zeros(self.nyears + 1) self.offglac_wide_prec = np.zeros(self.nmonths) self.offglac_wide_refreeze = np.zeros(self.nmonths) self.offglac_wide_melt = np.zeros(self.nmonths) self.offglac_wide_snowpack = np.zeros(self.nmonths) self.offglac_wide_runoff = np.zeros(self.nmonths) - self.dayspermonth = self.dates_table['daysinmonth'].values + self.dayspermonth = self.dates_table["daysinmonth"].values self.surfacetype_ddf = np.zeros((nbins)) # Surface type DDF dictionary (manipulate this function for calibration or for each glacier) self.surfacetype_ddf_dict = self._surfacetypeDDFdict(self.modelprms) - self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial(self.heights) + self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial( + self.heights + ) # Refreezing specific layers - if pygem_prms['mb']['option_refreezing'] == 'HH2015': + if pygem_prms["mb"]["option_refreezing"] == "HH2015": # Refreezing layers density, volumetric heat capacity, and thermal conductivity - self.rf_dens_expb = (pygem_prms['mb']['HH2015_rf_opts']['rf_dens_bot'] / pygem_prms['mb']['HH2015_rf_opts']['rf_dens_top'])**(1/(pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1)) - self.rf_layers_dens = np.array([pygem_prms['mb']['HH2015_rf_opts']['rf_dens_top'] * self.rf_dens_expb**x - for x in np.arange(0,pygem_prms['mb']['HH2015_rf_opts']['rf_layers'])]) - self.rf_layers_ch = ((1 - self.rf_layers_dens/1000) * pygem_prms['constants']['ch_air'] + self.rf_layers_dens/1000 * - pygem_prms['constants']['ch_ice']) - self.rf_layers_k = ((1 - self.rf_layers_dens/1000) * pygem_prms['constants']['k_air'] + self.rf_layers_dens/1000 * - pygem_prms['constants']['k_ice']) + self.rf_dens_expb = ( + pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_bot"] + / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_top"] + ) ** (1 / (pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] - 1)) + self.rf_layers_dens = np.array( + [ + pygem_prms["mb"]["HH2015_rf_opts"]["rf_dens_top"] + * self.rf_dens_expb**x + for x in np.arange( + 0, pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"] + ) + ] + ) + self.rf_layers_ch = (1 - self.rf_layers_dens / 1000) * pygem_prms[ + "constants" + ]["ch_air"] + self.rf_layers_dens / 1000 * pygem_prms["constants"][ + "ch_ice" + ] + self.rf_layers_k = (1 - self.rf_layers_dens / 1000) * pygem_prms[ + "constants" + ]["k_air"] + self.rf_layers_dens / 1000 * pygem_prms["constants"][ + "k_ice" + ] # refreeze in each bin self.refr = np.zeros(nbins) # refrezee cold content or "potential" refreeze self.rf_cold = np.zeros(nbins) # layer temp of each elev bin for present time step - self.te_rf = np.zeros((pygem_prms['mb']['HH2015_rf_opts']['rf_layers'],nbins,self.nmonths)) + self.te_rf = np.zeros( + ( + pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"], + nbins, + self.nmonths, + ) + ) # layer temp of each elev bin for previous time step - self.tl_rf = np.zeros((pygem_prms['mb']['HH2015_rf_opts']['rf_layers'],nbins,self.nmonths)) + self.tl_rf = np.zeros( + ( + pygem_prms["mb"]["HH2015_rf_opts"]["rf_layers"], + nbins, + self.nmonths, + ) + ) # Sea level for marine-terminating glaciers self.sea_level = 0 - rgi_region = int(glacier_rgi_table.RGIId.split('-')[1].split('.')[0]) - - - def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, - debug=pygem_prms['debug']['mb'], option_areaconstant=False): + rgi_region = int(glacier_rgi_table.RGIId.split("-")[1].split(".")[0]) + + def get_annual_mb( + self, + heights, + year=None, + fls=None, + fl_id=None, + debug=pygem_prms["debug"]["mb"], + option_areaconstant=False, + ): """FIXED FORMAT FOR THE FLOWLINE MODEL Returns annual climatic mass balance [m ice per second] @@ -187,7 +246,7 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, elevation bins year : int year starting with 0 to the number of years in the study - + Returns ------- mb : np.array @@ -195,315 +254,612 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, """ year = int(year) if self.repeat_period: - year = year % (pygem_prms['climate']['gcm_endyear'] - pygem_prms['climate']['gcm_startyear']) + year = year % ( + pygem_prms["climate"]["gcm_endyear"] + - pygem_prms["climate"]["gcm_startyear"] + ) fl = fls[fl_id] np.testing.assert_allclose(heights, fl.surface_h) glacier_area_t0 = fl.widths_m * fl.dx_meter glacier_area_initial = self.glacier_area_initial - fl_widths_m = getattr(fl, 'widths_m', None) - fl_section = getattr(fl,'section',None) + fl_widths_m = getattr(fl, "widths_m", None) + fl_section = getattr(fl, "section", None) # Ice thickness (average) if fl_section is not None and fl_widths_m is not None: icethickness_t0 = np.zeros(fl_section.shape) - icethickness_t0[fl_widths_m > 0] = fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + icethickness_t0[fl_widths_m > 0] = ( + fl_section[fl_widths_m > 0] / fl_widths_m[fl_widths_m > 0] + ) else: icethickness_t0 = None # Quality control: ensure you only have glacier area where there is ice if icethickness_t0 is not None: glacier_area_t0[icethickness_t0 == 0] = 0 - + # Record ice thickness - self.glac_bin_icethickness_annual[:,year] = icethickness_t0 - + self.glac_bin_icethickness_annual[:, year] = icethickness_t0 + # Glacier indices glac_idx_t0 = glacier_area_t0.nonzero()[0] - + nbins = heights.shape[0] nmonths = self.glacier_gcm_temp.shape[0] # Local variables - bin_precsnow = np.zeros((nbins,nmonths)) + bin_precsnow = np.zeros((nbins, nmonths)) # Refreezing specific layers - if pygem_prms['mb']['option_refreezing'] == 'HH2015' and year == 0: - self.te_rf[:,:,0] = 0 # layer temp of each elev bin for present time step - self.tl_rf[:,:,0] = 0 # layer temp of each elev bin for previous time step - elif pygem_prms['mb']['option_refreezing'] == 'Woodward': + if pygem_prms["mb"]["option_refreezing"] == "HH2015" and year == 0: + self.te_rf[:, :, 0] = ( + 0 # layer temp of each elev bin for present time step + ) + self.tl_rf[:, :, 0] = ( + 0 # layer temp of each elev bin for previous time step + ) + elif pygem_prms["mb"]["option_refreezing"] == "Woodward": refreeze_potential = np.zeros(nbins) if self.glacier_area_initial.sum() > 0: -# if len(glac_idx_t0) > 0: - + # if len(glac_idx_t0) > 0: + # Surface type [0=off-glacier, 1=ice, 2=snow, 3=firn, 4=debris] if year == 0: - self.surfacetype, self.firnline_idx = self._surfacetypebinsinitial(self.heights) - self.glac_bin_surfacetype_annual[:,year] = self.surfacetype + self.surfacetype, self.firnline_idx = ( + self._surfacetypebinsinitial(self.heights) + ) + self.glac_bin_surfacetype_annual[:, year] = self.surfacetype # Off-glacier area and indices if option_areaconstant == False: - self.offglac_bin_area_annual[:,year] = glacier_area_initial - glacier_area_t0 - offglac_idx = np.where(self.offglac_bin_area_annual[:,year] > 0)[0] + self.offglac_bin_area_annual[:, year] = ( + glacier_area_initial - glacier_area_t0 + ) + offglac_idx = np.where( + self.offglac_bin_area_annual[:, year] > 0 + )[0] # Functions currently set up for monthly timestep # only compute mass balance while glacier exists - if (pygem_prms['time']['timestep'] == 'monthly'): -# if (pygem_prms['time']['timestep'] == 'monthly') and (glac_idx_t0.shape[0] != 0): + if pygem_prms["time"]["timestep"] == "monthly": + # if (pygem_prms['time']['timestep'] == 'monthly') and (glac_idx_t0.shape[0] != 0): # AIR TEMPERATURE: Downscale the gcm temperature [deg C] to each bin - if pygem_prms['mb']['option_temp2bins'] == 1: + if pygem_prms["mb"]["option_temp2bins"] == 1: # Downscale using gcm and glacier lapse rates - # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange - self.bin_temp[:,12*year:12*(year+1)] = (self.glacier_gcm_temp[12*year:12*(year+1)] + - self.glacier_gcm_lrgcm[12*year:12*(year+1)] * - (self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']] - self.glacier_gcm_elev) + - self.glacier_gcm_lrglac[12*year:12*(year+1)] * (heights - - self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']])[:, np.newaxis] + - self.modelprms['tbias']) + # T_bin = T_gcm + lr_gcm * (z_ref - z_gcm) + lr_glac * (z_bin - z_ref) + tempchange + self.bin_temp[:, 12 * year : 12 * (year + 1)] = ( + self.glacier_gcm_temp[12 * year : 12 * (year + 1)] + + self.glacier_gcm_lrgcm[12 * year : 12 * (year + 1)] + * ( + self.glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + - self.glacier_gcm_elev + ) + + self.glacier_gcm_lrglac[12 * year : 12 * (year + 1)] + * ( + heights + - self.glacier_rgi_table.loc[ + pygem_prms["mb"]["option_elev_ref_downscale"] + ] + )[:, np.newaxis] + + self.modelprms["tbias"] + ) # PRECIPITATION/ACCUMULATION: Downscale the precipitation (liquid and solid) to each bin - if pygem_prms['mb']['option_prec2bins'] == 1: + if pygem_prms["mb"]["option_prec2bins"] == 1: # Precipitation using precipitation factor and precipitation gradient # P_bin = P_gcm * prec_factor * (1 + prec_grad * (z_bin - z_ref)) - bin_precsnow[:,12*year:12*(year+1)] = (self.glacier_gcm_prec[12*year:12*(year+1)] * - self.modelprms['kp'] * (1 + self.modelprms['precgrad'] * (heights - - self.glacier_rgi_table.loc[pygem_prms['mb']['option_elev_ref_downscale']]))[:,np.newaxis]) + bin_precsnow[:, 12 * year : 12 * (year + 1)] = ( + self.glacier_gcm_prec[12 * year : 12 * (year + 1)] + * self.modelprms["kp"] + * ( + 1 + + self.modelprms["precgrad"] + * ( + heights + - self.glacier_rgi_table.loc[ + pygem_prms["mb"][ + "option_elev_ref_downscale" + ] + ] + ) + )[:, np.newaxis] + ) # Option to adjust prec of uppermost 25% of glacier for wind erosion and reduced moisture content - if pygem_prms['mb']['option_preclimit'] == 1: + if pygem_prms["mb"]["option_preclimit"] == 1: # Elevation range based on all flowlines raw_min_elev = [] raw_max_elev = [] if len(fl.surface_h[fl.widths_m > 0]): - raw_min_elev.append(fl.surface_h[fl.widths_m > 0].min()) - raw_max_elev.append(fl.surface_h[fl.widths_m > 0].max()) + raw_min_elev.append( + fl.surface_h[fl.widths_m > 0].min() + ) + raw_max_elev.append( + fl.surface_h[fl.widths_m > 0].max() + ) elev_range = np.max(raw_max_elev) - np.min(raw_min_elev) elev_75 = np.min(raw_min_elev) + 0.75 * (elev_range) # If elevation range > 1000 m, apply corrections to uppermost 25% of glacier (Huss and Hock, 2015) if elev_range > 1000: # Indices of upper 25% - glac_idx_upper25 = glac_idx_t0[heights[glac_idx_t0] >= elev_75] + glac_idx_upper25 = glac_idx_t0[ + heights[glac_idx_t0] >= elev_75 + ] # Exponential decay according to elevation difference from the 75% elevation # prec_upper25 = prec * exp(-(elev_i - elev_75%)/(elev_max- - elev_75%)) # height at 75% of the elevation height_75 = heights[glac_idx_upper25].min() glac_idx_75 = np.where(heights == height_75)[0][0] # exponential decay - bin_precsnow[glac_idx_upper25,12*year:12*(year+1)] = ( - bin_precsnow[glac_idx_75,12*year:12*(year+1)] * - np.exp(-1*(heights[glac_idx_upper25] - height_75) / - (heights[glac_idx_upper25].max() - heights[glac_idx_upper25].min())) - [:,np.newaxis]) + bin_precsnow[ + glac_idx_upper25, 12 * year : 12 * (year + 1) + ] = ( + bin_precsnow[ + glac_idx_75, 12 * year : 12 * (year + 1) + ] + * np.exp( + -1 + * (heights[glac_idx_upper25] - height_75) + / ( + heights[glac_idx_upper25].max() + - heights[glac_idx_upper25].min() + ) + )[:, np.newaxis] + ) # Precipitation cannot be less than 87.5% of the maximum accumulation elsewhere on the glacier - for month in range(0,12): - bin_precsnow[glac_idx_upper25[(bin_precsnow[glac_idx_upper25,month] < 0.875 * - bin_precsnow[glac_idx_t0,month].max()) & - (bin_precsnow[glac_idx_upper25,month] != 0)], month] = ( - 0.875 * bin_precsnow[glac_idx_t0,month].max()) - + for month in range(0, 12): + bin_precsnow[ + glac_idx_upper25[ + ( + bin_precsnow[glac_idx_upper25, month] + < 0.875 + * bin_precsnow[ + glac_idx_t0, month + ].max() + ) + & ( + bin_precsnow[glac_idx_upper25, month] + != 0 + ) + ], + month, + ] = 0.875 * bin_precsnow[glac_idx_t0, month].max() + # Separate total precipitation into liquid (bin_prec) and solid (bin_acc) - if pygem_prms['mb']['option_accumulation'] == 1: + if pygem_prms["mb"]["option_accumulation"] == 1: # if temperature above threshold, then rain - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold']]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold']]) + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + ] # if temperature below threshold, then snow - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold']]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold']]) - elif pygem_prms['mb']['option_accumulation'] == 2: + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] + ] + elif pygem_prms["mb"]["option_accumulation"] == 2: # if temperature between min/max, then mix of snow/rain using linear relationship between min/max - self.bin_prec[:,12*year:12*(year+1)] = ( - (0.5 + (self.bin_temp[:,12*year:12*(year+1)] - - self.modelprms['tsnow_threshold']) / 2) * bin_precsnow[:,12*year:12*(year+1)]) - self.bin_acc[:,12*year:12*(year+1)] = ( - bin_precsnow[:,12*year:12*(year+1)] - self.bin_prec[:,12*year:12*(year+1)]) + self.bin_prec[:, 12 * year : 12 * (year + 1)] = ( + 0.5 + + ( + self.bin_temp[:, 12 * year : 12 * (year + 1)] + - self.modelprms["tsnow_threshold"] + ) + / 2 + ) * bin_precsnow[:, 12 * year : 12 * (year + 1)] + self.bin_acc[:, 12 * year : 12 * (year + 1)] = ( + bin_precsnow[:, 12 * year : 12 * (year + 1)] + - self.bin_prec[:, 12 * year : 12 * (year + 1)] + ) # if temperature above maximum threshold, then all rain - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] > self.modelprms['tsnow_threshold'] + 1]) = 0 + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + > self.modelprms["tsnow_threshold"] + 1 + ] + ) = 0 # if temperature below minimum threshold, then all snow - (self.bin_acc[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) = ( - bin_precsnow[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) - (self.bin_prec[:,12*year:12*(year+1)] - [self.bin_temp[:,12*year:12*(year+1)] <= self.modelprms['tsnow_threshold'] - 1]) = 0 + ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ) = bin_precsnow[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][ + self.bin_temp[:, 12 * year : 12 * (year + 1)] + <= self.modelprms["tsnow_threshold"] - 1 + ] + ) = 0 # ENTER MONTHLY LOOP (monthly loop required since surface type changes) - for month in range(0,12): + for month in range(0, 12): # Step is the position as a function of year and month, which improves readability - step = 12*year + month + step = 12 * year + month # ACCUMULATION, MELT, REFREEZE, AND CLIMATIC MASS BALANCE # Snowpack [m w.e.] = snow remaining + new snow if step == 0: - self.bin_snowpack[:,step] = self.bin_acc[:,step] + self.bin_snowpack[:, step] = self.bin_acc[:, step] else: - self.bin_snowpack[:,step] = self.snowpack_remaining[:,step-1] + self.bin_acc[:,step] + self.bin_snowpack[:, step] = ( + self.snowpack_remaining[:, step - 1] + + self.bin_acc[:, step] + ) # MELT [m w.e.] # energy available for melt [degC day] - if pygem_prms['mb']['option_ablation'] == 1: + if pygem_prms["mb"]["option_ablation"] == 1: # option 1: energy based on monthly temperature - melt_energy_available = self.bin_temp[:,step]*self.dayspermonth[step] + melt_energy_available = ( + self.bin_temp[:, step] * self.dayspermonth[step] + ) melt_energy_available[melt_energy_available < 0] = 0 - elif pygem_prms['mb']['option_ablation'] == 2: + elif pygem_prms["mb"]["option_ablation"] == 2: # Seed randomness for repeatability, but base it on step to ensure the daily variability is not # the same for every single time step np.random.seed(step) # option 2: monthly temperature superimposed with daily temperature variability # daily temperature variation in each bin for the monthly timestep bin_tempstd_daily = np.repeat( - np.random.normal(loc=0, scale=self.glacier_gcm_tempstd[step], - size=self.dayspermonth[step]) - .reshape(1,self.dayspermonth[step]), heights.shape[0], axis=0) + np.random.normal( + loc=0, + scale=self.glacier_gcm_tempstd[step], + size=self.dayspermonth[step], + ).reshape(1, self.dayspermonth[step]), + heights.shape[0], + axis=0, + ) # daily temperature in each bin for the monthly timestep - bin_temp_daily = self.bin_temp[:,step][:,np.newaxis] + bin_tempstd_daily + bin_temp_daily = ( + self.bin_temp[:, step][:, np.newaxis] + + bin_tempstd_daily + ) # remove negative values bin_temp_daily[bin_temp_daily < 0] = 0 # Energy available for melt [degC day] = sum of daily energy available melt_energy_available = bin_temp_daily.sum(axis=1) # SNOW MELT [m w.e.] - self.bin_meltsnow[:,step] = self.surfacetype_ddf_dict[2] * melt_energy_available + self.bin_meltsnow[:, step] = ( + self.surfacetype_ddf_dict[2] * melt_energy_available + ) # snow melt cannot exceed the snow depth - self.bin_meltsnow[self.bin_meltsnow[:,step] > self.bin_snowpack[:,step], step] = ( - self.bin_snowpack[self.bin_meltsnow[:,step] > self.bin_snowpack[:,step], step]) + self.bin_meltsnow[ + self.bin_meltsnow[:, step] + > self.bin_snowpack[:, step], + step, + ] = self.bin_snowpack[ + self.bin_meltsnow[:, step] + > self.bin_snowpack[:, step], + step, + ] # GLACIER MELT (ice and firn) [m w.e.] # energy remaining after snow melt [degC day] melt_energy_available = ( - melt_energy_available - self.bin_meltsnow[:,step] / self.surfacetype_ddf_dict[2]) + melt_energy_available + - self.bin_meltsnow[:, step] + / self.surfacetype_ddf_dict[2] + ) # remove low values of energy available caused by rounding errors in the step above - melt_energy_available[abs(melt_energy_available) < pygem_prms['constants']['tolerance']] = 0 + melt_energy_available[ + abs(melt_energy_available) + < pygem_prms["constants"]["tolerance"] + ] = 0 # DDF based on surface type [m w.e. degC-1 day-1] for surfacetype_idx in self.surfacetype_ddf_dict: - self.surfacetype_ddf[self.surfacetype == surfacetype_idx] = ( - self.surfacetype_ddf_dict[surfacetype_idx]) + self.surfacetype_ddf[ + self.surfacetype == surfacetype_idx + ] = self.surfacetype_ddf_dict[surfacetype_idx] # Debris enhancement factors in ablation area (debris in accumulation area would submerge) - if surfacetype_idx == 1 and pygem_prms['mb']['include_debris']: + if ( + surfacetype_idx == 1 + and pygem_prms["mb"]["include_debris"] + ): self.surfacetype_ddf[self.surfacetype == 1] = ( - self.surfacetype_ddf[self.surfacetype == 1] * self.debris_ed[self.surfacetype == 1]) - self.bin_meltglac[glac_idx_t0,step] = ( - self.surfacetype_ddf[glac_idx_t0] * melt_energy_available[glac_idx_t0]) + self.surfacetype_ddf[self.surfacetype == 1] + * self.debris_ed[self.surfacetype == 1] + ) + self.bin_meltglac[glac_idx_t0, step] = ( + self.surfacetype_ddf[glac_idx_t0] + * melt_energy_available[glac_idx_t0] + ) # TOTAL MELT (snow + glacier) # off-glacier need to include melt of refreeze because there are no glacier dynamics, # but on-glacier do not need to account for this (simply assume refreeze has same surface type) - self.bin_melt[:,step] = self.bin_meltglac[:,step] + self.bin_meltsnow[:,step] + self.bin_melt[:, step] = ( + self.bin_meltglac[:, step] + self.bin_meltsnow[:, step] + ) # REFREEZING - if pygem_prms['mb']['option_refreezing'] == 'HH2015': + if pygem_prms["mb"]["option_refreezing"] == "HH2015": if step > 0: - self.tl_rf[:,:,step] = self.tl_rf[:,:,step-1] - self.te_rf[:,:,step] = self.te_rf[:,:,step-1] + self.tl_rf[:, :, step] = self.tl_rf[:, :, step - 1] + self.te_rf[:, :, step] = self.te_rf[:, :, step - 1] # Refreeze based on heat conduction approach (Huss and Hock 2015) # refreeze time step (s) - rf_dt = 3600 * 24 * self.dayspermonth[step] / pygem_prms['mb']['HH2015_rf_opts']['rf_dsc'] - - if pygem_prms['mb']['HH2015_rf_opts']['option_rf_limit_meltsnow'] == 1: + rf_dt = ( + 3600 + * 24 + * self.dayspermonth[step] + / pygem_prms["mb"]["HH2015_rf_opts"]["rf_dsc"] + ) + + if ( + pygem_prms["mb"]["HH2015_rf_opts"][ + "option_rf_limit_meltsnow" + ] + == 1 + ): bin_meltlimit = self.bin_meltsnow.copy() else: bin_meltlimit = self.bin_melt.copy() # Debug lowest bin if self.debug_refreeze: - gidx_debug = np.where(heights == heights[glac_idx_t0].min())[0] + gidx_debug = np.where( + heights == heights[glac_idx_t0].min() + )[0] # Loop through each elevation bin of glacier for nbin, gidx in enumerate(glac_idx_t0): # COMPUTE HEAT CONDUCTION - BUILD COLD RESERVOIR # If no melt, then build up cold reservoir (compute heat conduction) - if self.bin_melt[gidx,step] < pygem_prms['mb']['HH2015_rf_opts']['rf_meltcrit']: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('\nMonth ' + str(self.dates_table.loc[step,'month']), - 'Computing heat conduction') + if ( + self.bin_melt[gidx, step] + < pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_meltcrit" + ] + ): + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "\nMonth " + + str( + self.dates_table.loc[step, "month"] + ), + "Computing heat conduction", + ) # Set refreeze equal to 0 self.refr[gidx] = 0 # Loop through multiple iterations to converge on a solution # -> this will loop through 0, 1, 2 - for h in np.arange(0, pygem_prms['mb']['HH2015_rf_opts']['rf_dsc']): + for h in np.arange( + 0, + pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_dsc" + ], + ): # Compute heat conduction in layers (loop through rows) # go from 1 to rf_layers-1 to avoid indexing errors with "j-1" and "j+1" # "j+1" is set to zero, which is fine for temperate glaciers but inaccurate for # cold/polythermal glaciers - for j in np.arange(1, pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1): + for j in np.arange( + 1, + pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1, + ): # Assume temperature of first layer equals air temperature # assumption probably wrong, but might still work at annual average # Since next line uses tl_rf for all calculations, set tl_rf[0] to present mean # monthly air temperature to ensure the present calculations are done with the # present time step's air temperature - self.tl_rf[0, gidx,step] = self.bin_temp[gidx,step] + self.tl_rf[0, gidx, step] = ( + self.bin_temp[gidx, step] + ) # Temperature for each layer - self.te_rf[j,gidx,step] = (self.tl_rf[j,gidx,step] + - rf_dt * self.rf_layers_k[j] / self.rf_layers_ch[j] / pygem_prms['mb']['HH2015_rf_opts']['rf_dz']**2 * - 0.5 * ((self.tl_rf[j-1,gidx,step] - self.tl_rf[j,gidx,step]) - - (self.tl_rf[j,gidx,step] - self.tl_rf[j+1,gidx,step]))) + self.te_rf[j, gidx, step] = self.tl_rf[ + j, gidx, step + ] + rf_dt * self.rf_layers_k[ + j + ] / self.rf_layers_ch[j] / pygem_prms[ + "mb" + ]["HH2015_rf_opts"][ + "rf_dz" + ] ** 2 * 0.5 * ( + ( + self.tl_rf[j - 1, gidx, step] + - self.tl_rf[j, gidx, step] + ) + - ( + self.tl_rf[j, gidx, step] + - self.tl_rf[j + 1, gidx, step] + ) + ) # Update previous time step - self.tl_rf[:,gidx,step] = self.te_rf[:,gidx,step] - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('tl_rf:', ["{:.2f}".format(x) for x in self.tl_rf[:,gidx,step]]) + self.tl_rf[:, gidx, step] = self.te_rf[ + :, gidx, step + ] + + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "tl_rf:", + [ + "{:.2f}".format(x) + for x in self.tl_rf[:, gidx, step] + ], + ) # COMPUTE REFREEZING - TAP INTO "COLD RESERVOIR" or potential refreezing else: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('\nMonth ' + str(self.dates_table.loc[step,'month']), 'Computing refreeze') + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "\nMonth " + + str( + self.dates_table.loc[step, "month"] + ), + "Computing refreeze", + ) # Refreezing over firn surface - if (self.surfacetype[gidx] == 2) or (self.surfacetype[gidx] == 3): - nlayers = pygem_prms['mb']['HH2015_rf_opts']['rf_layers']-1 + if (self.surfacetype[gidx] == 2) or ( + self.surfacetype[gidx] == 3 + ): + nlayers = ( + pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1 + ) # Refreezing over ice surface else: # Approximate number of layers of snow on top of ice - smax = np.round((self.bin_snowpack[gidx,step] / (self.rf_layers_dens[0] / 1000) + - pygem_prms['mb']['HH2015_rf_opts']['pp']) / pygem_prms['mb']['HH2015_rf_opts']['rf_dz'], 0) + smax = np.round( + ( + self.bin_snowpack[gidx, step] + / (self.rf_layers_dens[0] / 1000) + + pygem_prms["mb"][ + "HH2015_rf_opts" + ]["pp"] + ) + / pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_dz" + ], + 0, + ) # if there is very little snow on the ground (SWE > 0.06 m for pp=0.3), # then still set smax (layers) to 1 - if self.bin_snowpack[gidx,step] > 0 and smax == 0: - smax=1 + if ( + self.bin_snowpack[gidx, step] > 0 + and smax == 0 + ): + smax = 1 # if no snow on the ground, then set to rf_cold to NoData value if smax == 0: self.rf_cold[gidx] = 0 # if smax greater than the number of layers, set to max number of layers minus 1 - if smax > pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1: - smax = pygem_prms['mb']['HH2015_rf_opts']['rf_layers'] - 1 + if ( + smax + > pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1 + ): + smax = ( + pygem_prms["mb"]["HH2015_rf_opts"][ + "rf_layers" + ] + - 1 + ) nlayers = int(smax) # Compute potential refreeze, "cold reservoir", from temperature in each layer # only calculate potential refreezing first time it starts melting each year - if self.rf_cold[gidx] == 0 and self.tl_rf[:,gidx,step].min() < 0: - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('calculating potential refreeze from ' + str(nlayers) + ' layers') - - for j in np.arange(0,nlayers): + if ( + self.rf_cold[gidx] == 0 + and self.tl_rf[:, gidx, step].min() < 0 + ): + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "calculating potential refreeze from " + + str(nlayers) + + " layers" + ) + + for j in np.arange(0, nlayers): j += 1 # units: (degC) * (J K-1 m-3) * (m) * (kg J-1) * (m3 kg-1) - rf_cold_layer = (self.tl_rf[j,gidx,step] * self.rf_layers_ch[j] * - pygem_prms['mb']['HH2015_rf_opts']['rf_dz'] / pygem_prms['constants']['Lh_rf'] / pygem_prms['constants']['density_water']) + rf_cold_layer = ( + self.tl_rf[j, gidx, step] + * self.rf_layers_ch[j] + * pygem_prms["mb"][ + "HH2015_rf_opts" + ]["rf_dz"] + / pygem_prms["constants"]["Lh_rf"] + / pygem_prms["constants"][ + "density_water" + ] + ) self.rf_cold[gidx] -= rf_cold_layer - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('j:', j, 'tl_rf @ j:', np.round(self.tl_rf[j,gidx,step],2), - 'ch @ j:', np.round(self.rf_layers_ch[j],2), - 'rf_cold_layer @ j:', np.round(rf_cold_layer,2), - 'rf_cold @ j:', np.round(self.rf_cold[gidx],2)) - - if self.debug_refreeze and gidx == gidx_debug and step < 12: - print('rf_cold:', np.round(self.rf_cold[gidx],2)) + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "j:", + j, + "tl_rf @ j:", + np.round( + self.tl_rf[j, gidx, step], + 2, + ), + "ch @ j:", + np.round( + self.rf_layers_ch[j], 2 + ), + "rf_cold_layer @ j:", + np.round(rf_cold_layer, 2), + "rf_cold @ j:", + np.round( + self.rf_cold[gidx], 2 + ), + ) + + if ( + self.debug_refreeze + and gidx == gidx_debug + and step < 12 + ): + print( + "rf_cold:", + np.round(self.rf_cold[gidx], 2), + ) # Compute refreezing # If melt and liquid prec < potential refreeze, then refreeze all melt and liquid prec - if (bin_meltlimit[gidx,step] + self.bin_prec[gidx,step]) < self.rf_cold[gidx]: - self.refr[gidx] = bin_meltlimit[gidx,step] + self.bin_prec[gidx,step] + if ( + bin_meltlimit[gidx, step] + + self.bin_prec[gidx, step] + ) < self.rf_cold[gidx]: + self.refr[gidx] = ( + bin_meltlimit[gidx, step] + + self.bin_prec[gidx, step] + ) # otherwise, refreeze equals the potential refreeze elif self.rf_cold[gidx] > 0: self.refr[gidx] = self.rf_cold[gidx] @@ -511,117 +867,241 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, self.refr[gidx] = 0 # Track the remaining potential refreeze - self.rf_cold[gidx] -= (bin_meltlimit[gidx,step] + self.bin_prec[gidx,step]) + self.rf_cold[gidx] -= ( + bin_meltlimit[gidx, step] + + self.bin_prec[gidx, step] + ) # if potential refreeze consumed, set to 0 and set temperature to 0 (temperate firn) if self.rf_cold[gidx] < 0: self.rf_cold[gidx] = 0 - self.tl_rf[:,gidx,step] = 0 + self.tl_rf[:, gidx, step] = 0 # Record refreeze - self.bin_refreeze[gidx,step] = self.refr[gidx] - - if self.debug_refreeze and step < 12 and gidx == gidx_debug: - print('Month ' + str(self.dates_table.loc[step,'month']), - 'Rf_cold remaining:', np.round(self.rf_cold[gidx],2), - 'Snow depth:', np.round(self.bin_snowpack[glac_idx_t0[nbin],step],2), - 'Snow melt:', np.round(self.bin_meltsnow[glac_idx_t0[nbin],step],2), - 'Rain:', np.round(self.bin_prec[glac_idx_t0[nbin],step],2), - 'Rfrz:', np.round(self.bin_refreeze[gidx,step],2)) - - elif pygem_prms['mb']['option_refreezing'] == 'Woodward': + self.bin_refreeze[gidx, step] = self.refr[gidx] + + if ( + self.debug_refreeze + and step < 12 + and gidx == gidx_debug + ): + print( + "Month " + + str(self.dates_table.loc[step, "month"]), + "Rf_cold remaining:", + np.round(self.rf_cold[gidx], 2), + "Snow depth:", + np.round( + self.bin_snowpack[ + glac_idx_t0[nbin], step + ], + 2, + ), + "Snow melt:", + np.round( + self.bin_meltsnow[ + glac_idx_t0[nbin], step + ], + 2, + ), + "Rain:", + np.round( + self.bin_prec[glac_idx_t0[nbin], step], + 2, + ), + "Rfrz:", + np.round(self.bin_refreeze[gidx, step], 2), + ) + + elif pygem_prms["mb"]["option_refreezing"] == "Woodward": # Refreeze based on annual air temperature (Woodward etal. 1997) # R(m) = (-0.69 * Tair + 0.0096) * 1 m / 100 cm # calculate annually and place potential refreeze in user defined month - if step%12 == 0: - bin_temp_annual = annualweightedmean_array(self.bin_temp[:,12*year:12*(year+1)], - self.dates_table.iloc[12*year:12*(year+1),:]) - bin_refreezepotential_annual = (-0.69 * bin_temp_annual + 0.0096) / 100 + if step % 12 == 0: + bin_temp_annual = annualweightedmean_array( + self.bin_temp[:, 12 * year : 12 * (year + 1)], + self.dates_table.iloc[ + 12 * year : 12 * (year + 1), : + ], + ) + bin_refreezepotential_annual = ( + -0.69 * bin_temp_annual + 0.0096 + ) / 100 # Remove negative refreezing values - bin_refreezepotential_annual[bin_refreezepotential_annual < 0] = 0 - self.bin_refreezepotential[:,step] = bin_refreezepotential_annual + bin_refreezepotential_annual[ + bin_refreezepotential_annual < 0 + ] = 0 + self.bin_refreezepotential[:, step] = ( + bin_refreezepotential_annual + ) # Reset refreeze potential every year - if self.bin_refreezepotential[:,step].max() > 0: - refreeze_potential = self.bin_refreezepotential[:,step] + if self.bin_refreezepotential[:, step].max() > 0: + refreeze_potential = ( + self.bin_refreezepotential[:, step] + ) if self.debug_refreeze: - print('Year ' + str(year) + ' Month ' + str(self.dates_table.loc[step,'month']), - 'Refreeze potential:', np.round(refreeze_potential[glac_idx_t0[0]],3), - 'Snow depth:', np.round(self.bin_snowpack[glac_idx_t0[0],step],2), - 'Snow melt:', np.round(self.bin_meltsnow[glac_idx_t0[0],step],2), - 'Rain:', np.round(self.bin_prec[glac_idx_t0[0],step],2)) + print( + "Year " + + str(year) + + " Month " + + str(self.dates_table.loc[step, "month"]), + "Refreeze potential:", + np.round( + refreeze_potential[glac_idx_t0[0]], 3 + ), + "Snow depth:", + np.round( + self.bin_snowpack[glac_idx_t0[0], step], 2 + ), + "Snow melt:", + np.round( + self.bin_meltsnow[glac_idx_t0[0], step], 2 + ), + "Rain:", + np.round( + self.bin_prec[glac_idx_t0[0], step], 2 + ), + ) # Refreeze [m w.e.] # refreeze cannot exceed rain and melt (snow & glacier melt) - self.bin_refreeze[:,step] = self.bin_meltsnow[:,step] + self.bin_prec[:,step] + self.bin_refreeze[:, step] = ( + self.bin_meltsnow[:, step] + self.bin_prec[:, step] + ) # refreeze cannot exceed snow depth - self.bin_refreeze[self.bin_refreeze[:,step] > self.bin_snowpack[:,step], step] = ( - self.bin_snowpack[self.bin_refreeze[:,step] > self.bin_snowpack[:,step], step]) + self.bin_refreeze[ + self.bin_refreeze[:, step] + > self.bin_snowpack[:, step], + step, + ] = self.bin_snowpack[ + self.bin_refreeze[:, step] + > self.bin_snowpack[:, step], + step, + ] # refreeze cannot exceed refreeze potential - self.bin_refreeze[self.bin_refreeze[:,step] > refreeze_potential, step] = ( - refreeze_potential[self.bin_refreeze[:,step] > refreeze_potential]) - self.bin_refreeze[abs(self.bin_refreeze[:,step]) < pygem_prms['constants']['tolerance'], step] = 0 + self.bin_refreeze[ + self.bin_refreeze[:, step] > refreeze_potential, + step, + ] = refreeze_potential[ + self.bin_refreeze[:, step] > refreeze_potential + ] + self.bin_refreeze[ + abs(self.bin_refreeze[:, step]) + < pygem_prms["constants"]["tolerance"], + step, + ] = 0 # update refreeze potential - refreeze_potential -= self.bin_refreeze[:,step] - refreeze_potential[abs(refreeze_potential) < pygem_prms['constants']['tolerance']] = 0 + refreeze_potential -= self.bin_refreeze[:, step] + refreeze_potential[ + abs(refreeze_potential) + < pygem_prms["constants"]["tolerance"] + ] = 0 # SNOWPACK REMAINING [m w.e.] - self.snowpack_remaining[:,step] = self.bin_snowpack[:,step] - self.bin_meltsnow[:,step] - self.snowpack_remaining[abs(self.snowpack_remaining[:,step]) < pygem_prms['constants']['tolerance'], step] = 0 + self.snowpack_remaining[:, step] = ( + self.bin_snowpack[:, step] - self.bin_meltsnow[:, step] + ) + self.snowpack_remaining[ + abs(self.snowpack_remaining[:, step]) + < pygem_prms["constants"]["tolerance"], + step, + ] = 0 # Record values - self.glac_bin_melt[glac_idx_t0,step] = self.bin_melt[glac_idx_t0,step] - self.glac_bin_refreeze[glac_idx_t0,step] = self.bin_refreeze[glac_idx_t0,step] - self.glac_bin_snowpack[glac_idx_t0,step] = self.bin_snowpack[glac_idx_t0,step] + self.glac_bin_melt[glac_idx_t0, step] = self.bin_melt[ + glac_idx_t0, step + ] + self.glac_bin_refreeze[glac_idx_t0, step] = ( + self.bin_refreeze[glac_idx_t0, step] + ) + self.glac_bin_snowpack[glac_idx_t0, step] = ( + self.bin_snowpack[glac_idx_t0, step] + ) # CLIMATIC MASS BALANCE [m w.e.] - self.glac_bin_massbalclim[glac_idx_t0,step] = ( - self.bin_acc[glac_idx_t0,step] + self.glac_bin_refreeze[glac_idx_t0,step] - - self.glac_bin_melt[glac_idx_t0,step]) + self.glac_bin_massbalclim[glac_idx_t0, step] = ( + self.bin_acc[glac_idx_t0, step] + + self.glac_bin_refreeze[glac_idx_t0, step] + - self.glac_bin_melt[glac_idx_t0, step] + ) # OFF-GLACIER ACCUMULATION, MELT, REFREEZE, AND SNOWPACK if option_areaconstant == False: # precipitation, refreeze, and snowpack are the same both on- and off-glacier - self.offglac_bin_prec[offglac_idx,step] = self.bin_prec[offglac_idx,step] - self.offglac_bin_refreeze[offglac_idx,step] = self.bin_refreeze[offglac_idx,step] - self.offglac_bin_snowpack[offglac_idx,step] = self.bin_snowpack[offglac_idx,step] + self.offglac_bin_prec[offglac_idx, step] = ( + self.bin_prec[offglac_idx, step] + ) + self.offglac_bin_refreeze[offglac_idx, step] = ( + self.bin_refreeze[offglac_idx, step] + ) + self.offglac_bin_snowpack[offglac_idx, step] = ( + self.bin_snowpack[offglac_idx, step] + ) # Off-glacier melt includes both snow melt and melting of refreezing # (this is not an issue on-glacier because energy remaining melts underlying snow/ice) # melt of refreezing (assumed to be snow) - self.offglac_meltrefreeze = self.surfacetype_ddf_dict[2] * melt_energy_available + self.offglac_meltrefreeze = ( + self.surfacetype_ddf_dict[2] + * melt_energy_available + ) # melt of refreezing cannot exceed refreezing - self.offglac_meltrefreeze[self.offglac_meltrefreeze > self.bin_refreeze[:,step]] = ( - self.bin_refreeze[:,step][self.offglac_meltrefreeze > self.bin_refreeze[:,step]]) + self.offglac_meltrefreeze[ + self.offglac_meltrefreeze + > self.bin_refreeze[:, step] + ] = self.bin_refreeze[:, step][ + self.offglac_meltrefreeze + > self.bin_refreeze[:, step] + ] # off-glacier melt = snow melt + refreezing melt - self.offglac_bin_melt[offglac_idx,step] = (self.bin_meltsnow[offglac_idx,step] + - self.offglac_meltrefreeze[offglac_idx]) + self.offglac_bin_melt[offglac_idx, step] = ( + self.bin_meltsnow[offglac_idx, step] + + self.offglac_meltrefreeze[offglac_idx] + ) # ===== RETURN TO ANNUAL LOOP ===== # SURFACE TYPE (-) # Annual climatic mass balance [m w.e.] used to determine the surface type - self.glac_bin_massbalclim_annual[:,year] = self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1) + self.glac_bin_massbalclim_annual[:, year] = ( + self.glac_bin_massbalclim[ + :, 12 * year : 12 * (year + 1) + ].sum(1) + ) # Update surface type for each bin - self.surfacetype, firnline_idx = self._surfacetypebinsannual(self.surfacetype, - self.glac_bin_massbalclim_annual, year) + self.surfacetype, firnline_idx = self._surfacetypebinsannual( + self.surfacetype, self.glac_bin_massbalclim_annual, year + ) # Record binned glacier area - self.glac_bin_area_annual[:,year] = glacier_area_t0 + self.glac_bin_area_annual[:, year] = glacier_area_t0 # Store glacier-wide results - self._convert_glacwide_results(year, glacier_area_t0, heights, fls=fls, fl_id=fl_id, - option_areaconstant=option_areaconstant) - -## if debug: -# debug_startyr = 57 -# debug_endyr = 61 -# if year > debug_startyr and year < debug_endyr: -# print('\n', year, 'glac_bin_massbalclim:', self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1)) -# print('ice thickness:', icethickness_t0) -# print('heights:', heights[glac_idx_t0]) -## print('surface type present:', self.glac_bin_surfacetype_annual[12:20,year]) -## print('surface type updated:', self.surfacetype[12:20]) + self._convert_glacwide_results( + year, + glacier_area_t0, + heights, + fls=fls, + fl_id=fl_id, + option_areaconstant=option_areaconstant, + ) + + ## if debug: + # debug_startyr = 57 + # debug_endyr = 61 + # if year > debug_startyr and year < debug_endyr: + # print('\n', year, 'glac_bin_massbalclim:', self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1)) + # print('ice thickness:', icethickness_t0) + # print('heights:', heights[glac_idx_t0]) + ## print('surface type present:', self.glac_bin_surfacetype_annual[12:20,year]) + ## print('surface type updated:', self.surfacetype[12:20]) # Mass balance for each bin [m ice per second] - seconds_in_year = self.dayspermonth[12*year:12*(year+1)].sum() * 24 * 3600 - mb = (self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1) - * pygem_prms['constants']['density_water'] / pygem_prms['constants']['density_ice'] / seconds_in_year) - + seconds_in_year = ( + self.dayspermonth[12 * year : 12 * (year + 1)].sum() * 24 * 3600 + ) + mb = ( + self.glac_bin_massbalclim[:, 12 * year : 12 * (year + 1)].sum(1) + * pygem_prms["constants"]["density_water"] + / pygem_prms["constants"]["density_ice"] + / seconds_in_year + ) + if self.inversion_filter: mb = np.minimum.accumulate(mb) @@ -633,32 +1113,45 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, height_max = np.max(heights[glac_idx_t0]) height_min = np.min(heights[glac_idx_t0]) mb_grad = (mb_min - mb_max) / (height_max - height_min) - mb_filled[(mb_filled==0) & (heights < height_max)] = ( - mb_min + mb_grad * (height_min - heights[(mb_filled==0) & (heights < height_max)])) + mb_filled[(mb_filled == 0) & (heights < height_max)] = ( + mb_min + + mb_grad + * ( + height_min + - heights[(mb_filled == 0) & (heights < height_max)] + ) + ) elif len(glac_idx_t0) >= 1 and len(glac_idx_t0) <= 3 and mb.max() <= 0: mb_min = np.min(mb[glac_idx_t0]) height_max = np.max(heights[glac_idx_t0]) - mb_filled[(mb_filled==0) & (heights < height_max)] = mb_min - -# if year > debug_startyr and year < debug_endyr: -# print('mb_min:', mb_min) -# -# if year > debug_startyr and year < debug_endyr: -# import matplotlib.pyplot as plt -# plt.plot(mb_filled, heights, '.') -# plt.ylabel('Elevation') -# plt.xlabel('Mass balance (mwea)') -# plt.show() -# -# print('mb_filled:', mb_filled) - - return mb_filled + mb_filled[(mb_filled == 0) & (heights < height_max)] = mb_min + + # if year > debug_startyr and year < debug_endyr: + # print('mb_min:', mb_min) + # + # if year > debug_startyr and year < debug_endyr: + # import matplotlib.pyplot as plt + # plt.plot(mb_filled, heights, '.') + # plt.ylabel('Elevation') + # plt.xlabel('Mass balance (mwea)') + # plt.show() + # + # print('mb_filled:', mb_filled) + return mb_filled - #%% - def _convert_glacwide_results(self, year, glacier_area, heights, - fls=None, fl_id=None, option_areaconstant=False, debug=False): + # %% + def _convert_glacwide_results( + self, + year, + glacier_area, + heights, + fls=None, + fl_id=None, + option_areaconstant=False, + debug=False, + ): """ Convert raw runmassbalance function output to glacier-wide results for output package 2 @@ -677,26 +1170,35 @@ def _convert_glacwide_results(self, year, glacier_area, heights, """ # Glacier area glac_idx = glacier_area.nonzero()[0] - glacier_area_monthly = glacier_area[:,np.newaxis].repeat(12,axis=1) - + glacier_area_monthly = glacier_area[:, np.newaxis].repeat(12, axis=1) + # Check if need to adjust for complete removal of the glacier # - needed for accurate runoff calcs and accurate mass balance components - icethickness_t0 = getattr(fls[fl_id], 'thick', None) + icethickness_t0 = getattr(fls[fl_id], "thick", None) if icethickness_t0 is not None: # Mass loss cannot exceed glacier volume if glacier_area.sum() > 0: - mb_max_loss = (-1 * (glacier_area * icethickness_t0).sum() / glacier_area.sum() * - pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water']) + mb_max_loss = ( + -1 + * (glacier_area * icethickness_t0).sum() + / glacier_area.sum() + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + ) # Check annual climatic mass balance (mwea) - mb_mwea = ((glacier_area * self.glac_bin_massbalclim[:,12*year:12*(year+1)].sum(1)).sum() / - glacier_area.sum()) + mb_mwea = ( + glacier_area + * self.glac_bin_massbalclim[ + :, 12 * year : 12 * (year + 1) + ].sum(1) + ).sum() / glacier_area.sum() else: mb_max_loss = 0 mb_mwea = 0 if len(glac_idx) > 0: # Quality control for thickness - if hasattr(fls[fl_id], 'thick'): + if hasattr(fls[fl_id], "thick"): thickness = fls[fl_id].thick glacier_area[thickness == 0] = 0 section = fls[fl_id].section @@ -704,154 +1206,233 @@ def _convert_glacwide_results(self, year, glacier_area, heights, # Glacier-wide area (m2) self.glac_wide_area_annual[year] = glacier_area.sum() # Glacier-wide volume (m3) - self.glac_wide_volume_annual[year] = (section * fls[fl_id].dx_meter).sum() + self.glac_wide_volume_annual[year] = ( + section * fls[fl_id].dx_meter + ).sum() else: # Glacier-wide area (m2) self.glac_wide_area_annual[year] = glacier_area.sum() # Glacier-wide temperature (degC) - self.glac_wide_temp[12*year:12*(year+1)] = ( - (self.bin_temp[:,12*year:12*(year+1)][glac_idx] * glacier_area_monthly[glac_idx]).sum(0) / - glacier_area.sum()) + self.glac_wide_temp[12 * year : 12 * (year + 1)] = ( + self.bin_temp[:, 12 * year : 12 * (year + 1)][glac_idx] + * glacier_area_monthly[glac_idx] + ).sum(0) / glacier_area.sum() # Glacier-wide precipitation (m3) - self.glac_wide_prec[12*year:12*(year+1)] = ( - (self.bin_prec[:,12*year:12*(year+1)][glac_idx] * glacier_area_monthly[glac_idx]).sum(0)) + self.glac_wide_prec[12 * year : 12 * (year + 1)] = ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][glac_idx] + * glacier_area_monthly[glac_idx] + ).sum(0) # Glacier-wide accumulation (m3 w.e.) - self.glac_wide_acc[12*year:12*(year+1)] = ( - (self.bin_acc[:,12*year:12*(year+1)][glac_idx] * glacier_area_monthly[glac_idx]).sum(0)) + self.glac_wide_acc[12 * year : 12 * (year + 1)] = ( + self.bin_acc[:, 12 * year : 12 * (year + 1)][glac_idx] + * glacier_area_monthly[glac_idx] + ).sum(0) # Glacier-wide refreeze (m3 w.e.) - self.glac_wide_refreeze[12*year:12*(year+1)] = ( - (self.glac_bin_refreeze[:,12*year:12*(year+1)][glac_idx] * glacier_area_monthly[glac_idx]).sum(0)) + self.glac_wide_refreeze[12 * year : 12 * (year + 1)] = ( + self.glac_bin_refreeze[:, 12 * year : 12 * (year + 1)][ + glac_idx + ] + * glacier_area_monthly[glac_idx] + ).sum(0) # Glacier-wide melt (m3 w.e.) - self.glac_wide_melt[12*year:12*(year+1)] = ( - (self.glac_bin_melt[:,12*year:12*(year+1)][glac_idx] * glacier_area_monthly[glac_idx]).sum(0)) + self.glac_wide_melt[12 * year : 12 * (year + 1)] = ( + self.glac_bin_melt[:, 12 * year : 12 * (year + 1)][glac_idx] + * glacier_area_monthly[glac_idx] + ).sum(0) # Glacier-wide total mass balance (m3 w.e.) - self.glac_wide_massbaltotal[12*year:12*(year+1)] = ( - self.glac_wide_acc[12*year:12*(year+1)] + self.glac_wide_refreeze[12*year:12*(year+1)] - - self.glac_wide_melt[12*year:12*(year+1)] - self.glac_wide_frontalablation[12*year:12*(year+1)]) + self.glac_wide_massbaltotal[12 * year : 12 * (year + 1)] = ( + self.glac_wide_acc[12 * year : 12 * (year + 1)] + + self.glac_wide_refreeze[12 * year : 12 * (year + 1)] + - self.glac_wide_melt[12 * year : 12 * (year + 1)] + - self.glac_wide_frontalablation[12 * year : 12 * (year + 1)] + ) # If mass loss more negative than glacier mass, reduce melt so glacier completely melts (no excess) if icethickness_t0 is not None and mb_mwea < mb_max_loss: - melt_yr_raw = self.glac_wide_melt[12*year:12*(year+1)].sum() - melt_yr_max = (self.glac_wide_volume_annual[year] - * pygem_prms['constants']['density_ice'] / pygem_prms['constants']['density_water'] + - self.glac_wide_acc[12*year:12*(year+1)].sum() + - self.glac_wide_refreeze[12*year:12*(year+1)].sum()) + melt_yr_raw = self.glac_wide_melt[ + 12 * year : 12 * (year + 1) + ].sum() + melt_yr_max = ( + self.glac_wide_volume_annual[year] + * pygem_prms["constants"]["density_ice"] + / pygem_prms["constants"]["density_water"] + + self.glac_wide_acc[12 * year : 12 * (year + 1)].sum() + + self.glac_wide_refreeze[ + 12 * year : 12 * (year + 1) + ].sum() + ) melt_frac = melt_yr_max / melt_yr_raw # Update glacier-wide melt (m3 w.e.) - self.glac_wide_melt[12*year:12*(year+1)] = self.glac_wide_melt[12*year:12*(year+1)] * melt_frac - - + self.glac_wide_melt[12 * year : 12 * (year + 1)] = ( + self.glac_wide_melt[12 * year : 12 * (year + 1)] + * melt_frac + ) + # Glacier-wide runoff (m3) - self.glac_wide_runoff[12*year:12*(year+1)] = ( - self.glac_wide_prec[12*year:12*(year+1)] + self.glac_wide_melt[12*year:12*(year+1)] - - self.glac_wide_refreeze[12*year:12*(year+1)]) + self.glac_wide_runoff[12 * year : 12 * (year + 1)] = ( + self.glac_wide_prec[12 * year : 12 * (year + 1)] + + self.glac_wide_melt[12 * year : 12 * (year + 1)] + - self.glac_wide_refreeze[12 * year : 12 * (year + 1)] + ) # Snow line altitude (m a.s.l.) - heights_monthly = heights[:,np.newaxis].repeat(12, axis=1) + heights_monthly = heights[:, np.newaxis].repeat(12, axis=1) snow_mask = np.zeros(heights_monthly.shape) - snow_mask[self.glac_bin_snowpack[:,12*year:12*(year+1)] > 0] = 1 + snow_mask[ + self.glac_bin_snowpack[:, 12 * year : 12 * (year + 1)] > 0 + ] = 1 heights_monthly_wsnow = heights_monthly * snow_mask heights_monthly_wsnow[heights_monthly_wsnow == 0] = np.nan heights_change = np.zeros(heights.shape) heights_change[0:-1] = heights[0:-1] - heights[1:] try: snowline_idx = np.nanargmin(heights_monthly_wsnow, axis=0) - self.glac_wide_snowline[12*year:12*(year+1)] = heights[snowline_idx] - heights_change[snowline_idx] / 2 + self.glac_wide_snowline[12 * year : 12 * (year + 1)] = ( + heights[snowline_idx] - heights_change[snowline_idx] / 2 + ) except: - snowline_idx = np.zeros((heights_monthly_wsnow.shape[1])).astype(int) + snowline_idx = np.zeros( + (heights_monthly_wsnow.shape[1]) + ).astype(int) snowline_idx_nan = [] for ncol in range(heights_monthly_wsnow.shape[1]): - if ~np.isnan(heights_monthly_wsnow[:,ncol]).all(): - snowline_idx[ncol] = np.nanargmin(heights_monthly_wsnow[:,ncol]) + if ~np.isnan(heights_monthly_wsnow[:, ncol]).all(): + snowline_idx[ncol] = np.nanargmin( + heights_monthly_wsnow[:, ncol] + ) else: snowline_idx_nan.append(ncol) - heights_manual = heights[snowline_idx] - heights_change[snowline_idx] / 2 + heights_manual = ( + heights[snowline_idx] - heights_change[snowline_idx] / 2 + ) heights_manual[snowline_idx_nan] = np.nan # this line below causes a potential All-NaN slice encountered issue at some time steps - self.glac_wide_snowline[12*year:12*(year+1)] = heights_manual + self.glac_wide_snowline[12 * year : 12 * (year + 1)] = ( + heights_manual + ) # Equilibrium line altitude (m a.s.l.) ela_mask = np.zeros(heights.shape) - ela_mask[self.glac_bin_massbalclim_annual[:,year] > 0] = 1 + ela_mask[self.glac_bin_massbalclim_annual[:, year] > 0] = 1 ela_onlypos = heights * ela_mask - ela_onlypos[ela_onlypos == 0] = np.nan + ela_onlypos[ela_onlypos == 0] = np.nan if np.isnan(ela_onlypos).all(): self.glac_wide_ELA_annual[year] = np.nan else: ela_idx = np.nanargmin(ela_onlypos) - self.glac_wide_ELA_annual[year] = heights[ela_idx] - heights_change[ela_idx] / 2 + self.glac_wide_ELA_annual[year] = ( + heights[ela_idx] - heights_change[ela_idx] / 2 + ) - # ===== Off-glacier ==== - offglac_idx = np.where(self.offglac_bin_area_annual[:,year] > 0)[0] + # ===== Off-glacier ==== + offglac_idx = np.where(self.offglac_bin_area_annual[:, year] > 0)[0] if option_areaconstant == False and len(offglac_idx) > 0: - offglacier_area_monthly = self.offglac_bin_area_annual[:,year][:,np.newaxis].repeat(12,axis=1) + offglacier_area_monthly = self.offglac_bin_area_annual[:, year][ + :, np.newaxis + ].repeat(12, axis=1) # Off-glacier precipitation (m3) - self.offglac_wide_prec[12*year:12*(year+1)] = ( - (self.bin_prec[:,12*year:12*(year+1)][offglac_idx] * offglacier_area_monthly[offglac_idx]).sum(0)) + self.offglac_wide_prec[12 * year : 12 * (year + 1)] = ( + self.bin_prec[:, 12 * year : 12 * (year + 1)][offglac_idx] + * offglacier_area_monthly[offglac_idx] + ).sum(0) # Off-glacier melt (m3 w.e.) - self.offglac_wide_melt[12*year:12*(year+1)] = ( - (self.offglac_bin_melt[:,12*year:12*(year+1)][offglac_idx] * offglacier_area_monthly[offglac_idx] - ).sum(0)) + self.offglac_wide_melt[12 * year : 12 * (year + 1)] = ( + self.offglac_bin_melt[:, 12 * year : 12 * (year + 1)][ + offglac_idx + ] + * offglacier_area_monthly[offglac_idx] + ).sum(0) # Off-glacier refreeze (m3 w.e.) - self.offglac_wide_refreeze[12*year:12*(year+1)] = ( - (self.offglac_bin_refreeze[:,12*year:12*(year+1)][offglac_idx] * offglacier_area_monthly[offglac_idx] - ).sum(0)) + self.offglac_wide_refreeze[12 * year : 12 * (year + 1)] = ( + self.offglac_bin_refreeze[:, 12 * year : 12 * (year + 1)][ + offglac_idx + ] + * offglacier_area_monthly[offglac_idx] + ).sum(0) # Off-glacier runoff (m3) - self.offglac_wide_runoff[12*year:12*(year+1)] = ( - self.offglac_wide_prec[12*year:12*(year+1)] + self.offglac_wide_melt[12*year:12*(year+1)] - - self.offglac_wide_refreeze[12*year:12*(year+1)]) + self.offglac_wide_runoff[12 * year : 12 * (year + 1)] = ( + self.offglac_wide_prec[12 * year : 12 * (year + 1)] + + self.offglac_wide_melt[12 * year : 12 * (year + 1)] + - self.offglac_wide_refreeze[12 * year : 12 * (year + 1)] + ) # Off-glacier snowpack (m3 w.e.) - self.offglac_wide_snowpack[12*year:12*(year+1)] = ( - (self.offglac_bin_snowpack[:,12*year:12*(year+1)][offglac_idx] * offglacier_area_monthly[offglac_idx] - ).sum(0)) - - + self.offglac_wide_snowpack[12 * year : 12 * (year + 1)] = ( + self.offglac_bin_snowpack[:, 12 * year : 12 * (year + 1)][ + offglac_idx + ] + * offglacier_area_monthly[offglac_idx] + ).sum(0) + def ensure_mass_conservation(self, diag): """ - Ensure mass conservation that may result from using OGGM's glacier dynamics model. This will be resolved on an + Ensure mass conservation that may result from using OGGM's glacier dynamics model. This will be resolved on an annual basis, and since the glacier dynamics are updated annually, the melt and runoff will be adjusted on a monthly-scale based on percent changes. - + OGGM's dynamic model limits mass loss based on the ice thickness and flux divergence. As a result, the actual volume change, glacier runoff, glacier melt, etc. may be less than that recorded by the mb_model. For PyGEM this is important because the glacier runoff and all parameters should be mass conserving. - - Note: other dynamical models (e.g., mass redistribution curves, volume-length-area scaling) are based on the + + Note: other dynamical models (e.g., mass redistribution curves, volume-length-area scaling) are based on the total volume change and therefore do not impose limitations like this because they do not estimate the flux divergence. As a result, they may systematically overestimate mass loss compared to OGGM's dynamical model. """ - # Compute difference between volume change - vol_change_annual_mbmod = (self.glac_wide_massbaltotal.reshape(-1,12).sum(1) * - pygem_prms['constants']['density_water'] / pygem_prms['constants']['density_ice']) + # Compute difference between volume change + vol_change_annual_mbmod = ( + self.glac_wide_massbaltotal.reshape(-1, 12).sum(1) + * pygem_prms["constants"]["density_water"] + / pygem_prms["constants"]["density_ice"] + ) vol_change_annual_diag = np.zeros(vol_change_annual_mbmod.shape) - vol_change_annual_diag[0:diag.volume_m3.values[1:].shape[0]] = diag.volume_m3.values[1:] - diag.volume_m3.values[:-1] - vol_change_annual_dif = vol_change_annual_diag - vol_change_annual_mbmod + vol_change_annual_diag[0 : diag.volume_m3.values[1:].shape[0]] = ( + diag.volume_m3.values[1:] - diag.volume_m3.values[:-1] + ) + vol_change_annual_dif = ( + vol_change_annual_diag - vol_change_annual_mbmod + ) # Reduce glacier melt by the difference - vol_change_annual_mbmod_melt = (self.glac_wide_melt.reshape(-1,12).sum(1) * - pygem_prms['constants']['density_water'] / pygem_prms['constants']['density_ice']) - vol_change_annual_melt_reduction = np.zeros(vol_change_annual_mbmod.shape) + vol_change_annual_mbmod_melt = ( + self.glac_wide_melt.reshape(-1, 12).sum(1) + * pygem_prms["constants"]["density_water"] + / pygem_prms["constants"]["density_ice"] + ) + vol_change_annual_melt_reduction = np.zeros( + vol_change_annual_mbmod.shape + ) chg_idx = vol_change_annual_mbmod.nonzero()[0] chg_idx_posmbmod = vol_change_annual_mbmod_melt.nonzero()[0] chg_idx_melt = list(set(chg_idx).intersection(chg_idx_posmbmod)) - + vol_change_annual_melt_reduction[chg_idx_melt] = ( - 1 - vol_change_annual_dif[chg_idx_melt] / vol_change_annual_mbmod_melt[chg_idx_melt]) - - vol_change_annual_melt_reduction_monthly = np.repeat(vol_change_annual_melt_reduction, 12) - + 1 + - vol_change_annual_dif[chg_idx_melt] + / vol_change_annual_mbmod_melt[chg_idx_melt] + ) + + vol_change_annual_melt_reduction_monthly = np.repeat( + vol_change_annual_melt_reduction, 12 + ) + # Glacier-wide melt (m3 w.e.) - self.glac_wide_melt = self.glac_wide_melt * vol_change_annual_melt_reduction_monthly - + self.glac_wide_melt = ( + self.glac_wide_melt * vol_change_annual_melt_reduction_monthly + ) + # Glacier-wide total mass balance (m3 w.e.) - self.glac_wide_massbaltotal = (self.glac_wide_acc + self.glac_wide_refreeze - self.glac_wide_melt - - self.glac_wide_frontalablation) - + self.glac_wide_massbaltotal = ( + self.glac_wide_acc + + self.glac_wide_refreeze + - self.glac_wide_melt + - self.glac_wide_frontalablation + ) + # Glacier-wide runoff (m3) - self.glac_wide_runoff = self.glac_wide_prec + self.glac_wide_melt - self.glac_wide_refreeze - + self.glac_wide_runoff = ( + self.glac_wide_prec + self.glac_wide_melt - self.glac_wide_refreeze + ) + self.glac_wide_volume_change_ignored_annual = vol_change_annual_dif - # ===== SURFACE TYPE FUNCTIONS ===== def _surfacetypebinsinitial(self, elev_bins): @@ -884,33 +1465,48 @@ def _surfacetypebinsinitial(self, elev_bins): """ surfacetype = np.zeros(self.glacier_area_initial.shape) # Option 1 - initial surface type based on the median elevation - if pygem_prms['mb']['option_surfacetype_initial'] == 1: - surfacetype[(elev_bins < self.glacier_rgi_table.loc['Zmed']) & (self.glacier_area_initial > 0)] = 1 - surfacetype[(elev_bins >= self.glacier_rgi_table.loc['Zmed']) & (self.glacier_area_initial > 0)] = 2 + if pygem_prms["mb"]["option_surfacetype_initial"] == 1: + surfacetype[ + (elev_bins < self.glacier_rgi_table.loc["Zmed"]) + & (self.glacier_area_initial > 0) + ] = 1 + surfacetype[ + (elev_bins >= self.glacier_rgi_table.loc["Zmed"]) + & (self.glacier_area_initial > 0) + ] = 2 # Option 2 - initial surface type based on the mean elevation - elif pygem_prms['mb']['option_surfacetype_initial'] ==2: - surfacetype[(elev_bins < self.glacier_rgi_table['Zmean']) & (self.glacier_area_initial > 0)] = 1 - surfacetype[(elev_bins >= self.glacier_rgi_table['Zmean']) & (self.glacier_area_initial > 0)] = 2 + elif pygem_prms["mb"]["option_surfacetype_initial"] == 2: + surfacetype[ + (elev_bins < self.glacier_rgi_table["Zmean"]) + & (self.glacier_area_initial > 0) + ] = 1 + surfacetype[ + (elev_bins >= self.glacier_rgi_table["Zmean"]) + & (self.glacier_area_initial > 0) + ] = 2 else: - print("This option for 'option_surfacetype' does not exist. Please choose an option that exists. " - + "Exiting model run.\n") + print( + "This option for 'option_surfacetype' does not exist. Please choose an option that exists. " + + "Exiting model run.\n" + ) exit() # Compute firnline index try: # firn in bins >= firnline_idx - firnline_idx = np.where(surfacetype==2)[0][0] + firnline_idx = np.where(surfacetype == 2)[0][0] except: # avoid errors if there is no firn, i.e., the entire glacier is melting - firnline_idx = np.where(surfacetype!=0)[0][-1] + firnline_idx = np.where(surfacetype != 0)[0][-1] # If firn is included, then specify initial firn conditions - if pygem_prms['mb']['include_firn'] == 1: + if pygem_prms["mb"]["include_firn"] == 1: surfacetype[surfacetype == 2] = 3 # everything initially considered snow is considered firn, i.e., the model initially assumes there is no # snow on the surface anywhere. return surfacetype, firnline_idx - - def _surfacetypebinsannual(self, surfacetype, glac_bin_massbalclim_annual, year_index): + def _surfacetypebinsannual( + self, surfacetype, glac_bin_massbalclim_annual, year_index + ): """ Update surface type according to climatic mass balance over the last five years. @@ -954,29 +1550,38 @@ def _surfacetypebinsannual(self, surfacetype, glac_bin_massbalclim_annual, year_ # less than 5 years, then use the average of the existing years. if year_index < 5: # Calculate average annual climatic mass balance since run began - massbal_clim_mwe_runningavg = glac_bin_massbalclim_annual[:,0:year_index+1].mean(1) + massbal_clim_mwe_runningavg = glac_bin_massbalclim_annual[ + :, 0 : year_index + 1 + ].mean(1) else: - massbal_clim_mwe_runningavg = glac_bin_massbalclim_annual[:,year_index-4:year_index+1].mean(1) + massbal_clim_mwe_runningavg = glac_bin_massbalclim_annual[ + :, year_index - 4 : year_index + 1 + ].mean(1) # If the average annual specific climatic mass balance is negative, then the surface type is ice (or debris) - surfacetype[(surfacetype !=0 ) & (massbal_clim_mwe_runningavg <= 0)] = 1 + surfacetype[ + (surfacetype != 0) & (massbal_clim_mwe_runningavg <= 0) + ] = 1 # If the average annual specific climatic mass balance is positive, then the surface type is snow (or firn) surfacetype[(surfacetype != 0) & (massbal_clim_mwe_runningavg > 0)] = 2 # Compute the firnline index try: # firn in bins >= firnline_idx - firnline_idx = np.where(surfacetype==2)[0][0] + firnline_idx = np.where(surfacetype == 2)[0][0] except: # avoid errors if there is no firn, i.e., the entire glacier is melting - firnline_idx = np.where(surfacetype!=0)[0][-1] + firnline_idx = np.where(surfacetype != 0)[0][-1] # Apply surface type model options # If firn surface type option is included, then snow is changed to firn - if pygem_prms['mb']['include_firn'] == 1: + if pygem_prms["mb"]["include_firn"] == 1: surfacetype[surfacetype == 2] = 3 return surfacetype, firnline_idx - - def _surfacetypeDDFdict(self, modelprms, include_firn=pygem_prms['mb']['include_firn'], - option_ddf_firn=pygem_prms['mb']['option_ddf_firn']): + def _surfacetypeDDFdict( + self, + modelprms, + include_firn=pygem_prms["mb"]["include_firn"], + option_ddf_firn=pygem_prms["mb"]["option_ddf_firn"], + ): """ Create a dictionary of surface type and its respective DDF. @@ -1002,12 +1607,15 @@ def _surfacetypeDDFdict(self, modelprms, include_firn=pygem_prms['mb']['include_ Dictionary relating the surface types with their respective degree day factors """ surfacetype_ddf_dict = { - 0: modelprms['ddfsnow'], - 1: modelprms['ddfice'], - 2: modelprms['ddfsnow']} + 0: modelprms["ddfsnow"], + 1: modelprms["ddfice"], + 2: modelprms["ddfsnow"], + } if include_firn: if option_ddf_firn == 0: - surfacetype_ddf_dict[3] = modelprms['ddfsnow'] + surfacetype_ddf_dict[3] = modelprms["ddfsnow"] elif option_ddf_firn == 1: - surfacetype_ddf_dict[3] = np.mean([modelprms['ddfsnow'],modelprms['ddfice']]) - return surfacetype_ddf_dict \ No newline at end of file + surfacetype_ddf_dict[3] = np.mean( + [modelprms["ddfsnow"], modelprms["ddfice"]] + ) + return surfacetype_ddf_dict diff --git a/pygem/mcmc.py b/pygem/mcmc.py index 98420d0f..07eb4cb8 100644 --- a/pygem/mcmc.py +++ b/pygem/mcmc.py @@ -7,31 +7,36 @@ Markov chain Monte Carlo methods """ -import sys + import copy -import torch + +import matplotlib.pyplot as plt import numpy as np +import torch from tqdm import tqdm -import matplotlib.pyplot as plt -import matplotlib.cm as cm + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file torch.set_default_dtype(torch.float64) plt.rcParams["font.family"] = "arial" -plt.rcParams['font.size'] = 8 -plt.rcParams['legend.fontsize'] = 6 +plt.rcParams["font.size"] = 8 +plt.rcParams["legend.fontsize"] = 6 + # z-normalization functions def z_normalize(params, means, std_devs): return (params - means) / std_devs + # inverse z-normalization -def inverse_z_normalize(z_params, means, std_devs): +def inverse_z_normalize(z_params, means, std_devs): return z_params * std_devs + means + def log_normal_density(x, **kwargs): """ Computes the log probability density of a normal distribution. @@ -44,7 +49,7 @@ def log_normal_density(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma = kwargs['mu'], kwargs['sigma'] + mu, sigma = kwargs["mu"], kwargs["sigma"] # flatten arrays and get dimensionality x = x.flatten() @@ -52,11 +57,14 @@ def log_normal_density(x, **kwargs): sigma = sigma.flatten() k = mu.shape[-1] - return torch.tensor([ - -k/2.*torch.log(torch.tensor(2*np.pi)) - - torch.log(sigma).nansum() - - 0.5*(((x-mu)/sigma)**2).nansum() - ]) + return torch.tensor( + [ + -k / 2.0 * torch.log(torch.tensor(2 * np.pi)) + - torch.log(sigma).nansum() + - 0.5 * (((x - mu) / sigma) ** 2).nansum() + ] + ) + def log_gamma_density(x, **kwargs): """ @@ -70,8 +78,14 @@ def log_gamma_density(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - alpha, beta = kwargs['alpha'], kwargs['beta'] # shape, scale - return alpha * torch.log(beta) + (alpha - 1) * torch.log(x) - beta * x - torch.lgamma(alpha) + alpha, beta = kwargs["alpha"], kwargs["beta"] # shape, scale + return ( + alpha * torch.log(beta) + + (alpha - 1) * torch.log(x) + - beta * x + - torch.lgamma(alpha) + ) + def log_truncated_normal(x, **kwargs): """ @@ -87,46 +101,61 @@ def log_truncated_normal(x, **kwargs): Returns: Log probability density at the given input tensor x. """ - mu, sigma, lo, hi = kwargs['mu'], kwargs['sigma'], kwargs['low'], kwargs['high'] + mu, sigma, lo, hi = ( + kwargs["mu"], + kwargs["sigma"], + kwargs["low"], + kwargs["high"], + ) # Standardize standard_x = (x - mu) / sigma standard_a = (lo - mu) / sigma standard_b = (hi - mu) / sigma - + # PDF of the standard normal distribution pdf = torch.exp(-0.5 * standard_x**2) / np.sqrt(2 * torch.pi) - + # CDF of the standard normal distribution using the error function cdf_upper = 0.5 * (1 + torch.erf(standard_b / np.sqrt(2))) cdf_lower = 0.5 * (1 + torch.erf(standard_a / np.sqrt(2))) - + normalization = cdf_upper - cdf_lower - + return torch.log(pdf) - torch.log(normalization) + # mapper dictionary - maps to appropriate log probability density function for given distribution `type` log_prob_fxn_map = { - 'normal': log_normal_density, - 'gamma': log_gamma_density, - 'truncnormal': log_truncated_normal + "normal": log_normal_density, + "gamma": log_gamma_density, + "truncnormal": log_truncated_normal, } + # mass balance posterior class class mbPosterior: - def __init__(self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kwargs): + def __init__( + self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kwargs + ): # obs will be passed as a list, where each item is a tuple with the first element being the mean observation, and the second being the variance self.obs = obs self.priors = copy.deepcopy(priors) self.mb_func = mb_func self.mb_args = mb_args - self.potential_functions = potential_fxns if potential_fxns is not None else [] + self.potential_functions = ( + potential_fxns if potential_fxns is not None else [] + ) self.preds = None self.check_priors() # get mean and std for each parameter type - self.means = torch.tensor([params['mu'] for params in self.priors.values()]) - self.stds = torch.tensor([params['sigma'] for params in self.priors.values()]) - + self.means = torch.tensor( + [params["mu"] for params in self.priors.values()] + ) + self.stds = torch.tensor( + [params["sigma"] for params in self.priors.values()] + ) + # check priors. remove any subkeys that have a `None` value, and ensure that we have a mean and standard deviation for and gamma distributions def check_priors(self): for k in list(self.priors.keys()): @@ -135,24 +164,34 @@ def check_priors(self): if value is None: keys_rm.append(i) # Add key to remove list # ensure torch tensor objects - elif isinstance(value,str) and 'inf' in value: + elif isinstance(value, str) and "inf" in value: self.priors[k][i] = torch.tensor([float(value)]) - elif isinstance(value,float): + elif isinstance(value, float): self.priors[k][i] = torch.tensor([self.priors[k][i]]) # Remove the keys outside of the iteration for i in keys_rm: del self.priors[k][i] for k in self.priors.keys(): - if self.priors[k]['type'] == 'gamma' and 'mu' not in self.priors[k].keys(): - self.priors[k]['mu'] = self.priors[k]['alpha'] / self.priors[k]['beta'] - self.priors[k]['sigma'] = float(np.sqrt(self.priors[k]['alpha']) / self.priors[k]['beta']) + if ( + self.priors[k]["type"] == "gamma" + and "mu" not in self.priors[k].keys() + ): + self.priors[k]["mu"] = ( + self.priors[k]["alpha"] / self.priors[k]["beta"] + ) + self.priors[k]["sigma"] = float( + np.sqrt(self.priors[k]["alpha"]) / self.priors[k]["beta"] + ) # update modelprms for evaluation def update_modelprms(self, m): - for i, k in enumerate(['tbias','kp','ddfsnow']): + for i, k in enumerate(["tbias", "kp", "ddfsnow"]): self.mb_args[1][k] = float(m[i]) - self.mb_args[1]['ddfice'] = self.mb_args[1]['ddfsnow'] / pygem_prms['sim']['params']['ddfsnow_iceratio'] + self.mb_args[1]["ddfice"] = ( + self.mb_args[1]["ddfsnow"] + / pygem_prms["sim"]["params"]["ddfsnow_iceratio"] + ) # get mb_pred def get_mb_pred(self, m): @@ -163,14 +202,16 @@ def get_mb_pred(self, m): self.preds = self.mb_func([*m]) if not isinstance(self.preds, tuple): self.preds = [self.preds] - self.preds = [torch.tensor(item) for item in self.preds] # make all preds torch.tensor() objects + self.preds = [ + torch.tensor(item) for item in self.preds + ] # make all preds torch.tensor() objects # get total log prior density def log_prior(self, m): log_prior = [] for i, (key, params) in enumerate(self.priors.items()): params_copy = params.copy() - prior_type = params_copy.pop('type') + prior_type = params_copy.pop("type") function_to_call = log_prob_fxn_map[prior_type] log_prior.append(function_to_call(m[i], **params_copy)) log_prior = torch.stack(log_prior).sum() @@ -180,21 +221,28 @@ def log_prior(self, m): def log_likelihood(self): log_likehood = 0 for i, pred in enumerate(self.preds): - log_likehood+=log_normal_density(self.obs[i][0], **{'mu': pred, 'sigma': self.obs[i][1]}) + log_likehood += log_normal_density( + self.obs[i][0], **{"mu": pred, "sigma": self.obs[i][1]} + ) return log_likehood - + # get log potential (sum up as any declared potential functions) def log_potential(self, m): log_potential = 0 for potential_function in self.potential_functions: - log_potential += potential_function(*m, **{'massbal':self.preds[0]}) + log_potential += potential_function( + *m, **{"massbal": self.preds[0]} + ) return log_potential # get log posterior (sum of log prior, log likelihood and log potential) def log_posterior(self, m): # anytime log_posterior is called for a new step, calculate the predicted mass balance self.get_mb_pred(m) - return self.log_prior(m) + self.log_likelihood() + self.log_potential(m), self.preds + return self.log_prior(m) + self.log_likelihood() + self.log_potential( + m + ), self.preds + # Metropolis-Hastings Markov chain Monte Carlo class class Metropolis: @@ -212,7 +260,7 @@ def __init__(self, means, stds): self.means = means self.stds = stds - def get_n_rm(self, tol=.1): + def get_n_rm(self, tol=0.1): """ get the number of samples from the beginning of the chain where the sampler is stuck Parameters: @@ -232,40 +280,54 @@ def get_n_rm(self, tol=.1): n_rms.append(count) self.n_rm = max(n_rms) return - + def rm_stuck_samples(self): """ remove stuck samples at the beginning of the chain """ - self.P_chain = self.P_chain[self.n_rm:] - self.m_chain = self.m_chain[self.n_rm:] - self.m_primes = self.m_primes[self.n_rm:] - self.steps = self.steps[self.n_rm:] - self.acceptance = self.acceptance[self.n_rm:] + self.P_chain = self.P_chain[self.n_rm :] + self.m_chain = self.m_chain[self.n_rm :] + self.m_primes = self.m_primes[self.n_rm :] + self.steps = self.steps[self.n_rm :] + self.acceptance = self.acceptance[self.n_rm :] for j in self.preds_primes.keys(): - self.preds_primes[j] = self.preds_primes[j][self.n_rm:] - self.preds_chain[j] = self.preds_chain[j][self.n_rm:] + self.preds_primes[j] = self.preds_primes[j][self.n_rm :] + self.preds_chain[j] = self.preds_chain[j][self.n_rm :] return - def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_factor=1, trim=True, progress_bar=False): + def sample( + self, + m_0, + log_posterior, + n_samples=1000, + h=0.1, + burnin=0, + thin_factor=1, + trim=True, + progress_bar=False, + ): # Compute initial unscaled log-posterior - P_0, pred_0 = log_posterior(inverse_z_normalize(m_0, self.means, self.stds)) + P_0, pred_0 = log_posterior( + inverse_z_normalize(m_0, self.means, self.stds) + ) n = len(m_0) # Create a tqdm progress bar if enabled pbar = tqdm(total=n_samples) if progress_bar else None - i=0 + i = 0 # Draw samples while i < n_samples: # Propose new value according to # proposal distribution Q(m) = N(m_0,h) - step = torch.randn(n)*h + step = torch.randn(n) * h m_prime = m_0 + step # Compute new unscaled log-posterior - P_1, pred_1 = log_posterior(inverse_z_normalize(m_prime, self.means, self.stds)) + P_1, pred_1 = log_posterior( + inverse_z_normalize(m_prime, self.means, self.stds) + ) # Compute logarithm of probability ratio log_ratio = P_1 - P_0 @@ -275,7 +337,7 @@ def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_facto # If proposed value is more probable than current value, accept. # If not, then accept proportional to the probability ratios - if ratio>torch.rand(1): + if ratio > torch.rand(1): m_0 = m_prime P_0 = P_1 pred_0 = pred_1 @@ -283,32 +345,38 @@ def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_facto self.naccept += 1 # Only append to the chain if we're past burn-in. - if i>burnin: + if i > burnin: # Only append every j-th sample to the chain - if i%thin_factor==0: + if i % thin_factor == 0: self.steps.append(step) self.P_chain.append(P_0) self.m_chain.append(m_0) self.m_primes.append(m_prime) - self.acceptance.append(self.naccept / (i + (thin_factor*self.n_rm))) + self.acceptance.append( + self.naccept / (i + (thin_factor * self.n_rm)) + ) for j in range(len(pred_1)): if j not in self.preds_chain.keys(): - self.preds_chain[j]=[] - self.preds_primes[j]=[] + self.preds_chain[j] = [] + self.preds_primes[j] = [] self.preds_chain[j].append(pred_0[j]) self.preds_primes[j].append(pred_1[j]) # trim off any initial steps that are stagnant - if (i == (n_samples-1)) and (trim): + if (i == (n_samples - 1)) and (trim): self.get_n_rm() if self.n_rm > 0: if self.n_rm < len(self.m_chain) - 1: self.rm_stuck_samples() - i-=int((self.n_rm)*thin_factor) # back track the iterator - trim = False # set trim to False as to only perform one time + i -= int( + (self.n_rm) * thin_factor + ) # back track the iterator + trim = ( + False # set trim to False as to only perform one time + ) # increment iterator - i+=1 + i += 1 # update progress bar if pbar: @@ -318,15 +386,19 @@ def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_facto if pbar: pbar.close() - return torch.vstack(self.m_chain), \ - self.preds_chain, \ - torch.vstack(self.m_primes), \ - self.preds_primes, \ - torch.vstack(self.steps), \ - self.acceptance - + return ( + torch.vstack(self.m_chain), + self.preds_chain, + torch.vstack(self.m_primes), + self.preds_primes, + torch.vstack(self.steps), + self.acceptance, + ) + + ### some other useful functions ### + def effective_n(x): """ Compute the effective sample size of a trace. @@ -349,11 +421,11 @@ def effective_n(x): # detrend trace using mean to be consistent with statistics # definition of autocorrelation x = np.asarray(x) - x = (x - x.mean()) + x = x - x.mean() # compute autocorrelation (note: only need second half since # they are symmetric) - rho = np.correlate(x, x, mode='full') - rho = rho[len(rho)//2:] + rho = np.correlate(x, x, mode="full") + rho = rho[len(rho) // 2 :] # normalize the autocorrelation values # note: rho[0] is the variance * n_samples, so this is consistent # with the statistics definition of autocorrelation on wikipedia @@ -367,14 +439,24 @@ def effective_n(x): n = len(x) while not negative_autocorr and (t < n): if not t % 2: - negative_autocorr = sum(rho_norm[t-1:t+1]) < 0 + negative_autocorr = sum(rho_norm[t - 1 : t + 1]) < 0 t += 1 - return int(n / (1 + 2*rho_norm[1:t].sum())) + return int(n / (1 + 2 * rho_norm[1:t].sum())) except: return None -def plot_chain(m_primes, m_chain, mb_obs, ar, title, ms=1, fontsize=8, show=False, fpath=None): +def plot_chain( + m_primes, + m_chain, + mb_obs, + ar, + title, + ms=1, + fontsize=8, + show=False, + fpath=None, +): # Plot the trace of the parameters fig, axes = plt.subplots(5, 1, figsize=(6, 8), sharex=True) m_chain = m_chain.detach().numpy() @@ -383,53 +465,110 @@ def plot_chain(m_primes, m_chain, mb_obs, ar, title, ms=1, fontsize=8, show=Fals # get n_eff neff = [effective_n(arr) for arr in m_chain.T] - axes[0].plot([],[],label=f'mean={np.mean(m_chain[:, 0]):.3f}\nstd={np.std(m_chain[:, 0]):.3f}') - l0 = axes[0].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - - axes[0].plot(m_primes[:, 0],'.',ms=ms, label='proposed', c='tab:blue') - axes[0].plot(m_chain[:, 0],'.',ms=ms, label='accepted', c='tab:orange') + axes[0].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 0]):.3f}\nstd={np.std(m_chain[:, 0]):.3f}", + ) + l0 = axes[0].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + + axes[0].plot(m_primes[:, 0], ".", ms=ms, label="proposed", c="tab:blue") + axes[0].plot(m_chain[:, 0], ".", ms=ms, label="accepted", c="tab:orange") hands, ls = axes[0].get_legend_handles_labels() # axes[0].add_artist(leg) - axes[0].set_ylabel(r'$T_{bias}$', fontsize=fontsize) - - axes[1].plot(m_primes[:, 1],'.',ms=ms, c='tab:blue') - axes[1].plot(m_chain[:, 1],'.',ms=ms, c='tab:orange') - axes[1].plot([],[],label=f'mean={np.mean(m_chain[:, 1]):.3f}\nstd={np.std(m_chain[:, 1]):.3f}') - l1 = axes[1].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - axes[1].set_ylabel(r'$K_p$', fontsize=fontsize) - - axes[2].plot(m_primes[:, 2],'.',ms=ms, c='tab:blue') - axes[2].plot(m_chain[:, 2],'.',ms=ms, c='tab:orange') - axes[2].plot([],[],label=f'mean={np.mean(m_chain[:, 2]):.3f}\nstd={np.std(m_chain[:, 2]):.3f}') - l2 = axes[2].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - axes[2].set_ylabel(r'$fsnow$', fontsize=fontsize) - - axes[3].fill_between(np.arange(len(ar)),mb_obs[0]-(2*mb_obs[1]),mb_obs[0]+(2*mb_obs[1]),color='grey',alpha=.3) - axes[3].fill_between(np.arange(len(ar)),mb_obs[0]-mb_obs[1],mb_obs[0]+mb_obs[1],color='grey',alpha=.3) - axes[3].plot(m_primes[:, 3],'.',ms=ms, c='tab:blue') - axes[3].plot(m_chain[:, 3],'.',ms=ms, c='tab:orange') - axes[3].plot([],[],label=f'mean={np.mean(m_chain[:, 3]):.3f}\nstd={np.std(m_chain[:, 3]):.3f}') - l3 = axes[3].legend(loc='upper right',handlelength=0, borderaxespad=0, fontsize=fontsize) - axes[3].set_ylabel(r'$\dot{{b}}$', fontsize=fontsize) - - axes[4].plot(ar,'tab:orange', lw=1) - axes[4].plot(np.convolve(ar, np.ones(100)/100, mode='valid'), 'k', label='moving avg.', lw=1) - l4 = axes[4].legend(loc='upper left',handlelength=.5, borderaxespad=0, fontsize=fontsize) - axes[4].set_ylabel(r'$AR$', fontsize=fontsize) + axes[0].set_ylabel(r"$T_{bias}$", fontsize=fontsize) + + axes[1].plot(m_primes[:, 1], ".", ms=ms, c="tab:blue") + axes[1].plot(m_chain[:, 1], ".", ms=ms, c="tab:orange") + axes[1].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 1]):.3f}\nstd={np.std(m_chain[:, 1]):.3f}", + ) + l1 = axes[1].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[1].set_ylabel(r"$K_p$", fontsize=fontsize) + + axes[2].plot(m_primes[:, 2], ".", ms=ms, c="tab:blue") + axes[2].plot(m_chain[:, 2], ".", ms=ms, c="tab:orange") + axes[2].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 2]):.3f}\nstd={np.std(m_chain[:, 2]):.3f}", + ) + l2 = axes[2].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[2].set_ylabel(r"$fsnow$", fontsize=fontsize) + + axes[3].fill_between( + np.arange(len(ar)), + mb_obs[0] - (2 * mb_obs[1]), + mb_obs[0] + (2 * mb_obs[1]), + color="grey", + alpha=0.3, + ) + axes[3].fill_between( + np.arange(len(ar)), + mb_obs[0] - mb_obs[1], + mb_obs[0] + mb_obs[1], + color="grey", + alpha=0.3, + ) + axes[3].plot(m_primes[:, 3], ".", ms=ms, c="tab:blue") + axes[3].plot(m_chain[:, 3], ".", ms=ms, c="tab:orange") + axes[3].plot( + [], + [], + label=f"mean={np.mean(m_chain[:, 3]):.3f}\nstd={np.std(m_chain[:, 3]):.3f}", + ) + l3 = axes[3].legend( + loc="upper right", handlelength=0, borderaxespad=0, fontsize=fontsize + ) + axes[3].set_ylabel(r"$\dot{{b}}$", fontsize=fontsize) + + axes[4].plot(ar, "tab:orange", lw=1) + axes[4].plot( + np.convolve(ar, np.ones(100) / 100, mode="valid"), + "k", + label="moving avg.", + lw=1, + ) + l4 = axes[4].legend( + loc="upper left", handlelength=0.5, borderaxespad=0, fontsize=fontsize + ) + axes[4].set_ylabel(r"$AR$", fontsize=fontsize) for i, ax in enumerate(axes): - ax.xaxis.set_ticks_position('both') - ax.yaxis.set_ticks_position('both') - ax.tick_params(axis="both",direction="inout") - if i==4: + ax.xaxis.set_ticks_position("both") + ax.yaxis.set_ticks_position("both") + ax.tick_params(axis="both", direction="inout") + if i == 4: continue - ax.plot([],[],label=f'n_eff={neff[i]}') + ax.plot([], [], label=f"n_eff={neff[i]}") hands, ls = ax.get_legend_handles_labels() - if i==0: - ax.legend(handles=[hands[1],hands[2],hands[3]], labels=[ls[1],ls[2],ls[3]], loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize) + if i == 0: + ax.legend( + handles=[hands[1], hands[2], hands[3]], + labels=[ls[1], ls[2], ls[3]], + loc="upper left", + borderaxespad=0, + handlelength=0, + fontsize=fontsize, + ) else: - ax.legend(handles=[hands[-1]], labels=[ls[-1]], loc='upper left', borderaxespad=0, handlelength=0, fontsize=fontsize) + ax.legend( + handles=[hands[-1]], + labels=[ls[-1]], + loc="upper left", + borderaxespad=0, + handlelength=0, + fontsize=fontsize, + ) axes[0].add_artist(l0) axes[1].add_artist(l1) @@ -452,16 +591,25 @@ def plot_resid_hist(obs, preds, title, fontsize=8, show=False, fpath=None): # Plot the trace of the parameters fig, axes = plt.subplots(1, 1, figsize=(3, 2)) # subtract obs from preds to get residuals - diffs = np.concatenate([pred.flatten() - obs[0].flatten().numpy() for pred in preds]) + diffs = np.concatenate( + [pred.flatten() - obs[0].flatten().numpy() for pred in preds] + ) # mask nans to avoid error in np.histogram() diffs = diffs[~np.isnan(diffs)] # Calculate histogram counts and bin edges counts, bin_edges = np.histogram(diffs, bins=20) pct = counts / counts.sum() * 100 bin_width = bin_edges[1] - bin_edges[0] - axes.bar(bin_edges[:-1], pct, width=bin_width, edgecolor='black', color='gray', align='edge') - axes.set_xlabel('residuals (pred - obs)', fontsize=fontsize) - axes.set_ylabel('count (%)', fontsize=fontsize) + axes.bar( + bin_edges[:-1], + pct, + width=bin_width, + edgecolor="black", + color="gray", + align="edge", + ) + axes.set_xlabel("residuals (pred - obs)", fontsize=fontsize) + axes.set_ylabel("count (%)", fontsize=fontsize) axes.set_title(title, fontsize=fontsize) plt.tight_layout() plt.subplots_adjust(hspace=0.1, wspace=0) @@ -470,4 +618,4 @@ def plot_resid_hist(obs, preds, title, fontsize=8, show=False, fpath=None): if show: plt.show(block=True) # wait until the figure is closed plt.close(fig) - return \ No newline at end of file + return diff --git a/pygem/oggm_compat.py b/pygem/oggm_compat.py index b3bebdb4..1747fb55 100755 --- a/pygem/oggm_compat.py +++ b/pygem/oggm_compat.py @@ -7,21 +7,27 @@ PYGEM-OGGGM COMPATIBILITY FUNCTIONS """ + import os + +import netCDF4 + # External libraries import numpy as np import pandas as pd -import netCDF4 -from oggm import cfg, utils -from oggm import workflow, tasks -#from oggm import tasks +from oggm import cfg, tasks, utils, workflow + +# from oggm import tasks from oggm.cfg import SEC_IN_YEAR from oggm.core import flowline from oggm.core.massbalance import MassBalanceModel -#from oggm.shop import rgitopo -from pygem.shop import debris, mbdata, icethickness + # Local libraries import pygem.setup.config as config + +# from oggm.shop import rgitopo +from pygem.shop import debris, icethickness, mbdata + # read config pygem_prms = config.read_config() @@ -29,10 +35,16 @@ class CompatGlacDir: def __init__(self, rgiid): self.rgiid = rgiid - -def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrite_gdirs'], prepro_border=pygem_prms['oggm']['border'], - logging_level= pygem_prms['oggm']['logging_level'], has_internet= pygem_prms['oggm']['has_internet'], - working_dir=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}"): + + +def single_flowline_glacier_directory( + rgi_id, + reset=pygem_prms["oggm"]["overwrite_gdirs"], + prepro_border=pygem_prms["oggm"]["border"], + logging_level=pygem_prms["oggm"]["logging_level"], + has_internet=pygem_prms["oggm"]["has_internet"], + working_dir=f"{pygem_prms['root']}/{pygem_prms['oggm']['oggm_gdir_relpath']}", +): """Prepare a GlacierDirectory for PyGEM (single flowline to start with) Parameters @@ -51,73 +63,88 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - if rgi_id.startswith('RGI60-') == False: - rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] + raise ValueError("We expect rgi_id to be a string") + if rgi_id.startswith("RGI60-") == False: + rgi_id = ( + "RGI60-" + + rgi_id.split(".")[0].zfill(2) + + "." + + rgi_id.split(".")[1] + ) else: - raise ValueError('Check RGIId is correct') - + raise ValueError("Check RGIId is correct") + # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS['use_multiprocessing'] = False - + cfg.PARAMS["use_multiprocessing"] = False + # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS['continue_on_error'] = True - + cfg.PARAMS["continue_on_error"] = True + # Has internet - cfg.PARAMS['has_internet'] = has_internet - + cfg.PARAMS["has_internet"] = has_internet + # Set border boundary - cfg.PARAMS['border'] = prepro_border + cfg.PARAMS["border"] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS['dl_verify'] = True - cfg.PARAMS['use_multiple_flowlines'] = False + cfg.PARAMS["dl_verify"] = True + cfg.PARAMS["use_multiple_flowlines"] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS['working_dir'] = working_dir + cfg.PATHS["working_dir"] = working_dir # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle('inversion_flowlines') + gdir.read_pickle("inversion_flowlines") except: reset = True - + if reset: # Start after the prepro task level - base_url = pygem_prms['oggm']['base_url'] + base_url = pygem_prms["oggm"]["base_url"] + + cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + gdir = workflow.init_glacier_directories( + [rgi_id], + from_prepro_level=2, + prepro_border=cfg.PARAMS["border"], + prepro_base_url=base_url, + prepro_rgi_version="62", + )[0] - cfg.PARAMS['has_internet'] = pygem_prms['oggm']['has_internet'] - gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=2, prepro_border=cfg.PARAMS['border'], - prepro_base_url=base_url, prepro_rgi_version='62')[0] - # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath('consensus_mass')): + if not os.path.isfile(gdir.get_filepath("consensus_mass")): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data - if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): + if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): workflow.execute_entity_task(mbdata.mb_df_to_gdir, gdir) # debris thickness and melt enhancement factors - if not os.path.isfile(gdir.get_filepath('debris_ed')) or os.path.isfile(gdir.get_filepath('debris_hd')): + if not os.path.isfile(gdir.get_filepath("debris_ed")) or os.path.isfile( + gdir.get_filepath("debris_hd") + ): workflow.execute_entity_task(debris.debris_to_gdir, gdir) workflow.execute_entity_task(debris.debris_binned, gdir) return gdir - -def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['oggm']['overwrite_gdirs'], - prepro_border=pygem_prms['oggm']['border'], k_calving=1, - logging_level= pygem_prms['oggm']['logging_level'], - has_internet= pygem_prms['oggm']['has_internet'], - working_dir=pygem_prms['root'] + pygem_prms['oggm']['oggm_gdir_relpath'], - facorrected=pygem_prms['setup']['include_frontalablation']): +def single_flowline_glacier_directory_with_calving( + rgi_id, + reset=pygem_prms["oggm"]["overwrite_gdirs"], + prepro_border=pygem_prms["oggm"]["border"], + k_calving=1, + logging_level=pygem_prms["oggm"]["logging_level"], + has_internet=pygem_prms["oggm"]["has_internet"], + working_dir=pygem_prms["root"] + pygem_prms["oggm"]["oggm_gdir_relpath"], + facorrected=pygem_prms["setup"]["include_frontalablation"], +): """Prepare a GlacierDirectory for PyGEM (single flowline to start with) k_calving is free variable! @@ -137,62 +164,74 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg a GlacierDirectory object """ if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - if rgi_id.startswith('RGI60-') == False: - rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] + raise ValueError("We expect rgi_id to be a string") + if rgi_id.startswith("RGI60-") == False: + rgi_id = ( + "RGI60-" + + rgi_id.split(".")[0].zfill(2) + + "." + + rgi_id.split(".")[1] + ) else: - raise ValueError('Check RGIId is correct') + raise ValueError("Check RGIId is correct") # Initialize OGGM and set up the default run parameters cfg.initialize(logging_level=logging_level) # Set multiprocessing to false; otherwise, causes daemonic error due to PyGEM's multiprocessing # - avoids having multiple multiprocessing going on at the same time - cfg.PARAMS['use_multiprocessing'] = False - + cfg.PARAMS["use_multiprocessing"] = False + # Avoid erroneous glaciers (e.g., Centerlines too short or other issues) - cfg.PARAMS['continue_on_error'] = True - + cfg.PARAMS["continue_on_error"] = True + # Has internet - cfg.PARAMS['has_internet'] = has_internet - + cfg.PARAMS["has_internet"] = has_internet + # Set border boundary - cfg.PARAMS['border'] = prepro_border + cfg.PARAMS["border"] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow # because of the huge files so we just turn it off. # Switch it on for real cases! - cfg.PARAMS['dl_verify'] = True - cfg.PARAMS['use_multiple_flowlines'] = False + cfg.PARAMS["dl_verify"] = True + cfg.PARAMS["use_multiple_flowlines"] = False # temporary directory for testing (deleted on computer restart) - cfg.PATHS['working_dir'] = working_dir - + cfg.PATHS["working_dir"] = working_dir + # check if gdir is already processed if not reset: try: gdir = utils.GlacierDirectory(rgi_id) - gdir.read_pickle('inversion_flowlines') + gdir.read_pickle("inversion_flowlines") except: reset = True - + if reset: # Start after the prepro task level - base_url = pygem_prms['oggm']['base_url'] + base_url = pygem_prms["oggm"]["base_url"] + + cfg.PARAMS["has_internet"] = pygem_prms["oggm"]["has_internet"] + gdir = workflow.init_glacier_directories( + [rgi_id], + from_prepro_level=2, + prepro_border=cfg.PARAMS["border"], + prepro_base_url=base_url, + prepro_rgi_version="62", + )[0] - cfg.PARAMS['has_internet'] = pygem_prms['oggm']['has_internet'] - gdir = workflow.init_glacier_directories([rgi_id], from_prepro_level=2, prepro_border=cfg.PARAMS['border'], - prepro_base_url=base_url, prepro_rgi_version='62')[0] - if not gdir.is_tidewater: - raise ValueError(f'{rgi_id} is not tidewater!') + raise ValueError(f"{rgi_id} is not tidewater!") # go through shop tasks to process auxiliary datasets to gdir if necessary # consensus glacier mass - if not os.path.isfile(gdir.get_filepath('consensus_mass')): + if not os.path.isfile(gdir.get_filepath("consensus_mass")): workflow.execute_entity_task(icethickness.consensus_gridded, gdir) # mass balance calibration data (note facorrected kwarg) - if not os.path.isfile(gdir.get_filepath('mb_calib_pygem')): - workflow.execute_entity_task(mbdata.mb_df_to_gdir, gdir, **{"facorrected": facorrected}) + if not os.path.isfile(gdir.get_filepath("mb_calib_pygem")): + workflow.execute_entity_task( + mbdata.mb_df_to_gdir, gdir, **{"facorrected": facorrected} + ) return gdir @@ -202,15 +241,18 @@ def l3_proc(gdir): OGGGM L3 preprocessing steps """ # process climate_hisotrical data to gdir - workflow.execute_entity_task(tasks.process_climate_data, gdir); + workflow.execute_entity_task(tasks.process_climate_data, gdir) # process mb_calib data from geodetic mass balance - workflow.execute_entity_task(tasks.mb_calibration_from_geodetic_mb, - gdir, informed_threestep=True, overwrite_gdir=True, - ); + workflow.execute_entity_task( + tasks.mb_calibration_from_geodetic_mb, + gdir, + informed_threestep=True, + overwrite_gdir=True, + ) # glacier bed inversion - workflow.execute_entity_task(tasks.apparent_mb_from_any_mb, gdir); + workflow.execute_entity_task(tasks.apparent_mb_from_any_mb, gdir) workflow.calibrate_inversion_from_consensus( gdir, apply_fs_on_mismatch=True, @@ -218,25 +260,28 @@ def l3_proc(gdir): filter_inversion_output=True, # this partly filters the overdeepening due to # the equilibrium assumption for retreating glaciers (see. Figure 5 of Maussion et al. 2019) volume_m3_reference=None, # here you could provide your own total volume estimate in m3 - ); + ) # after inversion, merge data from preprocessing tasks form mode_flowlines - workflow.execute_entity_task(tasks.init_present_time_glacier, gdir); + workflow.execute_entity_task(tasks.init_present_time_glacier, gdir) def oggm_spinup(gdir): # perform OGGM dynamic spinup and return flowline model at year 2000 # define mb_model for spinup - workflow.execute_entity_task(tasks.run_dynamic_spinup, - gdir, - spinup_start_yr=1979, # When to start the spinup - minimise_for='area', # what target to match at the RGI date - output_filesuffix='_dynamic_area', # Where to write the output - ye=2020, # When the simulation should stop - # first_guess_t_spinup = , could be passed as input argument for each step in the sampler based on prior tbias, current default first guess is -2 + workflow.execute_entity_task( + tasks.run_dynamic_spinup, + gdir, + spinup_start_yr=1979, # When to start the spinup + minimise_for="area", # what target to match at the RGI date + output_filesuffix="_dynamic_area", # Where to write the output + ye=2020, # When the simulation should stop + # first_guess_t_spinup = , could be passed as input argument for each step in the sampler based on prior tbias, current default first guess is -2 + ) + fmd_dynamic = flowline.FileModel( + gdir.get_filepath("model_geometry", filesuffix="_dynamic_area") ) - fmd_dynamic = flowline.FileModel(gdir.get_filepath('model_geometry', filesuffix='_dynamic_area')) fmd_dynamic.run_until(2000) - return fmd_dynamic.fls # flowlines after dynamic spinup at year 2000 + return fmd_dynamic.fls # flowlines after dynamic spinup at year 2000 def create_empty_glacier_directory(rgi_id): @@ -253,8 +298,8 @@ def create_empty_glacier_directory(rgi_id): """ # RGIId check if type(rgi_id) != str: - raise ValueError('We expect rgi_id to be a string') - assert rgi_id.startswith('RGI60-'), 'Check RGIId starts with RGI60-' + raise ValueError("We expect rgi_id to be a string") + assert rgi_id.startswith("RGI60-"), "Check RGIId starts with RGI60-" # Create empty directory gdir = CompatGlacDir(rgi_id) @@ -275,7 +320,7 @@ def get_glacier_zwh(gdir): a dataframe with the requested data """ - fls = gdir.read_pickle('model_flowlines') + fls = gdir.read_pickle("model_flowlines") z = np.array([]) w = np.array([]) h = np.array([]) @@ -291,10 +336,10 @@ def get_glacier_zwh(gdir): # Output df = pd.DataFrame() - df['z'] = z - df['w'] = w - df['h'] = h - df['dx'] = dx + df["z"] = z + df["w"] = w + df["h"] = h + df["dx"] = dx return df @@ -312,8 +357,8 @@ class RandomLinearMassBalance(MassBalanceModel): oriented programming, I hope that the example below is simple enough. """ - def __init__(self, gdir, grad=3., h_perc=60, sigma_ela=100., seed=None): - """ Initialize. + def __init__(self, gdir, grad=3.0, h_perc=60, sigma_ela=100.0, seed=None): + """Initialize. Parameters ---------- @@ -333,17 +378,18 @@ def __init__(self, gdir, grad=3., h_perc=60, sigma_ela=100., seed=None): self.valid_bounds = [-1e4, 2e4] # in m self.grad = grad self.sigma_ela = sigma_ela - self.hemisphere = 'nh' + self.hemisphere = "nh" self.rng = np.random.RandomState(seed) # Decide on a reference ELA - grids_file = gdir.get_filepath('gridded_data') + grids_file = gdir.get_filepath("gridded_data") with netCDF4.Dataset(grids_file) as nc: - glacier_mask = nc.variables['glacier_mask'][:] - glacier_topo = nc.variables['topo_smoothed'][:] + glacier_mask = nc.variables["glacier_mask"][:] + glacier_topo = nc.variables["topo_smoothed"][:] - self.orig_ela_h = np.percentile(glacier_topo[glacier_mask == 1], - h_perc) + self.orig_ela_h = np.percentile( + glacier_topo[glacier_mask == 1], h_perc + ) self.ela_h_per_year = dict() # empty dictionary def get_random_ela_h(self, year): @@ -364,10 +410,9 @@ def get_random_ela_h(self, year): return ela_h def get_annual_mb(self, heights, year=None, fl_id=None): - # Compute the mass-balance gradient ela_h = self.get_random_ela_h(year) mb = (np.asarray(heights) - ela_h) * self.grad # Convert to units of [m s-1] (meters of ice per second) - return mb / SEC_IN_YEAR / cfg.PARAMS['ice_density'] + return mb / SEC_IN_YEAR / cfg.PARAMS["ice_density"] diff --git a/pygem/output.py b/pygem/output.py index a04e7ec9..2518eab6 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -11,43 +11,54 @@ The two main parent classes are single_glacier(object) and compiled_regional(object) Both of these have several subclasses which will inherit the necessary parent information """ + +import collections +import json +import os from dataclasses import dataclass -from scipy.stats import median_abs_deviation from datetime import datetime + +import cftime import numpy as np import pandas as pd import xarray as xr -import os, types, json, cftime, collections +from scipy.stats import median_abs_deviation + import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file + ### single glacier output parent class ### @dataclass class single_glacier: """ Single glacier output dataset class for the Python Glacier Evolution Model. """ - glacier_rgi_table : pd.DataFrame - dates_table : pd.DataFrame - pygem_version : float - gcm_name : str - scenario : str - realization : str - nsims : int - modelprms : dict - ref_startyear : int - ref_endyear : int - gcm_startyear : int + + glacier_rgi_table: pd.DataFrame + dates_table: pd.DataFrame + pygem_version: float + gcm_name: str + scenario: str + realization: str + nsims: int + modelprms: dict + ref_startyear: int + ref_endyear: int + gcm_startyear: int gcm_endyear: int option_calibration: str option_bias_adjustment: str def __post_init__(self): self.glac_values = np.array([self.glacier_rgi_table.name]) - self.glacier_str = '{0:0.5f}'.format(self.glacier_rgi_table['RGIId_float']) - self.reg_str = str(self.glacier_rgi_table.O1Region).zfill(2) - self.outdir = pygem_prms['root'] + '/Output/simulations/' + self.glacier_str = "{0:0.5f}".format( + self.glacier_rgi_table["RGIId_float"] + ) + self.reg_str = str(self.glacier_rgi_table.O1Region).zfill(2) + self.outdir = pygem_prms["root"] + "/Output/simulations/" self.set_fn() self.set_time_vals() self.model_params_record() @@ -55,23 +66,23 @@ def __post_init__(self): # set output dataset filename def set_fn(self): - self.outfn = self.glacier_str + '_' + self.gcm_name + '_' + self.outfn = self.glacier_str + "_" + self.gcm_name + "_" if self.scenario: - self.outfn += f'{self.scenario}_' + self.outfn += f"{self.scenario}_" if self.realization: - self.outfn += f'{self.realization}_' + self.outfn += f"{self.realization}_" if self.option_calibration: - self.outfn += f'{self.option_calibration}_' + self.outfn += f"{self.option_calibration}_" else: - self.outfn += f'kp{self.modelprms["kp"]}_ddfsnow{self.modelprms["ddfsnow"]}_tbias{self.modelprms["tbias"]}_' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outfn += f'ba{self.option_bias_adjustment}_' + self.outfn += f"kp{self.modelprms['kp']}_ddfsnow{self.modelprms['ddfsnow']}_tbias{self.modelprms['tbias']}_" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outfn += f"ba{self.option_bias_adjustment}_" else: - self.outfn += 'ba0_' + self.outfn += "ba0_" if self.option_calibration: - self.outfn += 'SETS_' - self.outfn += f'{self.gcm_startyear}_' - self.outfn += f'{self.gcm_endyear}_' + self.outfn += "SETS_" + self.outfn += f"{self.gcm_startyear}_" + self.outfn += f"{self.gcm_endyear}_" # return output dataset filename def get_fn(self): @@ -85,34 +96,53 @@ def set_modelprms(self, modelprms): # set dataset time value coordiantes def set_time_vals(self): - if pygem_prms['climate']['gcm_wateryear'] == 'hydro': - self.year_type = 'water year' - self.annual_columns = np.unique(self.dates_table['wateryear'].values)[0:int(self.dates_table.shape[0]/12)] - elif pygem_prms['climate']['gcm_wateryear'] == 'calendar': - self.year_type = 'calendar year' - self.annual_columns = np.unique(self.dates_table['year'].values)[0:int(self.dates_table.shape[0]/12)] - elif pygem_prms['climate']['gcm_wateryear'] == 'custom': - self.year_type = 'custom year' - self.time_values = self.dates_table.loc[pygem_prms['climate']['gcm_spinupyears']*12:self.dates_table.shape[0]+1,'date'].tolist() - self.time_values = [cftime.DatetimeNoLeap(x.year, x.month, x.day) for x in self.time_values] + if pygem_prms["climate"]["gcm_wateryear"] == "hydro": + self.year_type = "water year" + self.annual_columns = np.unique( + self.dates_table["wateryear"].values + )[0 : int(self.dates_table.shape[0] / 12)] + elif pygem_prms["climate"]["gcm_wateryear"] == "calendar": + self.year_type = "calendar year" + self.annual_columns = np.unique(self.dates_table["year"].values)[ + 0 : int(self.dates_table.shape[0] / 12) + ] + elif pygem_prms["climate"]["gcm_wateryear"] == "custom": + self.year_type = "custom year" + self.time_values = self.dates_table.loc[ + pygem_prms["climate"]["gcm_spinupyears"] + * 12 : self.dates_table.shape[0] + 1, + "date", + ].tolist() + self.time_values = [ + cftime.DatetimeNoLeap(x.year, x.month, x.day) + for x in self.time_values + ] # append additional year to self.year_values to account for mass and area at end of period - self.year_values = self.annual_columns[pygem_prms['climate']['gcm_spinupyears']:self.annual_columns.shape[0]] - self.year_values = np.concatenate((self.year_values, np.array([self.annual_columns[-1] + 1]))) + self.year_values = self.annual_columns[ + pygem_prms["climate"][ + "gcm_spinupyears" + ] : self.annual_columns.shape[0] + ] + self.year_values = np.concatenate( + (self.year_values, np.array([self.annual_columns[-1] + 1])) + ) # record all model parameters from run_simualtion and pygem_input def model_params_record(self): # get all locally defined variables from the pygem_prms, excluding imports, functions, and classes self.mdl_params_dict = {} # overwrite variables that are possibly different from pygem_input - self.mdl_params_dict['ref_startyear'] = self.ref_startyear - self.mdl_params_dict['ref_endyear'] = self.ref_endyear - self.mdl_params_dict['gcm_startyear'] = self.gcm_startyear - self.mdl_params_dict['gcm_endyear'] = self.gcm_endyear - self.mdl_params_dict['gcm_name'] = self.gcm_name - self.mdl_params_dict['realization'] = self.realization - self.mdl_params_dict['scenario'] = self.scenario - self.mdl_params_dict['option_calibration'] = self.option_calibration - self.mdl_params_dict['option_bias_adjustment'] = self.option_bias_adjustment + self.mdl_params_dict["ref_startyear"] = self.ref_startyear + self.mdl_params_dict["ref_endyear"] = self.ref_endyear + self.mdl_params_dict["gcm_startyear"] = self.gcm_startyear + self.mdl_params_dict["gcm_endyear"] = self.gcm_endyear + self.mdl_params_dict["gcm_name"] = self.gcm_name + self.mdl_params_dict["realization"] = self.realization + self.mdl_params_dict["scenario"] = self.scenario + self.mdl_params_dict["option_calibration"] = self.option_calibration + self.mdl_params_dict["option_bias_adjustment"] = ( + self.option_bias_adjustment + ) # record manually defined modelprms if calibration option is None if not self.option_calibration: self.update_modelparams_record() @@ -125,47 +155,68 @@ def update_modelparams_record(self): # initialize boilerplate coordinate and attribute dictionaries - these will be the same for both glacier-wide and binned outputs def init_dicts(self): self.output_coords_dict = collections.OrderedDict() - self.output_coords_dict['RGIId'] = collections.OrderedDict([('glac', self.glac_values)]) - self.output_coords_dict['CenLon'] = collections.OrderedDict([('glac', self.glac_values)]) - self.output_coords_dict['CenLat'] = collections.OrderedDict([('glac', self.glac_values)]) - self.output_coords_dict['O1Region'] = collections.OrderedDict([('glac', self.glac_values)]) - self.output_coords_dict['O2Region'] = collections.OrderedDict([('glac', self.glac_values)]) - self.output_coords_dict['Area'] = collections.OrderedDict([('glac', self.glac_values)]) + self.output_coords_dict["RGIId"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) + self.output_coords_dict["CenLon"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) + self.output_coords_dict["CenLat"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) + self.output_coords_dict["O1Region"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) + self.output_coords_dict["O2Region"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) + self.output_coords_dict["Area"] = collections.OrderedDict( + [("glac", self.glac_values)] + ) self.output_attrs_dict = { - 'time': { - 'long_name': 'time', - 'year_type':self.year_type, - 'comment':'start of the month'}, - 'glac': { - 'long_name': 'glacier index', - 'comment': 'glacier index referring to glaciers properties and model results'}, - 'year': { - 'long_name': 'years', - 'year_type': self.year_type, - 'comment': 'years referring to the start of each year'}, - 'RGIId': { - 'long_name': 'Randolph Glacier Inventory ID', - 'comment': 'RGIv6.0'}, - 'CenLon': { - 'long_name': 'center longitude', - 'units': 'degrees E', - 'comment': 'value from RGIv6.0'}, - 'CenLat': { - 'long_name': 'center latitude', - 'units': 'degrees N', - 'comment': 'value from RGIv6.0'}, - 'O1Region': { - 'long_name': 'RGI order 1 region', - 'comment': 'value from RGIv6.0'}, - 'O2Region': { - 'long_name': 'RGI order 2 region', - 'comment': 'value from RGIv6.0'}, - 'Area': { - 'long_name': 'glacier area', - 'units': 'm2', - 'comment': 'value from RGIv6.0'} - } - + "time": { + "long_name": "time", + "year_type": self.year_type, + "comment": "start of the month", + }, + "glac": { + "long_name": "glacier index", + "comment": "glacier index referring to glaciers properties and model results", + }, + "year": { + "long_name": "years", + "year_type": self.year_type, + "comment": "years referring to the start of each year", + }, + "RGIId": { + "long_name": "Randolph Glacier Inventory ID", + "comment": "RGIv6.0", + }, + "CenLon": { + "long_name": "center longitude", + "units": "degrees E", + "comment": "value from RGIv6.0", + }, + "CenLat": { + "long_name": "center latitude", + "units": "degrees N", + "comment": "value from RGIv6.0", + }, + "O1Region": { + "long_name": "RGI order 1 region", + "comment": "value from RGIv6.0", + }, + "O2Region": { + "long_name": "RGI order 2 region", + "comment": "value from RGIv6.0", + }, + "Area": { + "long_name": "glacier area", + "units": "m2", + "comment": "value from RGIv6.0", + }, + } + # create dataset def create_xr_ds(self): # Add variables to empty dataset and merge together @@ -173,15 +224,24 @@ def create_xr_ds(self): self.encoding = {} for vn in self.output_coords_dict.keys(): count_vn += 1 - empty_holder = np.zeros([len(self.output_coords_dict[vn][i]) for i in list(self.output_coords_dict[vn].keys())]) - output_xr_ds_ = xr.Dataset({vn: (list(self.output_coords_dict[vn].keys()), empty_holder)}, - coords=self.output_coords_dict[vn]) + empty_holder = np.zeros( + [ + len(self.output_coords_dict[vn][i]) + for i in list(self.output_coords_dict[vn].keys()) + ] + ) + output_xr_ds_ = xr.Dataset( + {vn: (list(self.output_coords_dict[vn].keys()), empty_holder)}, + coords=self.output_coords_dict[vn], + ) # Merge datasets of stats into one output if count_vn == 1: self.output_xr_ds = output_xr_ds_ else: - self.output_xr_ds = xr.merge((self.output_xr_ds, output_xr_ds_)) - noencoding_vn = ['RGIId'] + self.output_xr_ds = xr.merge( + (self.output_xr_ds, output_xr_ds_) + ) + noencoding_vn = ["RGIId"] # Add attributes for vn in self.output_xr_ds.variables: try: @@ -189,33 +249,51 @@ def create_xr_ds(self): except: pass # Encoding (specify _FillValue, offsets, etc.) - + if vn not in noencoding_vn: - self.encoding[vn] = {'_FillValue': None, - 'zlib':True, - 'complevel':9 - } - self.output_xr_ds['RGIId'].values = np.array([self.glacier_rgi_table.loc['RGIId']]) - self.output_xr_ds['CenLon'].values = np.array([self.glacier_rgi_table.CenLon]) - self.output_xr_ds['CenLat'].values = np.array([self.glacier_rgi_table.CenLat]) - self.output_xr_ds['O1Region'].values = np.array([self.glacier_rgi_table.O1Region]) - self.output_xr_ds['O2Region'].values = np.array([self.glacier_rgi_table.O2Region]) - self.output_xr_ds['Area'].values = np.array([self.glacier_rgi_table.Area * 1e6]) - - self.output_xr_ds.attrs = {'source': f'PyGEMv{self.pygem_version}', - 'institution': pygem_prms['user']['institution'], - 'history': f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + datetime.today().strftime('%Y-%m-%d'), - 'references': 'doi:10.1126/science.abo1324', - 'model_parameters':json.dumps(self.mdl_params_dict)} + self.encoding[vn] = { + "_FillValue": None, + "zlib": True, + "complevel": 9, + } + self.output_xr_ds["RGIId"].values = np.array( + [self.glacier_rgi_table.loc["RGIId"]] + ) + self.output_xr_ds["CenLon"].values = np.array( + [self.glacier_rgi_table.CenLon] + ) + self.output_xr_ds["CenLat"].values = np.array( + [self.glacier_rgi_table.CenLat] + ) + self.output_xr_ds["O1Region"].values = np.array( + [self.glacier_rgi_table.O1Region] + ) + self.output_xr_ds["O2Region"].values = np.array( + [self.glacier_rgi_table.O2Region] + ) + self.output_xr_ds["Area"].values = np.array( + [self.glacier_rgi_table.Area * 1e6] + ) + + self.output_xr_ds.attrs = { + "source": f"PyGEMv{self.pygem_version}", + "institution": pygem_prms["user"]["institution"], + "history": f"Created by {pygem_prms['user']['name']} ({pygem_prms['user']['email']}) on " + + datetime.today().strftime("%Y-%m-%d"), + "references": "doi:10.1126/science.abo1324", + "model_parameters": json.dumps(self.mdl_params_dict), + } # return dataset def get_xr_ds(self): return self.output_xr_ds - + # save dataset def save_xr_ds(self, netcdf_fn): # export netcdf - self.output_xr_ds.to_netcdf(self.outdir + netcdf_fn, encoding=self.encoding) + self.output_xr_ds.to_netcdf( + self.outdir + netcdf_fn, encoding=self.encoding + ) # close datasets self.output_xr_ds.close() @@ -227,412 +305,694 @@ class glacierwide_stats(single_glacier): """ def __post_init__(self): - super().__post_init__() # call parent class __post_init__ (get glacier values, time stamps, and instantiate output dictionaries that will form netcdf file output) + super().__post_init__() # call parent class __post_init__ (get glacier values, time stamps, and instantiate output dictionaries that will form netcdf file output) self.set_outdir() - self.update_dicts() # add required fields to output dictionary + self.update_dicts() # add required fields to output dictionary # set output directory def set_outdir(self): - self.outdir += self.reg_str + '/' + self.gcm_name + '/' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outdir += self.scenario + '/' - self.outdir += 'stats/' + self.outdir += self.reg_str + "/" + self.gcm_name + "/" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outdir += self.scenario + "/" + self.outdir += "stats/" # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) # update coordinate and attribute dictionaries def update_dicts(self): - self.output_coords_dict['glac_runoff_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_runoff_monthly'] = { - 'long_name': 'glacier-wide runoff', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'runoff from the glacier terminus, which moves over time'} - self.output_coords_dict['glac_area_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_area_annual'] = { - 'long_name': 'glacier area', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'area at start of the year'} - self.output_coords_dict['glac_mass_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_annual'] = { - 'long_name': 'glacier mass', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_mass_bsl_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_bsl_annual'] = { - 'long_name': 'glacier mass below sea level', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_ELA_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_ELA_annual'] = { - 'long_name': 'annual equilibrium line altitude above mean sea level', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'equilibrium line altitude is the elevation where the climatic mass balance is zero'} - self.output_coords_dict['offglac_runoff_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_runoff_monthly'] = { - 'long_name': 'off-glacier-wide runoff', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'off-glacier runoff from area where glacier no longer exists'} + self.output_coords_dict["glac_runoff_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_runoff_monthly"] = { + "long_name": "glacier-wide runoff", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "runoff from the glacier terminus, which moves over time", + } + self.output_coords_dict["glac_area_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_area_annual"] = { + "long_name": "glacier area", + "units": "m2", + "temporal_resolution": "annual", + "comment": "area at start of the year", + } + self.output_coords_dict["glac_mass_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_mass_annual"] = { + "long_name": "glacier mass", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_mass_bsl_annual"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_bsl_annual"] = { + "long_name": "glacier mass below sea level", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice below sea level based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_ELA_annual"] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict["glac_ELA_annual"] = { + "long_name": "annual equilibrium line altitude above mean sea level", + "units": "m", + "temporal_resolution": "annual", + "comment": "equilibrium line altitude is the elevation where the climatic mass balance is zero", + } + self.output_coords_dict["offglac_runoff_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_runoff_monthly"] = { + "long_name": "off-glacier-wide runoff", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "off-glacier runoff from area where glacier no longer exists", + } if self.nsims > 1: - self.output_coords_dict['glac_runoff_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_runoff_monthly_mad'] = { - 'long_name': 'glacier-wide runoff median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'runoff from the glacier terminus, which moves over time'} - self.output_coords_dict['glac_area_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_area_annual_mad'] = { - 'long_name': 'glacier area median absolute deviation', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'area at start of the year'} - self.output_coords_dict['glac_mass_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_annual_mad'] = { - 'long_name': 'glacier mass median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_mass_bsl_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_bsl_annual_mad'] = { - 'long_name': 'glacier mass below sea level median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice below sea level based on area and ice thickness at start of the year'} - self.output_coords_dict['glac_ELA_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_ELA_annual_mad'] = { - 'long_name': 'annual equilibrium line altitude above mean sea level median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'equilibrium line altitude is the elevation where the climatic mass balance is zero'} - self.output_coords_dict['offglac_runoff_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_runoff_monthly_mad'] = { - 'long_name': 'off-glacier-wide runoff median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'off-glacier runoff from area where glacier no longer exists'} - - if pygem_prms['sim']['out']['export_extra_vars']: - self.output_coords_dict['glac_prec_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_prec_monthly'] = { - 'long_name': 'glacier-wide precipitation (liquid)', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['glac_temp_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_temp_monthly'] = { - 'standard_name': 'air_temperature', - 'long_name': 'glacier-wide mean air temperature', - 'units': 'K', - 'temporal_resolution': 'monthly', - 'comment': ('each elevation bin is weighted equally to compute the mean temperature, and ' - 'bins where the glacier no longer exists due to retreat have been removed')} - self.output_coords_dict['glac_acc_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_acc_monthly'] = { - 'long_name': 'glacier-wide accumulation, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the solid precipitation'} - self.output_coords_dict['glac_refreeze_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_refreeze_monthly'] = { - 'long_name': 'glacier-wide refreeze, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_melt_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_melt_monthly'] = { - 'long_name': 'glacier-wide melt, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_frontalablation_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_frontalablation_monthly'] = { - 'long_name': 'glacier-wide frontal ablation, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': ( - 'mass losses from calving, subaerial frontal melting, sublimation above the ' - 'waterline and subaqueous frontal melting below the waterline; positive values indicate mass lost like melt')} - self.output_coords_dict['glac_massbaltotal_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_massbaltotal_monthly'] = { - 'long_name': 'glacier-wide total mass balance, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'total mass balance is the sum of the climatic mass balance and frontal ablation'} - self.output_coords_dict['glac_snowline_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_snowline_monthly'] = { - 'long_name': 'transient snowline altitude above mean sea level', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'transient snowline is altitude separating snow from ice/firn'} - self.output_coords_dict['glac_mass_change_ignored_annual'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_change_ignored_annual'] = { - 'long_name': 'glacier mass change ignored', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'glacier mass change ignored due to flux divergence'} - self.output_coords_dict['offglac_prec_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_prec_monthly'] = { - 'long_name': 'off-glacier-wide precipitation (liquid)', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['offglac_refreeze_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_refreeze_monthly'] = { - 'long_name': 'off-glacier-wide refreeze, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['offglac_melt_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_melt_monthly'] = { - 'long_name': 'off-glacier-wide melt, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only melt of snow and refreeze since off-glacier'} - self.output_coords_dict['offglac_snowpack_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_snowpack_monthly'] = { - 'long_name': 'off-glacier-wide snowpack, in water equivalent', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze'} + self.output_coords_dict["glac_runoff_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_runoff_monthly_mad"] = { + "long_name": "glacier-wide runoff median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "runoff from the glacier terminus, which moves over time", + } + self.output_coords_dict["glac_area_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_area_annual_mad"] = { + "long_name": "glacier area median absolute deviation", + "units": "m2", + "temporal_resolution": "annual", + "comment": "area at start of the year", + } + self.output_coords_dict["glac_mass_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_annual_mad"] = { + "long_name": "glacier mass median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_mass_bsl_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_bsl_annual_mad"] = { + "long_name": "glacier mass below sea level median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice below sea level based on area and ice thickness at start of the year", + } + self.output_coords_dict["glac_ELA_annual_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_ELA_annual_mad"] = { + "long_name": "annual equilibrium line altitude above mean sea level median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "equilibrium line altitude is the elevation where the climatic mass balance is zero", + } + self.output_coords_dict["offglac_runoff_monthly_mad"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_runoff_monthly_mad"] = { + "long_name": "off-glacier-wide runoff median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "off-glacier runoff from area where glacier no longer exists", + } + + if pygem_prms["sim"]["out"]["export_extra_vars"]: + self.output_coords_dict["glac_prec_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_prec_monthly"] = { + "long_name": "glacier-wide precipitation (liquid)", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["glac_temp_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_temp_monthly"] = { + "standard_name": "air_temperature", + "long_name": "glacier-wide mean air temperature", + "units": "K", + "temporal_resolution": "monthly", + "comment": ( + "each elevation bin is weighted equally to compute the mean temperature, and " + "bins where the glacier no longer exists due to retreat have been removed" + ), + } + self.output_coords_dict["glac_acc_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_acc_monthly"] = { + "long_name": "glacier-wide accumulation, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the solid precipitation", + } + self.output_coords_dict["glac_refreeze_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_refreeze_monthly"] = { + "long_name": "glacier-wide refreeze, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_melt_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_melt_monthly"] = { + "long_name": "glacier-wide melt, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_frontalablation_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_frontalablation_monthly"] = { + "long_name": "glacier-wide frontal ablation, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": ( + "mass losses from calving, subaerial frontal melting, sublimation above the " + "waterline and subaqueous frontal melting below the waterline; positive values indicate mass lost like melt" + ), + } + self.output_coords_dict["glac_massbaltotal_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_massbaltotal_monthly"] = { + "long_name": "glacier-wide total mass balance, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "total mass balance is the sum of the climatic mass balance and frontal ablation", + } + self.output_coords_dict["glac_snowline_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["glac_snowline_monthly"] = { + "long_name": "transient snowline altitude above mean sea level", + "units": "m", + "temporal_resolution": "monthly", + "comment": "transient snowline is altitude separating snow from ice/firn", + } + self.output_coords_dict["glac_mass_change_ignored_annual"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + ) + self.output_attrs_dict["glac_mass_change_ignored_annual"] = { + "long_name": "glacier mass change ignored", + "units": "kg", + "temporal_resolution": "annual", + "comment": "glacier mass change ignored due to flux divergence", + } + self.output_coords_dict["offglac_prec_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_prec_monthly"] = { + "long_name": "off-glacier-wide precipitation (liquid)", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["offglac_refreeze_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_refreeze_monthly"] = { + "long_name": "off-glacier-wide refreeze, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["offglac_melt_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_melt_monthly"] = { + "long_name": "off-glacier-wide melt, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only melt of snow and refreeze since off-glacier", + } + self.output_coords_dict["offglac_snowpack_monthly"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("time", self.time_values)] + ) + ) + self.output_attrs_dict["offglac_snowpack_monthly"] = { + "long_name": "off-glacier-wide snowpack, in water equivalent", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + } if self.nsims > 1: - self.output_coords_dict['glac_prec_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_prec_monthly_mad'] = { - 'long_name': 'glacier-wide precipitation (liquid) median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['glac_temp_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_temp_monthly_mad'] = { - 'standard_name': 'air_temperature', - 'long_name': 'glacier-wide mean air temperature median absolute deviation', - 'units': 'K', - 'temporal_resolution': 'monthly', - 'comment': ( - 'each elevation bin is weighted equally to compute the mean temperature, and ' - 'bins where the glacier no longer exists due to retreat have been removed')} - self.output_coords_dict['glac_acc_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_acc_monthly_mad'] = { - 'long_name': 'glacier-wide accumulation, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the solid precipitation'} - self.output_coords_dict['glac_refreeze_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_refreeze_monthly_mad'] = { - 'long_name': 'glacier-wide refreeze, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_melt_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_melt_monthly_mad'] = { - 'long_name': 'glacier-wide melt, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['glac_frontalablation_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_frontalablation_monthly_mad'] = { - 'long_name': 'glacier-wide frontal ablation, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': ( - 'mass losses from calving, subaerial frontal melting, sublimation above the ' - 'waterline and subaqueous frontal melting below the waterline')} - self.output_coords_dict['glac_massbaltotal_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_massbaltotal_monthly_mad'] = { - 'long_name': 'glacier-wide total mass balance, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'total mass balance is the sum of the climatic mass balance and frontal ablation'} - self.output_coords_dict['glac_snowline_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['glac_snowline_monthly_mad'] = { - 'long_name': 'transient snowline above mean sea level median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'transient snowline is altitude separating snow from ice/firn'} - self.output_coords_dict['glac_mass_change_ignored_annual_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('year', self.year_values)]) - self.output_attrs_dict['glac_mass_change_ignored_annual_mad'] = { - 'long_name': 'glacier mass change ignored median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'glacier mass change ignored due to flux divergence'} - self.output_coords_dict['offglac_prec_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_prec_monthly_mad'] = { - 'long_name': 'off-glacier-wide precipitation (liquid) median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only the liquid precipitation, solid precipitation excluded'} - self.output_coords_dict['offglac_refreeze_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_refreeze_monthly_mad'] = { - 'long_name': 'off-glacier-wide refreeze, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly'} - self.output_coords_dict['offglac_melt_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_melt_monthly_mad'] = { - 'long_name': 'off-glacier-wide melt, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'only melt of snow and refreeze since off-glacier'} - self.output_coords_dict['offglac_snowpack_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) - self.output_attrs_dict['offglac_snowpack_monthly_mad'] = { - 'long_name': 'off-glacier-wide snowpack, in water equivalent, median absolute deviation', - 'units': 'm3', - 'temporal_resolution': 'monthly', - 'comment': 'snow remaining accounting for new accumulation, melt, and refreeze'} - + self.output_coords_dict["glac_prec_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_prec_monthly_mad"] = { + "long_name": "glacier-wide precipitation (liquid) median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["glac_temp_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_temp_monthly_mad"] = { + "standard_name": "air_temperature", + "long_name": "glacier-wide mean air temperature median absolute deviation", + "units": "K", + "temporal_resolution": "monthly", + "comment": ( + "each elevation bin is weighted equally to compute the mean temperature, and " + "bins where the glacier no longer exists due to retreat have been removed" + ), + } + self.output_coords_dict["glac_acc_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_acc_monthly_mad"] = { + "long_name": "glacier-wide accumulation, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the solid precipitation", + } + self.output_coords_dict["glac_refreeze_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_refreeze_monthly_mad"] = { + "long_name": "glacier-wide refreeze, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_melt_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_melt_monthly_mad"] = { + "long_name": "glacier-wide melt, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["glac_frontalablation_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_frontalablation_monthly_mad"] = { + "long_name": "glacier-wide frontal ablation, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": ( + "mass losses from calving, subaerial frontal melting, sublimation above the " + "waterline and subaqueous frontal melting below the waterline" + ), + } + self.output_coords_dict["glac_massbaltotal_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_massbaltotal_monthly_mad"] = { + "long_name": "glacier-wide total mass balance, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "total mass balance is the sum of the climatic mass balance and frontal ablation", + } + self.output_coords_dict["glac_snowline_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["glac_snowline_monthly_mad"] = { + "long_name": "transient snowline above mean sea level median absolute deviation", + "units": "m", + "temporal_resolution": "monthly", + "comment": "transient snowline is altitude separating snow from ice/firn", + } + self.output_coords_dict[ + "glac_mass_change_ignored_annual_mad" + ] = collections.OrderedDict( + [("glac", self.glac_values), ("year", self.year_values)] + ) + self.output_attrs_dict[ + "glac_mass_change_ignored_annual_mad" + ] = { + "long_name": "glacier mass change ignored median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "glacier mass change ignored due to flux divergence", + } + self.output_coords_dict["offglac_prec_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["offglac_prec_monthly_mad"] = { + "long_name": "off-glacier-wide precipitation (liquid) median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only the liquid precipitation, solid precipitation excluded", + } + self.output_coords_dict["offglac_refreeze_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["offglac_refreeze_monthly_mad"] = { + "long_name": "off-glacier-wide refreeze, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + } + self.output_coords_dict["offglac_melt_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["offglac_melt_monthly_mad"] = { + "long_name": "off-glacier-wide melt, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "only melt of snow and refreeze since off-glacier", + } + self.output_coords_dict["offglac_snowpack_monthly_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["offglac_snowpack_monthly_mad"] = { + "long_name": "off-glacier-wide snowpack, in water equivalent, median absolute deviation", + "units": "m3", + "temporal_resolution": "monthly", + "comment": "snow remaining accounting for new accumulation, melt, and refreeze", + } + @dataclass class binned_stats(single_glacier): """ Single glacier binned dataset """ - nbins : int - binned_components : bool + + nbins: int + binned_components: bool def __post_init__(self): - super().__post_init__() # call parent class __post_init__ (get glacier values, time stamps, and instantiate output dictionaries that will form netcdf file output) - self.bin_values = np.arange(self.nbins) # bin indices + super().__post_init__() # call parent class __post_init__ (get glacier values, time stamps, and instantiate output dictionaries that will form netcdf file output) + self.bin_values = np.arange(self.nbins) # bin indices self.set_outdir() - self.update_dicts() # add required fields to output dictionary + self.update_dicts() # add required fields to output dictionary # set output directory def set_outdir(self): - self.outdir += self.reg_str + '/' + self.gcm_name + '/' - if self.gcm_name not in ['ERA-Interim', 'ERA5', 'COAWST']: - self.outdir += self.scenario + '/' - self.outdir += 'binned/' + self.outdir += self.reg_str + "/" + self.gcm_name + "/" + if self.gcm_name not in ["ERA-Interim", "ERA5", "COAWST"]: + self.outdir += self.scenario + "/" + self.outdir += "binned/" # Create filepath if it does not exist os.makedirs(self.outdir, exist_ok=True) # update coordinate and attribute dictionaries def update_dicts(self): - self.output_coords_dict['bin_distance'] = collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values)]) - self.output_attrs_dict['bin_distance'] = { - 'long_name': 'distance downglacier', - 'units': 'm', - 'comment': 'horizontal distance calculated from top of glacier moving downglacier'} - self.output_coords_dict['bin_surface_h_initial'] = collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values)]) - self.output_attrs_dict['bin_surface_h_initial'] = { - 'long_name': 'initial binned surface elevation', - 'units': 'm above sea level'} - self.output_coords_dict['bin_area_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_area_annual'] = { - 'long_name': 'binned glacier area', - 'units': 'm2', - 'temporal_resolution': 'annual', - 'comment': 'binned area at start of the year'} - self.output_coords_dict['bin_mass_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_mass_annual'] = { - 'long_name': 'binned ice mass', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'binned ice mass at start of the year'} - self.output_coords_dict['bin_thick_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_thick_annual'] = { - 'long_name': 'binned ice thickness', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'binned ice thickness at start of the year'} - self.output_coords_dict['bin_massbalclim_annual'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_massbalclim_annual'] = { - 'long_name': 'binned climatic mass balance, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'climatic mass balance is computed before dynamics so can theoretically exceed ice thickness'}, - self.output_coords_dict['bin_massbalclim_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_massbalclim_monthly'] = { - 'long_name': 'binned monthly climatic mass balance, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly climatic mass balance from the PyGEM mass balance module'} + self.output_coords_dict["bin_distance"] = collections.OrderedDict( + [("glac", self.glac_values), ("bin", self.bin_values)] + ) + self.output_attrs_dict["bin_distance"] = { + "long_name": "distance downglacier", + "units": "m", + "comment": "horizontal distance calculated from top of glacier moving downglacier", + } + self.output_coords_dict["bin_surface_h_initial"] = ( + collections.OrderedDict( + [("glac", self.glac_values), ("bin", self.bin_values)] + ) + ) + self.output_attrs_dict["bin_surface_h_initial"] = { + "long_name": "initial binned surface elevation", + "units": "m above sea level", + } + self.output_coords_dict["bin_area_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_area_annual"] = { + "long_name": "binned glacier area", + "units": "m2", + "temporal_resolution": "annual", + "comment": "binned area at start of the year", + } + self.output_coords_dict["bin_mass_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_mass_annual"] = { + "long_name": "binned ice mass", + "units": "kg", + "temporal_resolution": "annual", + "comment": "binned ice mass at start of the year", + } + self.output_coords_dict["bin_thick_annual"] = collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + self.output_attrs_dict["bin_thick_annual"] = { + "long_name": "binned ice thickness", + "units": "m", + "temporal_resolution": "annual", + "comment": "binned ice thickness at start of the year", + } + self.output_coords_dict["bin_massbalclim_annual"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + ) + self.output_attrs_dict["bin_massbalclim_annual"] = ( + { + "long_name": "binned climatic mass balance, in water equivalent", + "units": "m", + "temporal_resolution": "annual", + "comment": "climatic mass balance is computed before dynamics so can theoretically exceed ice thickness", + }, + ) + self.output_coords_dict["bin_massbalclim_monthly"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["bin_massbalclim_monthly"] = { + "long_name": "binned monthly climatic mass balance, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly climatic mass balance from the PyGEM mass balance module", + } if self.binned_components: - self.output_coords_dict['bin_accumulation_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_accumulation_monthly'] = { - 'long_name': 'binned monthly accumulation, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly accumulation from the PyGEM mass balance module'} - self.output_coords_dict['bin_melt_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_melt_monthly'] = { - 'long_name': 'binned monthly melt, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly melt from the PyGEM mass balance module'} - self.output_coords_dict['bin_refreeze_monthly'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('time', self.time_values)])) - self.output_attrs_dict['bin_refreeze_monthly'] = { - 'long_name': 'binned monthly refreeze, in water equivalent', - 'units': 'm', - 'temporal_resolution': 'monthly', - 'comment': 'monthly refreeze from the PyGEM mass balance module'} - + self.output_coords_dict["bin_accumulation_monthly"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["bin_accumulation_monthly"] = { + "long_name": "binned monthly accumulation, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly accumulation from the PyGEM mass balance module", + } + self.output_coords_dict["bin_melt_monthly"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["bin_melt_monthly"] = { + "long_name": "binned monthly melt, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly melt from the PyGEM mass balance module", + } + self.output_coords_dict["bin_refreeze_monthly"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("time", self.time_values), + ] + ) + ) + self.output_attrs_dict["bin_refreeze_monthly"] = { + "long_name": "binned monthly refreeze, in water equivalent", + "units": "m", + "temporal_resolution": "monthly", + "comment": "monthly refreeze from the PyGEM mass balance module", + } + if self.nsims > 1: - self.output_coords_dict['bin_mass_annual_mad'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_mass_annual_mad'] = { - 'long_name': 'binned ice mass median absolute deviation', - 'units': 'kg', - 'temporal_resolution': 'annual', - 'comment': 'mass of ice based on area and ice thickness at start of the year'} - self.output_coords_dict['bin_thick_annual_mad'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_thick_annual_mad'] = { - 'long_name': 'binned ice thickness median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'thickness of ice at start of the year'} - self.output_coords_dict['bin_massbalclim_annual_mad'] = ( - collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values), ('year', self.year_values)])) - self.output_attrs_dict['bin_massbalclim_annual_mad'] = { - 'long_name': 'binned climatic mass balance, in water equivalent, median absolute deviation', - 'units': 'm', - 'temporal_resolution': 'annual', - 'comment': 'climatic mass balance is computed before dynamics so can theoretically exceed ice thickness'} - + self.output_coords_dict["bin_mass_annual_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + ) + self.output_attrs_dict["bin_mass_annual_mad"] = { + "long_name": "binned ice mass median absolute deviation", + "units": "kg", + "temporal_resolution": "annual", + "comment": "mass of ice based on area and ice thickness at start of the year", + } + self.output_coords_dict["bin_thick_annual_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + ) + self.output_attrs_dict["bin_thick_annual_mad"] = { + "long_name": "binned ice thickness median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "thickness of ice at start of the year", + } + self.output_coords_dict["bin_massbalclim_annual_mad"] = ( + collections.OrderedDict( + [ + ("glac", self.glac_values), + ("bin", self.bin_values), + ("year", self.year_values), + ] + ) + ) + self.output_attrs_dict["bin_massbalclim_annual_mad"] = { + "long_name": "binned climatic mass balance, in water equivalent, median absolute deviation", + "units": "m", + "temporal_resolution": "annual", + "comment": "climatic mass balance is computed before dynamics so can theoretically exceed ice thickness", + } + ### compiled regional output parent class ### @dataclass @@ -641,24 +1001,28 @@ class compiled_regional: Compiled regional output dataset for the Python Glacier Evolution Model. """ + @dataclass class regional_annual_mass(compiled_regional): """ compiled regional annual mass """ + @dataclass class regional_annual_area(compiled_regional): """ compiled regional annual area """ + @dataclass class regional_monthly_runoff(compiled_regional): """ compiled regional monthly runoff """ + @dataclass class regional_monthly_massbal(compiled_regional): """ @@ -666,7 +1030,7 @@ class regional_monthly_massbal(compiled_regional): """ -def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): +def calc_stats_array(data, stats_cns=pygem_prms["sim"]["out"]["sim_stats"]): """ Calculate stats for a given variable @@ -683,24 +1047,42 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): Statistics related to a given variable """ stats = None - if 'mean' in stats_cns: + if "mean" in stats_cns: if stats is None: - stats = np.nanmean(data,axis=1)[:,np.newaxis] - if 'std' in stats_cns: - stats = np.append(stats, np.nanstd(data,axis=1)[:,np.newaxis], axis=1) - if '2.5%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 2.5, axis=1)[:,np.newaxis], axis=1) - if '25%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 25, axis=1)[:,np.newaxis], axis=1) - if 'median' in stats_cns: + stats = np.nanmean(data, axis=1)[:, np.newaxis] + if "std" in stats_cns: + stats = np.append( + stats, np.nanstd(data, axis=1)[:, np.newaxis], axis=1 + ) + if "2.5%" in stats_cns: + stats = np.append( + stats, np.nanpercentile(data, 2.5, axis=1)[:, np.newaxis], axis=1 + ) + if "25%" in stats_cns: + stats = np.append( + stats, np.nanpercentile(data, 25, axis=1)[:, np.newaxis], axis=1 + ) + if "median" in stats_cns: if stats is None: - stats = np.nanmedian(data, axis=1)[:,np.newaxis] + stats = np.nanmedian(data, axis=1)[:, np.newaxis] else: - stats = np.append(stats, np.nanmedian(data, axis=1)[:,np.newaxis], axis=1) - if '75%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 75, axis=1)[:,np.newaxis], axis=1) - if '97.5%' in stats_cns: - stats = np.append(stats, np.nanpercentile(data, 97.5, axis=1)[:,np.newaxis], axis=1) - if 'mad' in stats_cns: - stats = np.append(stats, median_abs_deviation(data, axis=1, nan_policy='omit')[:,np.newaxis], axis=1) - return stats \ No newline at end of file + stats = np.append( + stats, np.nanmedian(data, axis=1)[:, np.newaxis], axis=1 + ) + if "75%" in stats_cns: + stats = np.append( + stats, np.nanpercentile(data, 75, axis=1)[:, np.newaxis], axis=1 + ) + if "97.5%" in stats_cns: + stats = np.append( + stats, np.nanpercentile(data, 97.5, axis=1)[:, np.newaxis], axis=1 + ) + if "mad" in stats_cns: + stats = np.append( + stats, + median_abs_deviation(data, axis=1, nan_policy="omit")[ + :, np.newaxis + ], + axis=1, + ) + return stats diff --git a/pygem/pygem_modelsetup.py b/pygem/pygem_modelsetup.py index a602decc..d7410699 100755 --- a/pygem/pygem_modelsetup.py +++ b/pygem/pygem_modelsetup.py @@ -7,19 +7,29 @@ List of functions used to set up different aspects of the model """ + # Built-in libaries import os +from datetime import datetime + +import numpy as np + # External libraries import pandas as pd -import numpy as np -from datetime import datetime + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file -def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pygem_prms['climate']['ref_endyear'], - spinupyears=pygem_prms['climate']['ref_spinupyears'], option_wateryear=pygem_prms['climate']['ref_wateryear']): + +def datesmodelrun( + startyear=pygem_prms["climate"]["ref_startyear"], + endyear=pygem_prms["climate"]["ref_endyear"], + spinupyears=pygem_prms["climate"]["ref_spinupyears"], + option_wateryear=pygem_prms["climate"]["ref_wateryear"], +): """ Create table of year, month, day, water year, season and number of days in the month. @@ -40,83 +50,98 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge # Include spinup time in start year startyear_wspinup = startyear - spinupyears # Convert start year into date depending on option_wateryear - if option_wateryear == 'hydro': - startdate = str(startyear_wspinup - 1) + '-10-01' - enddate = str(endyear) + '-09-30' - elif option_wateryear == 'calendar': - startdate = str(startyear_wspinup) + '-01-01' - enddate = str(endyear) + '-12-31' - elif option_wateryear == 'custom': - startdate = str(startyear_wspinup) + '-' + pygem_prms['time']['startmonthday'] - enddate = str(endyear) + '-' + pygem_prms['time']['endmonthday'] + if option_wateryear == "hydro": + startdate = str(startyear_wspinup - 1) + "-10-01" + enddate = str(endyear) + "-09-30" + elif option_wateryear == "calendar": + startdate = str(startyear_wspinup) + "-01-01" + enddate = str(endyear) + "-12-31" + elif option_wateryear == "custom": + startdate = ( + str(startyear_wspinup) + "-" + pygem_prms["time"]["startmonthday"] + ) + enddate = str(endyear) + "-" + pygem_prms["time"]["endmonthday"] else: - assert True==False, "\n\nError: Select an option_wateryear that exists.\n" + assert True == False, ( + "\n\nError: Select an option_wateryear that exists.\n" + ) # Convert input format into proper datetime format - startdate = datetime(*[int(item) for item in startdate.split('-')]) - enddate = datetime(*[int(item) for item in enddate.split('-')]) - if pygem_prms['time']['timestep'] == 'monthly': - startdate = startdate.strftime('%Y-%m') - enddate = enddate.strftime('%Y-%m') - elif pygem_prms['time']['timestep'] == 'daily': - startdate = startdate.strftime('%Y-%m-%d') - enddate = enddate.strftime('%Y-%m-%d') + startdate = datetime(*[int(item) for item in startdate.split("-")]) + enddate = datetime(*[int(item) for item in enddate.split("-")]) + if pygem_prms["time"]["timestep"] == "monthly": + startdate = startdate.strftime("%Y-%m") + enddate = enddate.strftime("%Y-%m") + elif pygem_prms["time"]["timestep"] == "daily": + startdate = startdate.strftime("%Y-%m-%d") + enddate = enddate.strftime("%Y-%m-%d") # Generate dates_table using date_range function - if pygem_prms['time']['timestep'] == 'monthly': + if pygem_prms["time"]["timestep"] == "monthly": # Automatically generate dates from start date to end data using a monthly frequency (MS), which generates # monthly data using the 1st of each month' - dates_table = pd.DataFrame({'date' : pd.date_range(startdate, enddate, freq='MS', unit='s')}) + dates_table = pd.DataFrame( + {"date": pd.date_range(startdate, enddate, freq="MS", unit="s")} + ) # Select attributes of DateTimeIndex (dt.year, dt.month, and dt.daysinmonth) - dates_table['year'] = dates_table['date'].dt.year - dates_table['month'] = dates_table['date'].dt.month - dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth - dates_table['timestep'] = np.arange(len(dates_table['date'])) + dates_table["year"] = dates_table["date"].dt.year + dates_table["month"] = dates_table["date"].dt.month + dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth + dates_table["timestep"] = np.arange(len(dates_table["date"])) # Set date as index - dates_table.set_index('timestep', inplace=True) + dates_table.set_index("timestep", inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms['time']['option_leapyear'] == 0: - mask1 = (dates_table['daysinmonth'] == 29) - dates_table.loc[mask1,'daysinmonth'] = 28 - elif pygem_prms['time']['timestep'] == 'daily': + if pygem_prms["time"]["option_leapyear"] == 0: + mask1 = dates_table["daysinmonth"] == 29 + dates_table.loc[mask1, "daysinmonth"] = 28 + elif pygem_prms["time"]["timestep"] == "daily": # Automatically generate daily (freq = 'D') dates - dates_table = pd.DataFrame({'date' : pd.date_range(startdate, enddate, freq='D')}) + dates_table = pd.DataFrame( + {"date": pd.date_range(startdate, enddate, freq="D")} + ) # Extract attributes for dates_table - dates_table['year'] = dates_table['date'].dt.year - dates_table['month'] = dates_table['date'].dt.month - dates_table['day'] = dates_table['date'].dt.day - dates_table['daysinmonth'] = dates_table['date'].dt.daysinmonth + dates_table["year"] = dates_table["date"].dt.year + dates_table["month"] = dates_table["date"].dt.month + dates_table["day"] = dates_table["date"].dt.day + dates_table["daysinmonth"] = dates_table["date"].dt.daysinmonth # Set date as index - dates_table.set_index('date', inplace=True) + dates_table.set_index("date", inplace=True) # Remove leap year days if user selected this with option_leapyear - if pygem_prms['time']['option_leapyear'] == 0: + if pygem_prms["time"]["option_leapyear"] == 0: # First, change 'daysinmonth' number - mask1 = dates_table['daysinmonth'] == 29 - dates_table.loc[mask1,'daysinmonth'] = 28 + mask1 = dates_table["daysinmonth"] == 29 + dates_table.loc[mask1, "daysinmonth"] = 28 # Next, remove the 29th days from the dates - mask2 = ((dates_table['month'] == 2) & (dates_table['day'] == 29)) + mask2 = (dates_table["month"] == 2) & (dates_table["day"] == 29) dates_table.drop(dates_table[mask2].index, inplace=True) else: - print("\n\nError: Please select 'daily' or 'monthly' for gcm_timestep. Exiting model run now.\n") + print( + "\n\nError: Please select 'daily' or 'monthly' for gcm_timestep. Exiting model run now.\n" + ) exit() # Add column for water year # Water year for northern hemisphere using USGS definition (October 1 - September 30th), # e.g., water year for 2000 is from October 1, 1999 - September 30, 2000 - dates_table['wateryear'] = dates_table['year'] + dates_table["wateryear"] = dates_table["year"] for step in range(dates_table.shape[0]): - if dates_table.loc[step, 'month'] >= 10: - dates_table.loc[step, 'wateryear'] = dates_table.loc[step, 'year'] + 1 + if dates_table.loc[step, "month"] >= 10: + dates_table.loc[step, "wateryear"] = ( + dates_table.loc[step, "year"] + 1 + ) # Add column for seasons # create a season dictionary to assist groupby functions seasondict = {} month_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] season_list = [] for i in range(len(month_list)): - if (month_list[i] >= pygem_prms['time']['summer_month_start'] and month_list[i] < pygem_prms['time']['winter_month_start']): - season_list.append('summer') + if ( + month_list[i] >= pygem_prms["time"]["summer_month_start"] + and month_list[i] < pygem_prms["time"]["winter_month_start"] + ): + season_list.append("summer") seasondict[month_list[i]] = season_list[i] else: - season_list.append('winter') + season_list.append("winter") seasondict[month_list[i]] = season_list[i] - dates_table['season'] = dates_table['month'].apply(lambda x: seasondict[x]) + dates_table["season"] = dates_table["month"].apply(lambda x: seasondict[x]) return dates_table @@ -133,12 +158,36 @@ def daysinmonth(year, month): ------- integer of the days in the month """ - if year%4 == 0: + if year % 4 == 0: daysinmonth_dict = { - 1:31, 2:29, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31} + 1: 31, + 2: 29, + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 30, + 12: 31, + } else: daysinmonth_dict = { - 1:31, 2:28, 3:31, 4:30, 5:31, 6:30, 7:31, 8:31, 9:30, 10:31, 11:30, 12:31} + 1: 31, + 2: 28, + 3: 31, + 4: 30, + 5: 31, + 6: 30, + 7: 31, + 8: 31, + 9: 30, + 10: 31, + 11: 30, + 12: 31, + } return daysinmonth_dict[month] @@ -148,25 +197,33 @@ def hypsometrystats(hyps_table, thickness_table): Output is a series of the glacier volume [km**3] and mean elevation values [m a.s.l.]. """ # Glacier volume [km**3] - glac_volume = (hyps_table * thickness_table/1000).sum(axis=1).values + glac_volume = (hyps_table * thickness_table / 1000).sum(axis=1).values # Mean glacier elevation glac_hyps_mean = np.zeros(glac_volume.shape) - glac_hyps_mean[glac_volume > 0] = ((hyps_table[glac_volume > 0].values * - hyps_table[glac_volume > 0].columns.values.astype(int)).sum(axis=1) / - hyps_table[glac_volume > 0].values.sum(axis=1)) + glac_hyps_mean[glac_volume > 0] = ( + hyps_table[glac_volume > 0].values + * hyps_table[glac_volume > 0].columns.values.astype(int) + ).sum(axis=1) / hyps_table[glac_volume > 0].values.sum(axis=1) # Median computations -# main_glac_hyps_cumsum = np.cumsum(hyps_table, axis=1) -# for glac in range(hyps_table.shape[0]): -# # Median glacier elevation -# # Computed as the elevation when the normalized cumulative sum of the glacier area exceeds 0.5 (50%) -# series_glac_hyps_cumsumnorm = main_glac_hyps_cumsum.loc[glac,:].copy() / glac_area.iloc[glac] -# series_glac_hyps_cumsumnorm_positions = (np.where(series_glac_hyps_cumsumnorm > 0.5))[0] -# glac_hyps_median = main_glac_hyps.columns.values[series_glac_hyps_cumsumnorm_positions[0]] -# NOTE THERE IS A 20 m (+/- 5 m) OFFSET BETWEEN THE 10 m PRODUCT FROM HUSS AND THE RGI INVENTORY """ + # main_glac_hyps_cumsum = np.cumsum(hyps_table, axis=1) + # for glac in range(hyps_table.shape[0]): + # # Median glacier elevation + # # Computed as the elevation when the normalized cumulative sum of the glacier area exceeds 0.5 (50%) + # series_glac_hyps_cumsumnorm = main_glac_hyps_cumsum.loc[glac,:].copy() / glac_area.iloc[glac] + # series_glac_hyps_cumsumnorm_positions = (np.where(series_glac_hyps_cumsumnorm > 0.5))[0] + # glac_hyps_median = main_glac_hyps.columns.values[series_glac_hyps_cumsumnorm_positions[0]] + # NOTE THERE IS A 20 m (+/- 5 m) OFFSET BETWEEN THE 10 m PRODUCT FROM HUSS AND THE RGI INVENTORY """ return glac_volume, glac_hyps_mean -def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=pygem_prms['rgi']['indexname'], option_shift_elevbins_20m=True): +def import_Husstable( + rgi_table, + filepath, + filedict, + drop_col_names, + indexname=pygem_prms["rgi"]["indexname"], + option_shift_elevbins_20m=True, +): """Use the dictionary specified by the user to extract the desired variable. The files must be in the proper units (ice thickness [m], area [km2], width [km]) and should be pre-processed. @@ -176,27 +233,29 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py Line Profiling: Loading in the table takes the most time (~2.3 s) """ rgi_regionsO1 = sorted(list(rgi_table.O1Region.unique())) - glac_no = [x.split('-')[1] for x in rgi_table.RGIId.values] + glac_no = [x.split("-")[1] for x in rgi_table.RGIId.values] glac_no_byregion = {} for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = int(i.split('.')[0]) - glac_no_only = i.split('.')[1] + region = int(i.split(".")[0]) + glac_no_only = i.split(".")[1] glac_no_byregion[int(region)].append(glac_no_only) # Load data for each region for count, region in enumerate(rgi_regionsO1): # Select regional data for indexing glac_no = sorted(glac_no_byregion[region]) - rgi_table_region = rgi_table.iloc[np.where(rgi_table.O1Region.values == region)[0]] + rgi_table_region = rgi_table.iloc[ + np.where(rgi_table.O1Region.values == region)[0] + ] # Load table ds = pd.read_csv(filepath + filedict[region]) # Select glaciers based on 01Index value from main_glac_rgi table # as long as Huss tables have all rows associated with rgi attribute table, then this shortcut works - glac_table = ds.iloc[rgi_table_region['O1Index'].values] + glac_table = ds.iloc[rgi_table_region["O1Index"].values] # Merge multiple regions if count == 0: glac_table_all = glac_table @@ -223,11 +282,11 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py # drop columns that are not elevation bins glac_table_copy.drop(drop_col_names, axis=1, inplace=True) # change NAN from -99 to 0 - glac_table_copy[glac_table_copy==-99] = 0. + glac_table_copy[glac_table_copy == -99] = 0.0 # Shift Huss bins by 20 m since the elevation bins appear to be 20 m higher than they should be if option_shift_elevbins_20m: colnames = glac_table_copy.columns.tolist()[:-2] - glac_table_copy = glac_table_copy.iloc[:,2:] + glac_table_copy = glac_table_copy.iloc[:, 2:] glac_table_copy.columns = colnames return glac_table_copy @@ -264,16 +323,23 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py # return main_glac_calmassbal -def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all', rgi_glac_number='all', - rgi_fp=pygem_prms['root'] + pygem_prms['rgi']['rgi_relpath'], - rgi_cols_drop=pygem_prms['rgi']['rgi_cols_drop'], - rgi_O1Id_colname=pygem_prms['rgi']['rgi_O1Id_colname'], - rgi_glacno_float_colname=pygem_prms['rgi']['rgi_glacno_float_colname'], - indexname=pygem_prms['rgi']['indexname'], - include_landterm=True,include_laketerm=True,include_tidewater=True, - glac_no_skip=pygem_prms['setup']['glac_no_skip'], - min_glac_area_km2=0, - debug=False): +def selectglaciersrgitable( + glac_no=None, + rgi_regionsO1=None, + rgi_regionsO2="all", + rgi_glac_number="all", + rgi_fp=pygem_prms["root"] + pygem_prms["rgi"]["rgi_relpath"], + rgi_cols_drop=pygem_prms["rgi"]["rgi_cols_drop"], + rgi_O1Id_colname=pygem_prms["rgi"]["rgi_O1Id_colname"], + rgi_glacno_float_colname=pygem_prms["rgi"]["rgi_glacno_float_colname"], + indexname=pygem_prms["rgi"]["indexname"], + include_landterm=True, + include_laketerm=True, + include_tidewater=True, + glac_no_skip=pygem_prms["setup"]["glac_no_skip"], + min_glac_area_km2=0, + debug=False, +): """ Select all glaciers to be used in the model run according to the regions and glacier numbers defined by the RGI glacier inventory. This function returns the rgi table associated with all of these glaciers. @@ -292,13 +358,13 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' """ if glac_no is not None: glac_no_byregion = {} - rgi_regionsO1 = [int(i.split('.')[0]) for i in glac_no] + rgi_regionsO1 = [int(i.split(".")[0]) for i in glac_no] rgi_regionsO1 = list(set(rgi_regionsO1)) for region in rgi_regionsO1: glac_no_byregion[region] = [] for i in glac_no: - region = i.split('.')[0] - glac_no_only = i.split('.')[1] + region = i.split(".")[0] + glac_no_only = i.split(".")[1] glac_no_byregion[int(region)].append(glac_no_only) for region in rgi_regionsO1: @@ -308,88 +374,132 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' rgi_regionsO1 = sorted(rgi_regionsO1) glacier_table = pd.DataFrame() for region in rgi_regionsO1: - if glac_no is not None: rgi_glac_number = glac_no_byregion[region] -# if len(rgi_glac_number) < 50: + # if len(rgi_glac_number) < 50: for i in os.listdir(rgi_fp): - if i.startswith(str(region).zfill(2)) and i.endswith('.csv'): + if i.startswith(str(region).zfill(2)) and i.endswith(".csv"): rgi_fn = i try: csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn) except: - csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding='latin1') - + csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn, encoding="latin1") + # Populate glacer_table with the glaciers of interest - if rgi_regionsO2 == 'all' and rgi_glac_number == 'all': + if rgi_regionsO2 == "all" and rgi_glac_number == "all": if debug: - print("All glaciers within region(s) %s are included in this model run." % (region)) + print( + "All glaciers within region(s) %s are included in this model run." + % (region) + ) if glacier_table.empty: glacier_table = csv_regionO1 else: - glacier_table = pd.concat([glacier_table, csv_regionO1], axis=0) - elif rgi_regionsO2 != 'all' and rgi_glac_number == 'all': + glacier_table = pd.concat( + [glacier_table, csv_regionO1], axis=0 + ) + elif rgi_regionsO2 != "all" and rgi_glac_number == "all": if debug: - print("All glaciers within subregion(s) %s in region %s are included in this model run." % - (rgi_regionsO2, region)) + print( + "All glaciers within subregion(s) %s in region %s are included in this model run." + % (rgi_regionsO2, region) + ) for regionO2 in rgi_regionsO2: if glacier_table.empty: - glacier_table = csv_regionO1.loc[csv_regionO1['O2Region'] == regionO2] + glacier_table = csv_regionO1.loc[ + csv_regionO1["O2Region"] == regionO2 + ] else: - glacier_table = (pd.concat([glacier_table, csv_regionO1.loc[csv_regionO1['O2Region'] == - regionO2]], axis=0)) + glacier_table = pd.concat( + [ + glacier_table, + csv_regionO1.loc[ + csv_regionO1["O2Region"] == regionO2 + ], + ], + axis=0, + ) else: if len(rgi_glac_number) < 20: - print("%s glaciers in region %s are included in this model run: %s" % (len(rgi_glac_number), region, - rgi_glac_number)) + print( + "%s glaciers in region %s are included in this model run: %s" + % (len(rgi_glac_number), region, rgi_glac_number) + ) else: - print("%s glaciers in region %s are included in this model run: %s and more" % - (len(rgi_glac_number), region, rgi_glac_number[0:50])) - - rgiid_subset = ['RGI60-' + str(region).zfill(2) + '.' + x for x in rgi_glac_number] + print( + "%s glaciers in region %s are included in this model run: %s and more" + % (len(rgi_glac_number), region, rgi_glac_number[0:50]) + ) + + rgiid_subset = [ + "RGI60-" + str(region).zfill(2) + "." + x + for x in rgi_glac_number + ] rgiid_all = list(csv_regionO1.RGIId.values) - rgi_idx = [rgiid_all.index(x) for x in rgiid_subset if x in rgiid_all] + rgi_idx = [ + rgiid_all.index(x) for x in rgiid_subset if x in rgiid_all + ] if glacier_table.empty: glacier_table = csv_regionO1.loc[rgi_idx] else: - glacier_table = (pd.concat([glacier_table, csv_regionO1.loc[rgi_idx]], - axis=0)) - + glacier_table = pd.concat( + [glacier_table, csv_regionO1.loc[rgi_idx]], axis=0 + ) + glacier_table = glacier_table.copy() # reset the index so that it is in sequential order (0, 1, 2, etc.) glacier_table.reset_index(inplace=True) # drop connectivity 2 for Greenland and Antarctica - glacier_table = glacier_table.loc[glacier_table['Connect'].isin([0,1])] + glacier_table = glacier_table.loc[glacier_table["Connect"].isin([0, 1])] glacier_table.reset_index(drop=True, inplace=True) # change old index to 'O1Index' to be easier to recall what it is - glacier_table.rename(columns={'index': 'O1Index'}, inplace=True) + glacier_table.rename(columns={"index": "O1Index"}, inplace=True) # Record the reference date - glacier_table['RefDate'] = glacier_table['BgnDate'] + glacier_table["RefDate"] = glacier_table["BgnDate"] # if there is an end date, then roughly average the year - enddate_idx = glacier_table.loc[(glacier_table['EndDate'] > 0), 'EndDate'].index.values - glacier_table.loc[enddate_idx,'RefDate'] = ( - np.mean((glacier_table.loc[enddate_idx,['BgnDate', 'EndDate']].values / 10**4).astype(int), - axis=1).astype(int) * 10**4 + 9999) + enddate_idx = glacier_table.loc[ + (glacier_table["EndDate"] > 0), "EndDate" + ].index.values + glacier_table.loc[enddate_idx, "RefDate"] = ( + np.mean( + ( + glacier_table.loc[enddate_idx, ["BgnDate", "EndDate"]].values + / 10**4 + ).astype(int), + axis=1, + ).astype(int) + * 10**4 + + 9999 + ) # drop columns of data that is not being used glacier_table.drop(rgi_cols_drop, axis=1, inplace=True) # add column with the O1 glacier numbers glacier_table[rgi_O1Id_colname] = ( - glacier_table['RGIId'].str.split('.').apply(pd.Series).loc[:,1].astype(int)) - glacier_table['rgino_str'] = [x.split('-')[1] for x in glacier_table.RGIId.values] -# glacier_table[rgi_glacno_float_colname] = (np.array([np.str.split(glacier_table['RGIId'][x],'-')[1] -# for x in range(glacier_table.shape[0])]).astype(float)) - glacier_table[rgi_glacno_float_colname] = (np.array([x.split('-')[1] for x in glacier_table['RGIId']] -# [np.str.split(glacier_table['RGIId'][x],'-')[1] -# for x in range(glacier_table.shape[0])] - ).astype(float)) + glacier_table["RGIId"] + .str.split(".") + .apply(pd.Series) + .loc[:, 1] + .astype(int) + ) + glacier_table["rgino_str"] = [ + x.split("-")[1] for x in glacier_table.RGIId.values + ] + # glacier_table[rgi_glacno_float_colname] = (np.array([np.str.split(glacier_table['RGIId'][x],'-')[1] + # for x in range(glacier_table.shape[0])]).astype(float)) + glacier_table[rgi_glacno_float_colname] = np.array( + [x.split("-")[1] for x in glacier_table["RGIId"]] + # [np.str.split(glacier_table['RGIId'][x],'-')[1] + # for x in range(glacier_table.shape[0])] + ).astype(float) # set index name glacier_table.index.name = indexname # Longitude between 0-360deg (no negative) - glacier_table['CenLon_360'] = glacier_table['CenLon'] - glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360'] = ( - 360 + glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360']) + glacier_table["CenLon_360"] = glacier_table["CenLon"] + glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] = ( + 360 + glacier_table.loc[glacier_table["CenLon"] < 0, "CenLon_360"] + ) # Subset glaciers based on their terminus type termtype_values = [] if include_landterm: @@ -404,25 +514,36 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' termtype_values.append(5) if include_laketerm: termtype_values.append(2) - glacier_table = glacier_table.loc[glacier_table['TermType'].isin(termtype_values)] + glacier_table = glacier_table.loc[ + glacier_table["TermType"].isin(termtype_values) + ] glacier_table.reset_index(inplace=True, drop=True) # Glacier number with no trailing zeros - glacier_table['glacno'] = [str(int(x.split('-')[1].split('.')[0])) + '.' + x.split('-')[1].split('.')[1] - for x in glacier_table.RGIId] - + glacier_table["glacno"] = [ + str(int(x.split("-")[1].split(".")[0])) + + "." + + x.split("-")[1].split(".")[1] + for x in glacier_table.RGIId + ] + # Remove glaciers below threshold - glacier_table = glacier_table.loc[glacier_table['Area'] > min_glac_area_km2,:] + glacier_table = glacier_table.loc[ + glacier_table["Area"] > min_glac_area_km2, : + ] glacier_table.reset_index(inplace=True, drop=True) # Remove glaciers that are meant to be skipped if glac_no_skip is not None: - glac_no_all = list(glacier_table['glacno']) + glac_no_all = list(glacier_table["glacno"]) glac_no_unique = [x for x in glac_no_all if x not in glac_no_skip] unique_idx = [glac_no_all.index(x) for x in glac_no_unique] - glacier_table = glacier_table.loc[unique_idx,:] + glacier_table = glacier_table.loc[unique_idx, :] glacier_table.reset_index(inplace=True, drop=True) - print("This study is focusing on %s glaciers in region %s" % (glacier_table.shape[0], rgi_regionsO1)) + print( + "This study is focusing on %s glaciers in region %s" + % (glacier_table.shape[0], rgi_regionsO1) + ) return glacier_table @@ -445,14 +566,14 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' def split_list(lst, n=1, option_ordered=1, group_thousands=False): """ Split list into batches for the supercomputer. - + Parameters ---------- lst : list List that you want to split into separate batches n : int Number of batches to split glaciers into. - + Returns ------- lst_batches : list @@ -462,8 +583,8 @@ def split_list(lst, n=1, option_ordered=1, group_thousands=False): if option_ordered == 1: if n > len(lst): n = len(lst) - n_perlist_low = int(len(lst)/n) - n_perlist_high = int(np.ceil(len(lst)/n)) + n_perlist_low = int(len(lst) / n) + n_perlist_high = int(np.ceil(len(lst) / n)) lst_copy = lst.copy() count = 0 lst_batches = [] @@ -477,19 +598,19 @@ def split_list(lst, n=1, option_ordered=1, group_thousands=False): lst_subset = lst_copy[0:n_perlist_low] lst_batches.append(lst_subset) [lst_copy.remove(i) for i in lst_subset] - + else: if n > len(lst): n = len(lst) - + lst_batches = [[] for x in np.arange(n)] nbatch = 0 for count, x in enumerate(lst): - if count%n == 0: + if count % n == 0: nbatch = 0 - + lst_batches[nbatch].append(x) - + nbatch += 1 if group_thousands: @@ -502,7 +623,12 @@ def split_list(lst, n=1, option_ordered=1, group_thousands=False): lst_batches_th = [] # keep the number of batches, but move items around to not have sets of RGIXX.YY ids in more than one batch for s in sets: - merged = [item for sublist in lst_batches for item in sublist if item[:5]==s] + merged = [ + item + for sublist in lst_batches + for item in sublist + if item[:5] == s + ] lst_batches_th.append(merged) # ensure that number of batches doesn't exceed original number while len(lst_batches_th) > n: @@ -511,10 +637,10 @@ def split_list(lst, n=1, option_ordered=1, group_thousands=False): sorted = lengths.argsort() idx0 = sorted[0] idx1 = sorted[1] - + lst_batches_th[idx1].extend(lst_batches_th[idx0]) del lst_batches_th[idx0] lst_batches = lst_batches_th - return lst_batches \ No newline at end of file + return lst_batches diff --git a/pygem/scraps/dummy_task_module.py b/pygem/scraps/dummy_task_module.py index 3d9ab03d..24269d52 100755 --- a/pygem/scraps/dummy_task_module.py +++ b/pygem/scraps/dummy_task_module.py @@ -1,19 +1,23 @@ import logging + +import xarray as xr from oggm import cfg from oggm.utils import entity_task -import xarray as xr # Module logger log = logging.getLogger(__name__) # Add the new name "my_netcdf_file" to the list of things that the GlacierDirectory understands -cfg.BASENAMES['my_netcdf_file'] = ('somefilename.nc', "This is just a documentation string") +cfg.BASENAMES["my_netcdf_file"] = ( + "somefilename.nc", + "This is just a documentation string", +) @entity_task(log, writes=[]) def dummy_task(gdir, some_param=None): """Very dummy""" - fpath = gdir.get_filepath('my_netcdf_file') + fpath = gdir.get_filepath("my_netcdf_file") da = xr.DataArray([1, 2, 3]) da.to_netcdf(fpath) diff --git a/pygem/scraps/run.py b/pygem/scraps/run.py index cca1669a..f6daddbe 100755 --- a/pygem/scraps/run.py +++ b/pygem/scraps/run.py @@ -1,38 +1,40 @@ # Libs import geopandas as gpd -from oggm import utils, cfg, workflow +from oggm import cfg, utils, workflow # Initialize OGGM and set up the default run parameters cfg.initialize() # How many grid points around the glacier? -cfg.PARAMS['border'] = 10 +cfg.PARAMS["border"] = 10 # Make it robust -cfg.PARAMS['use_intersects'] = False -cfg.PARAMS['continue_on_error'] = True +cfg.PARAMS["use_intersects"] = False +cfg.PARAMS["continue_on_error"] = True # Local working directory (where OGGM will write its output) -cfg.PATHS['working_dir'] = utils.get_temp_dir('some_wd') +cfg.PATHS["working_dir"] = utils.get_temp_dir("some_wd") # RGI file -path = utils.get_rgi_region_file('11') +path = utils.get_rgi_region_file("11") rgidf = gpd.read_file(path) # Select only 2 glaciers rgidf = rgidf.iloc[:2] # Sort for more efficient parallel computing -rgidf = rgidf.sort_values('Area', ascending=False) +rgidf = rgidf.sort_values("Area", ascending=False) # Go - create the pre-processed glacier directories gdirs = workflow.init_glacier_directories(rgidf) # Our task now from dummy_task_module import dummy_task + workflow.execute_entity_task(dummy_task, gdirs) # See that we can read the new dummy data: import xarray as xr -fpath = gdirs[0].get_filepath('my_netcdf_file') + +fpath = gdirs[0].get_filepath("my_netcdf_file") print(xr.open_dataset(fpath)) diff --git a/pygem/setup/__init__.py b/pygem/setup/__init__.py index 9fdea20b..a4fc2db0 100755 --- a/pygem/setup/__init__.py +++ b/pygem/setup/__init__.py @@ -4,4 +4,4 @@ copyright © 2018 David Rounce Distrubted under the MIT lisence -""" \ No newline at end of file +""" diff --git a/pygem/setup/config.py b/pygem/setup/config.py index 4645af90..d5423560 100644 --- a/pygem/setup/config.py +++ b/pygem/setup/config.py @@ -5,44 +5,59 @@ Distrubted under the MIT lisence """ + import os import shutil -import sys + import yaml # pygem_params file name -config_fn = 'config.yaml' +config_fn = "config.yaml" # Define the base directory and the path to the configuration file -basedir = os.path.join(os.path.expanduser('~'), 'PyGEM') -config_file = os.path.join(basedir, config_fn) # Path where you want the config file +basedir = os.path.join(os.path.expanduser("~"), "PyGEM") +config_file = os.path.join( + basedir, config_fn +) # Path where you want the config file # Get the source configuration file path from your package -package_dir = os.path.dirname(__file__) # Get the directory of the current script +package_dir = os.path.dirname( + __file__ +) # Get the directory of the current script source_config_file = os.path.join(package_dir, config_fn) # Path to params.py + def ensure_config(overwrite=False): isfile = os.path.isfile(config_file) if isfile and overwrite: overwrite = None while overwrite is None: # Ask the user for a y/n response - response = input(f"PyGEM configuration.yaml file already exist ({config_file}), do you wish to overwrite (y/n):").strip().lower() + response = ( + input( + f"PyGEM configuration.yaml file already exist ({config_file}), do you wish to overwrite (y/n):" + ) + .strip() + .lower() + ) # Check if the response is valid - if response == 'y' or response == 'yes': + if response == "y" or response == "yes": overwrite = True - elif response == 'n' or response == 'no': + elif response == "n" or response == "no": overwrite = False else: print("Invalid input. Please enter 'y' or 'n'.") - + if (not isfile) or (overwrite): os.makedirs(basedir, exist_ok=True) # Ensure the base directory exists shutil.copy(source_config_file, config_file) # Copy the file print(f"Copied default configuration to {config_file}") - + + def read_config(): """Read the configuration file and return its contents as a dictionary.""" - with open(config_file, 'r') as f: - config = yaml.safe_load(f) # Use safe_load to avoid arbitrary code execution - return config \ No newline at end of file + with open(config_file, "r") as f: + config = yaml.safe_load( + f + ) # Use safe_load to avoid arbitrary code execution + return config diff --git a/pygem/shop/debris.py b/pygem/shop/debris.py index 191f256a..ba27defb 100755 --- a/pygem/shop/debris.py +++ b/pygem/shop/debris.py @@ -5,19 +5,20 @@ Distrubted under the MIT lisence """ -import os + import logging +import os import numpy as np import rasterio import xarray as xr - from oggm import cfg -from oggm.utils import entity_task from oggm.core.gis import rasterio_to_gdir -from oggm.utils import ncDataset +from oggm.utils import entity_task, ncDataset + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file @@ -31,104 +32,140 @@ log = logging.getLogger(__name__) # Add the new name "hd" to the list of things that the GlacierDirectory understands -if not 'debris_hd' in cfg.BASENAMES: - cfg.BASENAMES['debris_hd'] = ('debris_hd.tif', 'Raster of debris thickness data') -if not 'debris_ed' in cfg.BASENAMES: - cfg.BASENAMES['debris_ed'] = ('debris_ed.tif', 'Raster of debris enhancement factor data') +if "debris_hd" not in cfg.BASENAMES: + cfg.BASENAMES["debris_hd"] = ( + "debris_hd.tif", + "Raster of debris thickness data", + ) +if "debris_ed" not in cfg.BASENAMES: + cfg.BASENAMES["debris_ed"] = ( + "debris_ed.tif", + "Raster of debris enhancement factor data", + ) -@entity_task(log, writes=['debris_hd', 'debris_ed']) -def debris_to_gdir(gdir, debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['debris_relpath']}", add_to_gridded=True, hd_max=5, hd_min=0, ed_max=10, ed_min=0): + +@entity_task(log, writes=["debris_hd", "debris_ed"]) +def debris_to_gdir( + gdir, + debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['debris_relpath']}", + add_to_gridded=True, + hd_max=5, + hd_min=0, + ed_max=10, + ed_min=0, +): """Reproject the debris thickness and enhancement factor files to the given glacier directory - + Variables are exported as new files in the glacier directory. - Reprojecting debris data from one map proj to another is done. + Reprojecting debris data from one map proj to another is done. We use bilinear interpolation to reproject the velocities to the local glacier map. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ - - assert os.path.exists(debris_dir), "Error: debris directory does not exist." - - hd_dir = debris_dir + 'hd_tifs/' + gdir.rgi_region + '/' - ed_dir = debris_dir + 'ed_tifs/' + gdir.rgi_region + '/' - - glac_str_nolead = str(int(gdir.rgi_region)) + '.' + gdir.rgi_id.split('-')[1].split('.')[1] - + + assert os.path.exists(debris_dir), ( + "Error: debris directory does not exist." + ) + + hd_dir = debris_dir + "hd_tifs/" + gdir.rgi_region + "/" + ed_dir = debris_dir + "ed_tifs/" + gdir.rgi_region + "/" + + glac_str_nolead = ( + str(int(gdir.rgi_region)) + + "." + + gdir.rgi_id.split("-")[1].split(".")[1] + ) + # If debris thickness data exists, then write to glacier directory - if os.path.exists(hd_dir + glac_str_nolead + '_hdts_m.tif'): - hd_fn = hd_dir + glac_str_nolead + '_hdts_m.tif' - elif os.path.exists(hd_dir + glac_str_nolead + '_hdts_m_extrap.tif'): - hd_fn = hd_dir + glac_str_nolead + '_hdts_m_extrap.tif' - else: + if os.path.exists(hd_dir + glac_str_nolead + "_hdts_m.tif"): + hd_fn = hd_dir + glac_str_nolead + "_hdts_m.tif" + elif os.path.exists(hd_dir + glac_str_nolead + "_hdts_m_extrap.tif"): + hd_fn = hd_dir + glac_str_nolead + "_hdts_m_extrap.tif" + else: hd_fn = None - + if hd_fn is not None: - rasterio_to_gdir(gdir, hd_fn, 'debris_hd', resampling='bilinear') + rasterio_to_gdir(gdir, hd_fn, "debris_hd", resampling="bilinear") if add_to_gridded and hd_fn is not None: - output_fn = gdir.get_filepath('debris_hd') - + output_fn = gdir.get_filepath("debris_hd") + # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask - data[data>hd_max] = 0 - data[data hd_max] = 0 + data[data < hd_min] = 0 + # Write data - vn = 'debris_hd' + vn = "debris_hd" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = 'm' - v.long_name = 'Debris thicknness' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "m" + v.long_name = "Debris thicknness" v[:] = data - + # If debris enhancement factor data exists, then write to glacier directory - if os.path.exists(ed_dir + glac_str_nolead + '_meltfactor.tif'): - ed_fn = ed_dir + glac_str_nolead + '_meltfactor.tif' - elif os.path.exists(ed_dir + glac_str_nolead + '_meltfactor_extrap.tif'): - ed_fn = ed_dir + glac_str_nolead + '_meltfactor_extrap.tif' - else: + if os.path.exists(ed_dir + glac_str_nolead + "_meltfactor.tif"): + ed_fn = ed_dir + glac_str_nolead + "_meltfactor.tif" + elif os.path.exists(ed_dir + glac_str_nolead + "_meltfactor_extrap.tif"): + ed_fn = ed_dir + glac_str_nolead + "_meltfactor_extrap.tif" + else: ed_fn = None - + if ed_fn is not None: - rasterio_to_gdir(gdir, ed_fn, 'debris_ed', resampling='bilinear') + rasterio_to_gdir(gdir, ed_fn, "debris_ed", resampling="bilinear") if add_to_gridded and ed_fn is not None: - output_fn = gdir.get_filepath('debris_ed') + output_fn = gdir.get_filepath("debris_ed") # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask - data[data>ed_max] = 1 - data[data ed_max] = 1 + data[data < ed_min] = 1 # Write data - vn = 'debris_ed' + vn = "debris_ed" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = '-' - v.long_name = 'Debris enhancement factor' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "-" + v.long_name = "Debris enhancement factor" v[:] = data - -@entity_task(log, writes=['inversion_flowlines']) -def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): +@entity_task(log, writes=["inversion_flowlines"]) +def debris_binned(gdir, ignore_debris=False, fl_str="inversion_flowlines"): """Bin debris thickness and enhancement factors. - + Updates the 'inversion_flowlines' save file. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` @@ -138,40 +175,51 @@ def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): try: flowlines = gdir.read_pickle(fl_str) fl = flowlines[0] - - assert len(flowlines) == 1, 'Error: binning debris only works for single flowlines at present' - + + assert len(flowlines) == 1, ( + "Error: binning debris only works for single flowlines at present" + ) + except: - flowlines = None - + flowlines = None + if flowlines is not None: # Add binned debris thickness and enhancement factors to flowlines - if os.path.exists(gdir.get_filepath('debris_hd')) and ignore_debris==False: - ds = xr.open_dataset(gdir.get_filepath('gridded_data')) - glacier_mask = ds['glacier_mask'].values - topo = ds['topo_smoothed'].values - hd = ds['debris_hd'].values - ed = ds['debris_ed'].values - + if ( + os.path.exists(gdir.get_filepath("debris_hd")) + and ignore_debris == False + ): + ds = xr.open_dataset(gdir.get_filepath("gridded_data")) + glacier_mask = ds["glacier_mask"].values + topo = ds["topo_smoothed"].values + hd = ds["debris_hd"].values + ed = ds["debris_ed"].values + # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) topo_onglac = topo[idx_glac] hd_onglac = hd[idx_glac] ed_onglac = ed[idx_glac] - - # Bin edges + + # Bin edges nbins = len(fl.dis_on_line) z_center = (fl.surface_h[0:-1] + fl.surface_h[1:]) / 2 - z_bin_edges = np.concatenate((np.array([topo[idx_glac].max() + 1]), - z_center, - np.array([topo[idx_glac].min() - 1]))) + z_bin_edges = np.concatenate( + ( + np.array([topo[idx_glac].max() + 1]), + z_center, + np.array([topo[idx_glac].min() - 1]), + ) + ) # Loop over bins and calculate the mean debris thickness and enhancement factor for each bin hd_binned = np.zeros(nbins) - ed_binned = np.ones(nbins) - for nbin in np.arange(0,len(z_bin_edges)-1): + ed_binned = np.ones(nbins) + for nbin in np.arange(0, len(z_bin_edges) - 1): bin_max = z_bin_edges[nbin] - bin_min = z_bin_edges[nbin+1] - bin_idx = np.where((topo_onglac < bin_max) & (topo_onglac >= bin_min))[0] + bin_min = z_bin_edges[nbin + 1] + bin_idx = np.where( + (topo_onglac < bin_max) & (topo_onglac >= bin_min) + )[0] # Debris thickness and enhancement factors for on-glacier bins if len(bin_idx) > 0: hd_binned[nbin] = np.nanmean(hd_onglac[bin_idx]) @@ -185,16 +233,15 @@ def debris_binned(gdir, ignore_debris=False, fl_str='inversion_flowlines'): ed_binned[nbin] = ed_terminus else: hd_binned[nbin] = 0 - ed_binned[nbin] = 1 - + ed_binned[nbin] = 1 + fl.debris_hd = hd_binned fl.debris_ed = ed_binned - + else: nbins = len(fl.dis_on_line) fl.debris_hd = np.zeros(nbins) fl.debris_ed = np.ones(nbins) - + # Overwrite pickle gdir.write_pickle(flowlines, fl_str) - \ No newline at end of file diff --git a/pygem/shop/icethickness.py b/pygem/shop/icethickness.py index dcdc4656..f1f01de2 100755 --- a/pygem/shop/icethickness.py +++ b/pygem/shop/icethickness.py @@ -5,138 +5,177 @@ Distrubted under the MIT lisence """ -import os + import logging +import os +import pickle import numpy as np -import pandas as pd -import pickle import rasterio import xarray as xr - from oggm import cfg -from oggm.utils import entity_task from oggm.core.gis import rasterio_to_gdir -from oggm.utils import ncDataset +from oggm.utils import entity_task, ncDataset + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file -if not 'consensus_mass' in cfg.BASENAMES: - cfg.BASENAMES['consensus_mass'] = ('consensus_mass.pkl', 'Glacier mass from consensus ice thickness data') -if not 'consensus_h' in cfg.BASENAMES: - cfg.BASENAMES['consensus_h'] = ('consensus_h.tif', 'Raster of consensus ice thickness data') +if "consensus_mass" not in cfg.BASENAMES: + cfg.BASENAMES["consensus_mass"] = ( + "consensus_mass.pkl", + "Glacier mass from consensus ice thickness data", + ) +if "consensus_h" not in cfg.BASENAMES: + cfg.BASENAMES["consensus_h"] = ( + "consensus_h.tif", + "Raster of consensus ice thickness data", + ) # Module logger log = logging.getLogger(__name__) -@entity_task(log, writes=['consensus_mass']) -def consensus_gridded(gdir, h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['calib']['data']['icethickness']['h_consensus_relpath']}", add_mass=True, add_to_gridded=True): + +@entity_task(log, writes=["consensus_mass"]) +def consensus_gridded( + gdir, + h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['calib']['data']['icethickness']['h_consensus_relpath']}", + add_mass=True, + add_to_gridded=True, +): """Bin consensus ice thickness and add total glacier mass to the given glacier directory - + Updates the 'inversion_flowlines' save file and creates new consensus_mass.pkl - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ # If binned mb data exists, then write to glacier directory - h_fn = h_consensus_fp + 'RGI60-' + gdir.rgi_region + '/' + gdir.rgi_id + '_thickness.tif' - assert os.path.exists(h_fn), 'Error: h_consensus_fullfn for ' + gdir.rgi_id + ' does not exist.' - + h_fn = ( + h_consensus_fp + + "RGI60-" + + gdir.rgi_region + + "/" + + gdir.rgi_id + + "_thickness.tif" + ) + assert os.path.exists(h_fn), ( + "Error: h_consensus_fullfn for " + gdir.rgi_id + " does not exist." + ) + # open consensus ice thickness estimate - h_dr = rasterio.open(h_fn, 'r', driver='GTiff') + h_dr = rasterio.open(h_fn, "r", driver="GTiff") h = h_dr.read(1).astype(rasterio.float32) - + # Glacier mass [kg] - glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms['constants']['density_ice'] -# print(glacier_mass_raw) + glacier_mass_raw = (h * h_dr.res[0] * h_dr.res[1]).sum() * pygem_prms[ + "constants" + ]["density_ice"] + # print(glacier_mass_raw) if add_mass: # Pickle data - consensus_fn = gdir.get_filepath('consensus_mass') - with open(consensus_fn, 'wb') as f: + consensus_fn = gdir.get_filepath("consensus_mass") + with open(consensus_fn, "wb") as f: pickle.dump(glacier_mass_raw, f) - - + if add_to_gridded: - rasterio_to_gdir(gdir, h_fn, 'consensus_h', resampling='bilinear') - output_fn = gdir.get_filepath('consensus_h') + rasterio_to_gdir(gdir, h_fn, "consensus_h", resampling="bilinear") + output_fn = gdir.get_filepath("consensus_h") # append the debris data to the gridded dataset with rasterio.open(output_fn) as src: - grids_file = gdir.get_filepath('gridded_data') - with ncDataset(grids_file, 'a') as nc: + grids_file = gdir.get_filepath("gridded_data") + with ncDataset(grids_file, "a") as nc: # Mask values - glacier_mask = nc['glacier_mask'][:] + glacier_mask = nc["glacier_mask"][:] data = src.read(1) * glacier_mask # Pixel area pixel_m2 = abs(gdir.grid.dx * gdir.grid.dy) # Glacier mass [kg] reprojoected (may lose or gain mass depending on resampling algorithm) - glacier_mass_reprojected = (data * pixel_m2).sum() * pygem_prms['constants']['density_ice'] + glacier_mass_reprojected = ( + data * pixel_m2 + ).sum() * pygem_prms["constants"]["density_ice"] # Scale data to ensure conservation of mass during reprojection - data_scaled = data * glacier_mass_raw / glacier_mass_reprojected -# glacier_mass = (data_scaled * pixel_m2).sum() * pygem_prms['constants']['density_ice'] -# print(glacier_mass) - + data_scaled = ( + data * glacier_mass_raw / glacier_mass_reprojected + ) + # glacier_mass = (data_scaled * pixel_m2).sum() * pygem_prms['constants']['density_ice'] + # print(glacier_mass) + # Write data - vn = 'consensus_h' + vn = "consensus_h" if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) - v.units = 'm' - v.long_name = 'Consensus ice thicknness' + v = nc.createVariable( + vn, + "f8", + ( + "y", + "x", + ), + zlib=True, + ) + v.units = "m" + v.long_name = "Consensus ice thicknness" v[:] = data_scaled - -@entity_task(log, writes=['inversion_flowlines']) + +@entity_task(log, writes=["inversion_flowlines"]) def consensus_binned(gdir): """Bin consensus ice thickness ice estimates. - + Updates the 'inversion_flowlines' save file. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` where to write the data """ - flowlines = gdir.read_pickle('inversion_flowlines') + flowlines = gdir.read_pickle("inversion_flowlines") fl = flowlines[0] - - assert len(flowlines) == 1, 'Error: binning debris data set up only for single flowlines at present' - + + assert len(flowlines) == 1, ( + "Error: binning debris data set up only for single flowlines at present" + ) + # Add binned debris thickness and enhancement factors to flowlines - ds = xr.open_dataset(gdir.get_filepath('gridded_data')) - glacier_mask = ds['glacier_mask'].values - topo = ds['topo_smoothed'].values - h = ds['consensus_h'].values + ds = xr.open_dataset(gdir.get_filepath("gridded_data")) + glacier_mask = ds["glacier_mask"].values + topo = ds["topo_smoothed"].values + h = ds["consensus_h"].values # Only bin on-glacier values idx_glac = np.where(glacier_mask == 1) topo_onglac = topo[idx_glac] h_onglac = h[idx_glac] - # Bin edges + # Bin edges nbins = len(fl.dis_on_line) z_center = (fl.surface_h[0:-1] + fl.surface_h[1:]) / 2 - z_bin_edges = np.concatenate((np.array([topo[idx_glac].max() + 1]), - z_center, - np.array([topo[idx_glac].min() - 1]))) + z_bin_edges = np.concatenate( + ( + np.array([topo[idx_glac].max() + 1]), + z_center, + np.array([topo[idx_glac].min() - 1]), + ) + ) # Loop over bins and calculate the mean debris thickness and enhancement factor for each bin - h_binned = np.zeros(nbins) - for nbin in np.arange(0,len(z_bin_edges)-1): + h_binned = np.zeros(nbins) + for nbin in np.arange(0, len(z_bin_edges) - 1): bin_max = z_bin_edges[nbin] - bin_min = z_bin_edges[nbin+1] + bin_min = z_bin_edges[nbin + 1] bin_idx = np.where((topo_onglac < bin_max) & (topo_onglac >= bin_min)) try: h_binned[nbin] = h_onglac[bin_idx].mean() except: h_binned[nbin] = 0 - + fl.consensus_h = h_binned - + # Overwrite pickle - gdir.write_pickle(flowlines, 'inversion_flowlines') - \ No newline at end of file + gdir.write_pickle(flowlines, "inversion_flowlines") diff --git a/pygem/shop/mbdata.py b/pygem/shop/mbdata.py index f73ebdc2..d84081a2 100755 --- a/pygem/shop/mbdata.py +++ b/pygem/shop/mbdata.py @@ -5,44 +5,54 @@ Distrubted under the MIT lisence """ + # Built-in libaries -import argparse -import os import json import logging +import os + # External libraries -from datetime import datetime, timedelta +from datetime import timedelta + import numpy as np import pandas as pd -import pickle -#import rasterio -#import xarray as xr + +# import rasterio +# import xarray as xr # Local libraries from oggm import cfg from oggm.utils import entity_task -#from oggm.core.gis import rasterio_to_gdir -#from oggm.utils import ncDataset + +# from oggm.core.gis import rasterio_to_gdir +# from oggm.utils import ncDataset # pygem imports import pygem.setup.config as config + # check for config config.ensure_config() # read the config pygem_prms = config.read_config() -import pygem.pygem_modelsetup as modelsetup # Module logger log = logging.getLogger(__name__) # Add the new name "mb_calib_pygem" to the list of things that the GlacierDirectory understands -if not 'mb_calib_pygem' in cfg.BASENAMES: - cfg.BASENAMES['mb_calib_pygem'] = ('mb_calib_pygem.json', 'Mass balance observations for model calibration') +if "mb_calib_pygem" not in cfg.BASENAMES: + cfg.BASENAMES["mb_calib_pygem"] = ( + "mb_calib_pygem.json", + "Mass balance observations for model calibration", + ) + - -@entity_task(log, writes=['mb_calib_pygem']) -def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup']['include_frontalablation']): +@entity_task(log, writes=["mb_calib_pygem"]) +def mb_df_to_gdir( + gdir, + mb_dataset="Hugonnet2021", + facorrected=pygem_prms["setup"]["include_frontalablation"], +): """Select specific mass balance and add observations to the given glacier directory - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` @@ -50,20 +60,32 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup """ # get dataset name (could potentially be swapped with others besides Hugonnet21) mbdata_fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}" - mbdata_fp_fa = mbdata_fp + pygem_prms['calib']['data']['massbalance']['hugonnet2021_facorrected_fn'] + mbdata_fp_fa = ( + mbdata_fp + + pygem_prms["calib"]["data"]["massbalance"][ + "hugonnet2021_facorrected_fn" + ] + ) if facorrected and os.path.exists(mbdata_fp_fa): mbdata_fp = mbdata_fp_fa else: - mbdata_fp = mbdata_fp + pygem_prms['calib']['data']['massbalance']['hugonnet2021_fn'] + mbdata_fp = ( + mbdata_fp + + pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_fn"] + ) + + assert os.path.exists(mbdata_fp), ( + "Error, mass balance dataset does not exist: {mbdata_fp}" + ) + assert "hugonnet2021" in mbdata_fp.lower(), ( + "Error, mass balance dataset not yet supported: {mbdata_fp}" + ) + rgiid_cn = "rgiid" + mb_cn = "mb_mwea" + mberr_cn = "mb_mwea_err" + mb_clim_cn = "mb_clim_mwea" + mberr_clim_cn = "mb_clim_mwea_err" - assert os.path.exists(mbdata_fp), "Error, mass balance dataset does not exist: {mbdata_fp}" - assert 'hugonnet2021' in mbdata_fp.lower(), "Error, mass balance dataset not yet supported: {mbdata_fp}" - rgiid_cn = 'rgiid' - mb_cn = 'mb_mwea' - mberr_cn = 'mb_mwea_err' - mb_clim_cn = 'mb_clim_mwea' - mberr_clim_cn = 'mb_clim_mwea_err' - # read reference mass balance dataset and pull data of interest mb_df = pd.read_csv(mbdata_fp) mb_df_rgiids = list(mb_df[rgiid_cn]) @@ -71,19 +93,19 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup if gdir.rgi_id in mb_df_rgiids: # RGIId index rgiid_idx = np.where(gdir.rgi_id == mb_df[rgiid_cn])[0][0] - + # Glacier-wide mass balance mb_mwea = mb_df.loc[rgiid_idx, mb_cn] mb_mwea_err = mb_df.loc[rgiid_idx, mberr_cn] - + if mb_clim_cn in mb_df.columns: mb_clim_mwea = mb_df.loc[rgiid_idx, mb_clim_cn] mb_clim_mwea_err = mb_df.loc[rgiid_idx, mberr_clim_cn] else: mb_clim_mwea = None mb_clim_mwea_err = None - - t1_str, t2_str = mb_df.loc[rgiid_idx, 'period'].split('_') + + t1_str, t2_str = mb_df.loc[rgiid_idx, "period"].split("_") t1_datetime = pd.to_datetime(t1_str) t2_datetime = pd.to_datetime(t2_str) @@ -94,46 +116,50 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # Record data mbdata = { - key: value + key: value for key, value in { - 'mb_mwea': float(mb_mwea), - 'mb_mwea_err': float(mb_mwea_err), - 'mb_clim_mwea': float(mb_clim_mwea) if mb_clim_mwea is not None else None, - 'mb_clim_mwea_err': float(mb_clim_mwea_err) if mb_clim_mwea_err is not None else None, - 't1_str': t1_str, - 't2_str': t2_str, - 'nyears': nyears, + "mb_mwea": float(mb_mwea), + "mb_mwea_err": float(mb_mwea_err), + "mb_clim_mwea": float(mb_clim_mwea) + if mb_clim_mwea is not None + else None, + "mb_clim_mwea_err": float(mb_clim_mwea_err) + if mb_clim_mwea_err is not None + else None, + "t1_str": t1_str, + "t2_str": t2_str, + "nyears": nyears, }.items() if value is not None } - mb_fn = gdir.get_filepath('mb_calib_pygem') - with open(mb_fn, 'w') as f: + mb_fn = gdir.get_filepath("mb_calib_pygem") + with open(mb_fn, "w") as f: json.dump(mbdata, f) -#@entity_task(log, writes=['mb_obs']) -#def mb_bins_to_glacierwide(gdir, mb_binned_fp=pygem_prms.mb_binned_fp): +# @entity_task(log, writes=['mb_obs']) +# def mb_bins_to_glacierwide(gdir, mb_binned_fp=pygem_prms.mb_binned_fp): # """Convert binned mass balance data to glacier-wide and add observations to the given glacier directory -# +# # Parameters # ---------- # gdir : :py:class:`oggm.GlacierDirectory` # where to write the data # """ -# +# # assert os.path.exists(mb_binned_fp), "Error: mb_binned_fp does not exist." -# +# # glac_str_nolead = str(int(gdir.rgi_region)) + '.' + gdir.rgi_id.split('-')[1].split('.')[1] -# +# # # If binned mb data exists, then write to glacier directory # if os.path.exists(mb_binned_fp + gdir.rgi_region + '/' + glac_str_nolead + '_mb_bins.csv'): # mb_binned_fn = mb_binned_fp + gdir.rgi_region + '/' + glac_str_nolead + '_mb_bins.csv' -# else: +# else: # mb_binned_fn = None -# +# # if mb_binned_fn is not None: # mbdata_fn = gdir.get_filepath('mb_obs') -# +# # # Glacier-wide mass balance # mb_binned_df = pd.read_csv(mb_binned_fn) # area_km2_valid = mb_binned_df['z1_bin_area_valid_km2'].sum() @@ -141,7 +167,7 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # mb_mwea_err = 0.3 # t1 = 2000 # t2 = 2018 -# +# # # Record data # mbdata = {'mb_mwea': mb_mwea, # 'mb_mwea_err': mb_mwea_err, @@ -150,17 +176,17 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # 'area_km2_valid': area_km2_valid} # with open(mbdata_fn, 'wb') as f: # pickle.dump(mbdata, f) -# +# # ##%% -#def mb_bins_to_reg_glacierwide(mb_binned_fp=pygem_prms.mb_binned_fp, O1Regions=['01']): +# def mb_bins_to_reg_glacierwide(mb_binned_fp=pygem_prms.mb_binned_fp, O1Regions=['01']): # # Delete these import # mb_binned_fp=pygem_prms.mb_binned_fp # O1Regions=['19'] -# +# # print('\n\n SPECIFYING UNCERTAINTY AS 0.3 mwea for model development - needs to be updated from mb providers!\n\n') # reg_mb_mwea_err = 0.3 -# +# # mb_yrfrac_dict = {'01': [2000.419, 2018.419], # '02': [2000.128, 2012], # '03': [2000.419, 2018.419], @@ -177,20 +203,20 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # '16': [2000.128, 2013.128], # '17': [2000.128, 2013.128], # '18': [2000.128, 2013]} -# +# # for reg in O1Regions: # reg_fp = mb_binned_fp + reg + '/' -# +# # main_glac_rgi = modelsetup.selectglaciersrgitable(rgi_regionsO1=[reg], rgi_regionsO2='all', rgi_glac_number='all') -# +# # reg_binned_fns = [] # for i in os.listdir(reg_fp): # if i.endswith('_mb_bins.csv'): # reg_binned_fns.append(i) # reg_binned_fns = sorted(reg_binned_fns) -# +# # print('Region ' + reg + ' has binned data for ' + str(len(reg_binned_fns)) + ' glaciers.') -# +# # reg_mb_df_cns = ['RGIId', 'O1Region', 'O2Region', 'area_km2', 'mb_mwea', 'mb_mwea_err', 't1', 't2', 'perc_valid'] # reg_mb_df = pd.DataFrame(np.zeros((main_glac_rgi.shape[0], len(reg_mb_df_cns))), columns=reg_mb_df_cns) # reg_mb_df.loc[:,:] = np.nan @@ -198,13 +224,13 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # reg_mb_df.loc[:, 'O1Region'] = main_glac_rgi['O1Region'] # reg_mb_df.loc[:, 'O2Region'] = main_glac_rgi['O2Region'] # reg_mb_df.loc[:, 'area_km2'] = main_glac_rgi['Area'] -# +# # # Process binned files # for nfn, reg_binned_fn in enumerate(reg_binned_fns): -# +# # if nfn%500 == 0: # print(' ', nfn, reg_binned_fn) -# +# # mb_binned_df = pd.read_csv(reg_fp + reg_binned_fn) # glac_str = reg_binned_fn.split('_')[0] # glac_rgiid = 'RGI60-' + glac_str.split('.')[0].zfill(2) + '.' + glac_str.split('.')[1] @@ -215,13 +241,13 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # t1 = mb_yrfrac_dict[reg][0] # t2 = mb_yrfrac_dict[reg][1] # perc_valid = area_km2_valid / reg_mb_df.loc[rgi_idx,'area_km2'] * 100 -# +# # reg_mb_df.loc[rgi_idx,'mb_mwea'] = mb_mwea # reg_mb_df.loc[rgi_idx,'mb_mwea_err'] = mb_mwea_err # reg_mb_df.loc[rgi_idx,'t1'] = t1 # reg_mb_df.loc[rgi_idx,'t2'] = t2 # reg_mb_df.loc[rgi_idx,'perc_valid'] = perc_valid -# +# # #%% # # Quality control # O2Regions = list(set(list(main_glac_rgi['O2Region'].values))) @@ -230,41 +256,39 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup # for O2Region in O2Regions: # reg_mb_df_subset = reg_mb_df[reg_mb_df['O2Region'] == O2Region] # reg_mb_df_subset = reg_mb_df_subset.dropna(subset=['mb_mwea']) -# +# # # Use 1.5*IQR to remove outliers # reg_mb_mwea_25 = np.percentile(reg_mb_df_subset['mb_mwea'], 25) # reg_mb_mwea_50 = np.percentile(reg_mb_df_subset['mb_mwea'], 50) # reg_mb_mwea_75 = np.percentile(reg_mb_df_subset['mb_mwea'], 75) # reg_mb_mwea_iqr = reg_mb_mwea_75 - reg_mb_mwea_25 -# -# print(np.round(reg_mb_mwea_25,2), np.round(reg_mb_mwea_50,2), np.round(reg_mb_mwea_75,2), +# +# print(np.round(reg_mb_mwea_25,2), np.round(reg_mb_mwea_50,2), np.round(reg_mb_mwea_75,2), # np.round(reg_mb_mwea_iqr,2)) -# +# # reg_mb_mwea_bndlow = reg_mb_mwea_25 - 1.5 * reg_mb_mwea_iqr # reg_mb_mwea_bndhigh = reg_mb_mwea_75 + 1.5 * reg_mb_mwea_iqr -# +# # # Record RGIIds that are outliers -# rgiid_outliers.extend(reg_mb_df_subset[(reg_mb_df_subset['mb_mwea'] < reg_mb_mwea_bndlow) | +# rgiid_outliers.extend(reg_mb_df_subset[(reg_mb_df_subset['mb_mwea'] < reg_mb_mwea_bndlow) | # (reg_mb_df_subset['mb_mwea'] > reg_mb_mwea_bndhigh)]['RGIId'].values) # # Select non-outliers and record mean -# reg_mb_df_subset_qc = reg_mb_df_subset[(reg_mb_df_subset['mb_mwea'] >= reg_mb_mwea_bndlow) & +# reg_mb_df_subset_qc = reg_mb_df_subset[(reg_mb_df_subset['mb_mwea'] >= reg_mb_mwea_bndlow) & # (reg_mb_df_subset['mb_mwea'] <= reg_mb_mwea_bndhigh)] -# +# # reg_mb_mwea_qc_mean = reg_mb_df_subset_qc['mb_mwea'].mean() # O2Regions_mb_mwea_dict[O2Region] = reg_mb_mwea_qc_mean -# +# # #%% # print('CREATE DICTIONARY FOR RGIIDs with nan values or those that are outliers') # # print(A['mb_mwea'].mean(), A['mb_mwea'].std(), A['mb_mwea'].min(), A['mb_mwea'].max()) # # print(reg_mb_mwea, reg_mb_mwea_std) -# -# +# +# # #%% # reg_mb_fn = reg + '_mb_glacwide_all.csv' # reg_mb_df.to_csv(mb_binned_fp + reg_mb_fn, index=False) -# +# # print('TO-DO LIST:') # print(' - quality control based on 3-sigma filter like Shean') # print(' - extrapolate for missing or outlier glaciers by region') - - diff --git a/pygem/shop/oib.py b/pygem/shop/oib.py index cbc79edc..24bf9c28 100644 --- a/pygem/shop/oib.py +++ b/pygem/shop/oib.py @@ -7,22 +7,37 @@ NASA Operation IceBridge data and processing class """ -import re, os, glob, json, pickle, datetime, warnings, sys + +import datetime +import glob +import json +import re +import warnings + +import matplotlib.pyplot as plt import numpy as np import pandas as pd -from scipy import signal, stats -import matplotlib.pyplot as plt +from scipy import stats + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file + class oib: - def __init__(self, rgi6id='', rgi7id=''): + def __init__(self, rgi6id="", rgi7id=""): self.oib_datpath = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['oib']['oib_relpath']}" - self.rgi7_6_df = pd.read_csv(f"{self.oib_datpath}/../oibak_rgi6_rgi7_ids.csv") - self.rgi7_6_df['rgi7id'] = self.rgi7_6_df['rgi7id'].str.split('RGI2000-v7.0-G-').str[1] - self.rgi7_6_df['rgi6id'] = self.rgi7_6_df['rgi6id'].str.split('RGI60-').str[1] + self.rgi7_6_df = pd.read_csv( + f"{self.oib_datpath}/../oibak_rgi6_rgi7_ids.csv" + ) + self.rgi7_6_df["rgi7id"] = ( + self.rgi7_6_df["rgi7id"].str.split("RGI2000-v7.0-G-").str[1] + ) + self.rgi7_6_df["rgi6id"] = ( + self.rgi7_6_df["rgi6id"].str.split("RGI60-").str[1] + ) self.rgi6id = rgi6id self.rgi7id = rgi7id self.name = None @@ -32,17 +47,22 @@ def __init__(self, rgi6id='', rgi7id=''): self.bin_edges = None self.bin_centers = None self.bin_area = None - + def _get_diffs(self): return self.oib_diffs + def _get_dbldiffs(self): return self.dbl_diffs + def _get_centers(self): return self.bin_centers + def _get_edges(self): return self.bin_edges + def _get_area(self): return self.bin_area + def _get_name(self): return self.name @@ -51,36 +71,50 @@ def _rgi6torgi7id(self, debug=False): return RGI version 7 glacier id for a given RGI version 6 id """ - self.rgi6id = self.rgi6id.split('.')[0].zfill(2) + '.' + self.rgi6id.split('.')[1] + self.rgi6id = ( + self.rgi6id.split(".")[0].zfill(2) + + "." + + self.rgi6id.split(".")[1] + ) # rgi7id = self.rgi7_6_df.loc[lambda self.rgi7_6_df: self.rgi7_6_df['rgi6id'] == rgi6id,'rgi7id'].tolist() - rgi7id = self.rgi7_6_df.loc[self.rgi7_6_df['rgi6id'] == self.rgi6id, 'rgi7id'].tolist() - if len(rgi7id)==1: - self.rgi7id = rgi7id[0] + rgi7id = self.rgi7_6_df.loc[ + self.rgi7_6_df["rgi6id"] == self.rgi6id, "rgi7id" + ].tolist() + if len(rgi7id) == 1: + self.rgi7id = rgi7id[0] if debug: - print(f'RGI6:{self.rgi6id} -> RGI7:{self.rgi7id}') - elif len(rgi7id)==0: - raise IndexError(f'No matching RGI7Id for {self.rgi6id}') - elif len(rgi7id)>1: - raise IndexError(f'More than one matching RGI7Id for {self.rgi6id}') - + print(f"RGI6:{self.rgi6id} -> RGI7:{self.rgi7id}") + elif len(rgi7id) == 0: + raise IndexError(f"No matching RGI7Id for {self.rgi6id}") + elif len(rgi7id) > 1: + raise IndexError( + f"More than one matching RGI7Id for {self.rgi6id}" + ) def _rgi7torgi6id(self, debug=False): """ return RGI version 6 glacier id for a given RGI version 7 id """ - self.rgi7id = self.rgi7id.split('-')[0].zfill(2) + '-' + self.rgi7id.split('-')[1] + self.rgi7id = ( + self.rgi7id.split("-")[0].zfill(2) + + "-" + + self.rgi7id.split("-")[1] + ) # rgi6id = self.rgi7_6_df.loc[lambda self.rgi7_6_df: self.rgi7_6_df['rgi7id'] == rgi7id,'rgi6id'].tolist() - rgi6id = self.rgi7_6_df.loc[self.rgi7_6_df['rgi7id'] == self.rgi7id, 'rgi6id'].tolist() - if len(rgi6id)==1: + rgi6id = self.rgi7_6_df.loc[ + self.rgi7_6_df["rgi7id"] == self.rgi7id, "rgi6id" + ].tolist() + if len(rgi6id) == 1: self.rgi6id = rgi6id[0] if debug: - print(f'RGI7:{self.rgi7id} -> RGI6:{self.rgi6id}') - elif len(rgi6id)==0: - raise IndexError(f'No matching RGI6Id for {self.rgi7id}') - elif len(rgi6id)>1: - raise IndexError(f'More than one matching RGI6Id for {self.rgi7id}') - + print(f"RGI7:{self.rgi7id} -> RGI6:{self.rgi6id}") + elif len(rgi6id) == 0: + raise IndexError(f"No matching RGI6Id for {self.rgi7id}") + elif len(rgi6id) > 1: + raise IndexError( + f"More than one matching RGI6Id for {self.rgi7id}" + ) def _date_check(self, dt_obj): """ @@ -88,26 +122,30 @@ def _date_check(self, dt_obj): """ dim = pd.Series(dt_obj).dt.daysinmonth.iloc[0] if dt_obj.day < dim // 2: - dt_obj_ = datetime.datetime(year=dt_obj.year, month=dt_obj.month, day=1) + dt_obj_ = datetime.datetime( + year=dt_obj.year, month=dt_obj.month, day=1 + ) else: - dt_obj_ = datetime.datetime(year=dt_obj.year, month=dt_obj.month+1, day=1) + dt_obj_ = datetime.datetime( + year=dt_obj.year, month=dt_obj.month + 1, day=1 + ) return dt_obj_ - def _load(self): """ load Operation IceBridge data """ - oib_fpath = glob.glob(f"{self.oib_datpath}/diffstats5_*{self.rgi7id}*.json") - if len(oib_fpath)==0: + oib_fpath = glob.glob( + f"{self.oib_datpath}/diffstats5_*{self.rgi7id}*.json" + ) + if len(oib_fpath) == 0: return else: oib_fpath = oib_fpath[0] # load diffstats file - with open(oib_fpath, 'rb') as f: + with open(oib_fpath, "rb") as f: self.oib_dict = json.load(f) - self.name = split_by_uppercase(self.oib_dict['glacier_shortname']) - + self.name = split_by_uppercase(self.oib_dict["glacier_shortname"]) def _parsediffs(self, filter_count_pctl=10, debug=False): """ @@ -115,36 +153,59 @@ def _parsediffs(self, filter_count_pctl=10, debug=False): diffs_stacked: np.ndarray (#bins, #surveys) """ # get seasons stored in oib diffs - seasons = list(set(self.oib_dict.keys()).intersection(['march','may','august'])) + seasons = list( + set(self.oib_dict.keys()).intersection(["march", "may", "august"]) + ) for ssn in seasons: for yr in list(self.oib_dict[ssn].keys()): # get survey date - doy_int = int(np.ceil(self.oib_dict[ssn][yr]['mean_doy'])) - dt_obj = datetime.datetime.strptime(f'{int(yr)}-{doy_int}', '%Y-%j') + doy_int = int(np.ceil(self.oib_dict[ssn][yr]["mean_doy"])) + dt_obj = datetime.datetime.strptime( + f"{int(yr)}-{doy_int}", "%Y-%j" + ) # get survey data and filter by pixel count - diffs = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_median_diffs_vec']) - counts = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_count_vec']) + diffs = np.asarray( + self.oib_dict[ssn][yr]["bin_vals"]["bin_median_diffs_vec"] + ) + counts = np.asarray( + self.oib_dict[ssn][yr]["bin_vals"]["bin_count_vec"] + ) mask = _filter_on_pixel_count(counts, filter_count_pctl) diffs[mask] = np.nan # uncertainty represented by IQR - sigmas = np.asarray(self.oib_dict[ssn][yr]['bin_vals']['bin_interquartile_range_diffs_vec']) + sigmas = np.asarray( + self.oib_dict[ssn][yr]["bin_vals"][ + "bin_interquartile_range_diffs_vec" + ] + ) sigmas[mask] = np.nan # add masked diffs to master dictionary - self.oib_diffs[self._date_check(dt_obj)] = (diffs,sigmas) + self.oib_diffs[self._date_check(dt_obj)] = (diffs, sigmas) # Sort the dictionary by date keys self.oib_diffs = dict(sorted(self.oib_diffs.items())) if debug: - print(f'OIB survey dates:\n{", ".join([str(dt.year)+"-"+str(dt.month)+"-"+str(dt.day) for dt in list(self.oib_diffs.keys())])}') + print( + f"OIB survey dates:\n{', '.join([str(dt.year) + '-' + str(dt.month) + '-' + str(dt.day) for dt in list(self.oib_diffs.keys())])}" + ) # get bin centers - self.bin_centers = (np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec']) + - np.asarray(self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'])) / 2 - self.bin_area = self.oib_dict['aad_dict']['hist_bin_areas_m2'] + self.bin_centers = ( + np.asarray( + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]][ + "bin_vals" + ]["bin_start_vec"] + ) + + np.asarray( + self.oib_dict[ssn][list(self.oib_dict[ssn].keys())[0]][ + "bin_vals" + ]["bin_stop_vec"] + ) + ) / 2 + self.bin_area = self.oib_dict["aad_dict"]["hist_bin_areas_m2"] # bin_edges = oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_start_vec'] # bin_edges.append(oib_dict[ssn][list(oib_dict[ssn].keys())[0]]['bin_vals']['bin_stop_vec'][-1]) # bin_edges = np.asarray(bin_edges) - def _terminus_mask(self, debug=False): """ create mask of missing terminus ice using last oib survey @@ -158,31 +219,35 @@ def _terminus_mask(self, debug=False): mask = [] try: for i in inds: - tmp = diffs[i][lowest_bin:lowest_bin+50] + tmp = diffs[i][lowest_bin : lowest_bin + 50] if np.isnan(tmp).all(): continue else: # find peak we'll bake in the assumption that terminus thickness has decreased over time - we'll thus look for a trough if yr>=2013 (cop30 date) - if survey_dates[i].year>2013: + if survey_dates[i].year > 2013: idx = np.nanargmin(tmp) + lowest_bin else: - tmp = -1*tmp + tmp = -1 * tmp idx = np.nanargmax(tmp) + lowest_bin - mask = np.arange(0,idx+1,1) + mask = np.arange(0, idx + 1, 1) break if debug: plt.figure() - cmap=plt.cm.rainbow(np.linspace(0, 1, len(inds))) + cmap = plt.cm.rainbow(np.linspace(0, 1, len(inds))) for i in inds[::-1]: - plt.plot(diffs[i],label=f'{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}',c=cmap[i]) + plt.plot( + diffs[i], + label=f"{survey_dates[i].year}:{survey_dates[i].month}:{survey_dates[i].day}", + c=cmap[i], + ) if idx: - plt.axvline(idx,c='k',ls=':') - plt.legend(loc='upper right') + plt.axvline(idx, c="k", ls=":") + plt.legend(loc="upper right") plt.show() except Exception as err: if debug: - print(f'_filter_terminus_missing_ice error: {err}') + print(f"_filter_terminus_missing_ice error: {err}") mask = [] # apply mask @@ -190,37 +255,59 @@ def _terminus_mask(self, debug=False): tup[0][mask] = np.nan tup[1][mask] = np.nan - def _rebin(self, agg=100): if agg: # aggregate both model and obs to specified size m bins - nbins = int(np.ceil((self.bin_centers[-1] - self.bin_centers[0]) // agg)) + nbins = int( + np.ceil((self.bin_centers[-1] - self.bin_centers[0]) // agg) + ) with warnings.catch_warnings(): - warnings.filterwarnings('ignore') - for i,(k, tup) in enumerate(self.oib_diffs.items()): - if i==0: - y, self.bin_edges, _ = stats.binned_statistic(x=self.bin_centers, values=tup[0], statistic=np.nanmean, bins=nbins) + warnings.filterwarnings("ignore") + for i, (k, tup) in enumerate(self.oib_diffs.items()): + if i == 0: + y, self.bin_edges, _ = stats.binned_statistic( + x=self.bin_centers, + values=tup[0], + statistic=np.nanmean, + bins=nbins, + ) else: - y = stats.binned_statistic(x=self.bin_centers, values=tup[0], statistic=np.nanmean, bins=self.bin_edges)[0] - s = stats.binned_statistic(x=self.bin_centers, values=tup[1], statistic=np.nanmean, bins=self.bin_edges)[0] - self.oib_diffs[k] = (y,s) - self.bin_area = stats.binned_statistic(x=self.bin_centers, values=self.bin_area, statistic=np.nanmean, bins=self.bin_edges)[0] - self.bin_centers = ((self.bin_edges[:-1] + self.bin_edges[1:]) / 2) - + y = stats.binned_statistic( + x=self.bin_centers, + values=tup[0], + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + s = stats.binned_statistic( + x=self.bin_centers, + values=tup[1], + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + self.oib_diffs[k] = (y, s) + self.bin_area = stats.binned_statistic( + x=self.bin_centers, + values=self.bin_area, + statistic=np.nanmean, + bins=self.bin_edges, + )[0] + self.bin_centers = (self.bin_edges[:-1] + self.bin_edges[1:]) / 2 # double difference all oib diffs from the same season 1+ year apart - def _dbl_diff(self, months=range(1,13)): + def _dbl_diff(self, months=range(1, 13)): # prepopulate dbl_diffs dictionary object will structure with dates, dh, sigma # where dates is a tuple for each double differenced array in the format of (date1,date2), # where date1's cop30 differences were subtracted from date2's to get the dh values for that time span, # and the sigma was taken as the mean sigma from each date - self.dbl_diffs['dates'] = [] - self.dbl_diffs['dh'] = [] - self.dbl_diffs['sigma'] = [] + self.dbl_diffs["dates"] = [] + self.dbl_diffs["dh"] = [] + self.dbl_diffs["sigma"] = [] # loop through months for m in months: # filter and sort dates to include only those in the target month - filtered_dates = sorted([x for x in list(self.oib_diffs.keys()) if x.month == m]) + filtered_dates = sorted( + [x for x in list(self.oib_diffs.keys()) if x.month == m] + ) # Calculate differences for consecutive pairs that are >=1 full year apart for i in range(len(filtered_dates) - 1): date1 = filtered_dates[i] @@ -229,47 +316,63 @@ def _dbl_diff(self, months=range(1,13)): # Check if the pair is at least one full year apart if year_diff >= 1: - self.dbl_diffs['dates'].append((date1,date2)) - self.dbl_diffs['dh'].append(self.oib_diffs[date2][0] - self.oib_diffs[date1][0]) + self.dbl_diffs["dates"].append((date1, date2)) + self.dbl_diffs["dh"].append( + self.oib_diffs[date2][0] - self.oib_diffs[date1][0] + ) # self.dbl_diffs['sigma'].append((self.oib_diffs[date2][1] + self.oib_diffs[date1][1]) / 2) - self.dbl_diffs['sigma'].append(self.oib_diffs[date2][1] + self.oib_diffs[date1][1]) + self.dbl_diffs["sigma"].append( + self.oib_diffs[date2][1] + self.oib_diffs[date1][1] + ) # column stack dh and sigmas into single 2d array - if len(self.dbl_diffs['dh'])>0: - self.dbl_diffs['dh'] = np.column_stack(self.dbl_diffs['dh']) - self.dbl_diffs['sigma'] = np.column_stack(self.dbl_diffs['sigma']) + if len(self.dbl_diffs["dh"]) > 0: + self.dbl_diffs["dh"] = np.column_stack(self.dbl_diffs["dh"]) + self.dbl_diffs["sigma"] = np.column_stack(self.dbl_diffs["sigma"]) else: - self.dbl_diffs['dh'] = np.nan + self.dbl_diffs["dh"] = np.nan # check if deltah is all nan - if np.isnan(self.dbl_diffs['dh']).all(): - self.dbl_diffs['dh'] = None - self.dbl_diffs['sigma'] = None - + if np.isnan(self.dbl_diffs["dh"]).all(): + self.dbl_diffs["dh"] = None + self.dbl_diffs["sigma"] = None - def _elevchange_to_masschange(self, ela, density_ablation=pygem_prms['constants']['density_ice'], density_accumulation=700): + def _elevchange_to_masschange( + self, + ela, + density_ablation=pygem_prms["constants"]["density_ice"], + density_accumulation=700, + ): # convert elevation changes to mass change using piecewise density conversion - if self.dbl_diffs['dh'] is not None: + if self.dbl_diffs["dh"] is not None: # populate density conversion column corresponding to bin center elevation conversion_factor = np.ones(len(self.bin_centers)) - conversion_factor[np.where(self.bin_centers=ela)] = density_accumulation + conversion_factor[np.where(self.bin_centers < ela)] = ( + density_ablation + ) + conversion_factor[np.where(self.bin_centers >= ela)] = ( + density_accumulation + ) # get change in mass per unit area as (dz * rho) [dmass / dm2] - self.dbl_diffs['dmda'] = self.dbl_diffs['dh'] * conversion_factor[:,np.newaxis] - self.dbl_diffs['dmda_err'] = self.dbl_diffs['sigma'] * conversion_factor[:,np.newaxis] + self.dbl_diffs["dmda"] = ( + self.dbl_diffs["dh"] * conversion_factor[:, np.newaxis] + ) + self.dbl_diffs["dmda_err"] = ( + self.dbl_diffs["sigma"] * conversion_factor[:, np.newaxis] + ) else: - self.dbl_diffs['dmda'] = None - self._dbl_diff['dmda_err'] = None + self.dbl_diffs["dmda"] = None + self._dbl_diff["dmda_err"] = None -def _filter_on_pixel_count(arr, pctl = 15): +def _filter_on_pixel_count(arr, pctl=15): """ filter oib diffs by perntile pixel count """ - arr=arr.astype(float) - arr[arr==0] = np.nan - mask = arr < np.nanpercentile(arr,pctl) + arr = arr.astype(float) + arr[arr == 0] = np.nan + mask = arr < np.nanpercentile(arr, pctl) return mask def split_by_uppercase(text): # Add space before each uppercase letter (except at the start of the string) - return re.sub(r"(? 0 if do_plot: import matplotlib.pyplot as plt from oggm import graphics + graphics.plot_modeloutput_section(flmodel) f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(12, 4)) (ds.volume_m3 * 1e-9).plot(ax=ax1) - ax1.set_ylabel('Glacier volume (km$^{3}$)') + ax1.set_ylabel("Glacier volume (km$^{3}$)") (ds.area_m2 * 1e-6).plot(ax=ax2) - ax2.set_ylabel('Glacier area (km$^{2}$)') + ax2.set_ylabel("Glacier area (km$^{2}$)") (ds.length_m * 1e3).plot(ax=ax3) - ax3.set_ylabel('Glacier length (km)') + ax3.set_ylabel("Glacier length (km)") plt.tight_layout() plt.show() diff --git a/pygem/utils/_funcs.py b/pygem/utils/_funcs.py index 2ecb9b5d..cd9977c6 100755 --- a/pygem/utils/_funcs.py +++ b/pygem/utils/_funcs.py @@ -7,19 +7,24 @@ Functions that didn't fit into other modules """ -import numpy as np + import json + +import numpy as np + # Local libraries import pygem.setup.config as config + # Read the config pygem_prms = config.read_config() # This reads the configuration file + def annualweightedmean_array(var, dates_table): """ Calculate annual mean of variable according to the timestep. - + Monthly timestep will group every 12 months, so starting month is important. - + Parameters ---------- var : np.ndarray @@ -30,39 +35,43 @@ def annualweightedmean_array(var, dates_table): ------- var_annual : np.ndarray Annual weighted mean of variable - """ - if pygem_prms['time']['timestep'] == 'monthly': - dayspermonth = dates_table['daysinmonth'].values.reshape(-1,12) + """ + if pygem_prms["time"]["timestep"] == "monthly": + dayspermonth = dates_table["daysinmonth"].values.reshape(-1, 12) # creates matrix (rows-years, columns-months) of the number of days per month daysperyear = dayspermonth.sum(axis=1) # creates an array of the days per year (includes leap years) - weights = (dayspermonth / daysperyear[:,np.newaxis]).reshape(-1) + weights = (dayspermonth / daysperyear[:, np.newaxis]).reshape(-1) # computes weights for each element, then reshapes it from matrix (rows-years, columns-months) to an array, # where each column (each monthly timestep) is the weight given to that specific month - var_annual = (var*weights[np.newaxis,:]).reshape(-1,12).sum(axis=1).reshape(-1,daysperyear.shape[0]) + var_annual = ( + (var * weights[np.newaxis, :]) + .reshape(-1, 12) + .sum(axis=1) + .reshape(-1, daysperyear.shape[0]) + ) # computes matrix (rows - bins, columns - year) of weighted average for each year - # explanation: var*weights[np.newaxis,:] multiplies each element by its corresponding weight; .reshape(-1,12) - # reshapes the matrix to only have 12 columns (1 year), so the size is (rows*cols/12, 12); .sum(axis=1) - # takes the sum of each year; .reshape(-1,daysperyear.shape[0]) reshapes the matrix back to the proper + # explanation: var*weights[np.newaxis,:] multiplies each element by its corresponding weight; .reshape(-1,12) + # reshapes the matrix to only have 12 columns (1 year), so the size is (rows*cols/12, 12); .sum(axis=1) + # takes the sum of each year; .reshape(-1,daysperyear.shape[0]) reshapes the matrix back to the proper # structure (rows - bins, columns - year) # If averaging a single year, then reshape so it returns a 1d array if var_annual.shape[1] == 1: var_annual = var_annual.reshape(var_annual.shape[0]) - elif pygem_prms['time']['timestep'] == 'daily': - print('\nError: need to code the groupbyyearsum and groupbyyearmean for daily timestep.' - 'Exiting the model run.\n') + elif pygem_prms["time"]["timestep"] == "daily": + print( + "\nError: need to code the groupbyyearsum and groupbyyearmean for daily timestep." + "Exiting the model run.\n" + ) exit() return var_annual - -import json - def append_json(file_path, new_key, new_value): """ Opens a JSON file, reads its content, adds a new key-value pair, and writes the updated data back to the file. - + :param file_path: Path to the JSON file :param new_key: The key to add :param new_value: The value to add @@ -74,7 +83,9 @@ def append_json(file_path, new_key, new_value): # Ensure the JSON data is a dictionary if not isinstance(data, dict): - raise ValueError("JSON file must contain a dictionary at the top level.") + raise ValueError( + "JSON file must contain a dictionary at the top level." + ) # Add the new key-value pair data[new_key] = new_value @@ -82,10 +93,10 @@ def append_json(file_path, new_key, new_value): # Write the updated data back to the file with open(file_path, "w") as file: json.dump(data, file) - + except FileNotFoundError: print(f"Error: The file '{file_path}' was not found.") except json.JSONDecodeError: print("Error: The file does not contain valid JSON.") except Exception as e: - print(f"An unexpected error occurred: {e}") \ No newline at end of file + print(f"An unexpected error occurred: {e}") diff --git a/pygem/utils/_funcs_selectglaciers.py b/pygem/utils/_funcs_selectglaciers.py index 94dc1acd..68a32a6a 100644 --- a/pygem/utils/_funcs_selectglaciers.py +++ b/pygem/utils/_funcs_selectglaciers.py @@ -7,14 +7,17 @@ Functions of different ways to select glaciers """ + # Built-in libraries import os -import pickle +import pickle + # External libraries import numpy as np import pandas as pd -#%% ----- Functions to select specific glacier numbers ----- + +# %% ----- Functions to select specific glacier numbers ----- def get_same_glaciers(glac_fp, ending): """ Get same glaciers for testing of priors @@ -36,7 +39,7 @@ def get_same_glaciers(glac_fp, ending): if i.endswith(ending): glac_list.append(i.split(ending)[0]) glac_list = sorted(glac_list) - + return glac_list @@ -56,55 +59,55 @@ def glac_num_fromrange(int_low, int_high): y : list list of rgi glacier numbers """ - x = (np.arange(int_low, int_high+1)).tolist() + x = (np.arange(int_low, int_high + 1)).tolist() y = [str(i).zfill(5) for i in x] return y -def glac_fromcsv(csv_fullfn, cn='RGIId'): +def glac_fromcsv(csv_fullfn, cn="RGIId"): """ Generate list of glaciers from csv file - + Parameters ---------- csv_fp, csv_fn : str csv filepath and filename - + Returns ------- y : list list of glacier numbers, e.g., ['14.00001', 15.00001'] """ df = pd.read_csv(csv_fullfn) - return [x.split('-')[1] for x in df[cn].values] + return [x.split("-")[1] for x in df[cn].values] -def glac_wo_cal(regions, prms_fp_sub=None, cal_option='MCMC'): +def glac_wo_cal(regions, prms_fp_sub=None, cal_option="MCMC"): """ Glacier list of glaciers that still need to be calibrated """ - todo_list=[] + todo_list = [] for reg in regions: prms_fns = [] - prms_fp = prms_fp_sub + str(reg).zfill(2) + '/' + prms_fp = prms_fp_sub + str(reg).zfill(2) + "/" for i in os.listdir(prms_fp): - if i.endswith('-modelprms_dict.pkl'): + if i.endswith("-modelprms_dict.pkl"): prms_fns.append(i) - + prms_fns = sorted(prms_fns) for nfn, prms_fn in enumerate(prms_fns): - glac_str = prms_fn.split('-')[0] - - if nfn%500 == 0: + glac_str = prms_fn.split("-")[0] + + if nfn % 500 == 0: print(glac_str) - + # Load model parameters - with open(prms_fp + prms_fn, 'rb') as f: + with open(prms_fp + prms_fn, "rb") as f: modelprms_dict = pickle.load(f) - + # Check if 'MCMC' is in the modelprms_dict - if not cal_option in modelprms_dict.keys(): + if cal_option not in modelprms_dict.keys(): todo_list.append(glac_str) - - return todo_list \ No newline at end of file + + return todo_list diff --git a/setup.py b/setup.py index 3f33ef3b..bb5e30c9 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ Distrubted under the MIT lisence """ + from setuptools import setup if __name__ == "__main__": - - setup() \ No newline at end of file + setup() From 06a7cf06da9e11060488bb4611dd9089b5663eca Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:33:46 +0000 Subject: [PATCH 5/6] Run linter and formatter via pre-commit --- .pre-commit-config.yaml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..143c24fe --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.6 + hooks: + - id: ruff # run the linter + args: [ --fix ] + - id: ruff-format # run the formatter \ No newline at end of file From 34e0e4b1ddeba7db8cf91cc929814bce1457e151 Mon Sep 17 00:00:00 2001 From: Davor Dundovic <33790330+ddundo@users.noreply.github.com> Date: Fri, 14 Feb 2025 17:15:51 +0000 Subject: [PATCH 6/6] Fix ruff distribution in deps --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d1fe5a37..093b1bd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ jupyter = "^1.1.1" arviz = "^0.20.0" oggm = "^1.6.2" ruamel-yaml = "^0.18.10" -ruff = ">0.9.6" +ruff = ">=0.9.6" [tool.poetry.scripts] initialize = "pygem.bin.op.initialize:main"