diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index 2a9f2bf0..30f9dfeb 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -45,6 +45,13 @@ jobs: - name: 'Reinstall PyGEM' run: pip install --break-system-packages -e . + - name: 'Run ruff linting check' + run: ruff check . + + - name: 'Run ruff formatting check' + if: ${{ !cancelled() }} + run: ruff format . --check + - name: 'Initialize PyGEM' run: initialize 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 diff --git a/docs/conf.py b/docs/conf.py index 7b3a8e23..db2f2529 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,47 +8,49 @@ import os import sys + import tomllib + sys.path.insert(0, os.path.abspath('../pygem/')) # source pyproject.toml to get release -with open("../pyproject.toml", "rb") as f: +with open('../pyproject.toml', 'rb') as f: pyproject = tomllib.load(f) project = 'PyGEM' copyright = '2023, David Rounce' author = 'David Rounce' -release = pyproject["tool"]["poetry"]["version"] +release = pyproject['tool']['poetry']['version'] # -- 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", - "attrs_inline", - "colon_fence", - "deflist", - "dollarmath", - "fieldlist", - "html_admonition", - "html_image", + 'amsmath', + 'attrs_inline', + 'colon_fence', + 'deflist', + 'dollarmath', + 'fieldlist', + 'html_admonition', + 'html_image', ] -#templates_path = ['_templates'] +# 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 @@ -57,8 +59,8 @@ 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 + 'repository_url': 'https://github.com/PyGEM-Community/PyGEM', + 'use_repository_button': True, + 'show_nav_level': 2, + 'navigation_depth': 3, +} diff --git a/docs/contributing.md b/docs/contributing.md index 054c356a..06845764 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -45,6 +45,7 @@ Installing a package in editable mode creates a symbolic link to your source cod - First, open a Draft PR. Then consider: - Have you finished making changes? - Have you added tests for all new functionalities you introduced? + - Have you run the ruff linter and formatter? See [the linting and formatting section below](ruff_target) on how to do that. - Have all tests passed in the CI? (Check the progress in the Checks tab of the PR.) If the answer to all of the above is "yes", mark the PR as "Ready for review" and request a review from an appropriate reviewer. If in doubt of which reviewer to assign, assign [drounce](https://github.com/drounce). @@ -56,3 +57,22 @@ Installing a package in editable mode creates a symbolic link to your source cod - After responding to a reviewer's comment, do not mark it as resolved. - Once all comments are addressed, request a new review from the same reviewer. The reviewer should then resolve the comments they are satisfied with. - After approving someone else's PR, do not merge it. Let the original author of the PR merge it when they are ready, as they might notice necessary last-minute changes. + +(ruff_target)= +## Code linting and formatting +PyGEM **requires** all code to be linted and formatted using [ruff](https://docs.astral.sh/ruff/formatter). Ruff enforces a consistent coding style (based on [Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and helps prevent potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file. + +⚠️ **Both linting and formatting must be completed before code is merged.** These checks are run automatically in the CI pipeline. If any issues are detected, the pipeline will fail. + +### Lint the codebase +To lint the codebase using Ruff, run the following command: +``` +ruff check /path/to/code +``` +Please address all reported errors. Many errors may be automatically and safely fixed by passing `--fix` to the above command. Other errors will need to be manually addressed. + +### Format the codebase +To automatically format the codebase using Ruff, run the following command: +``` +ruff format /path/to/code +``` 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 a84a2f4b..af236931 100644 --- a/pygem/bin/op/duplicate_gdirs.py +++ b/pygem/bin/op/duplicate_gdirs.py @@ -7,43 +7,62 @@ duplicate OGGM glacier directories """ + import argparse import os import shutil + # pygem imports from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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}/' 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) # 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 + main() diff --git a/pygem/bin/op/initialize.py b/pygem/bin/op/initialize.py index ead82679..c6f052e1 100644 --- a/pygem/bin/op/initialize.py +++ b/pygem/bin/op/initialize.py @@ -7,79 +7,90 @@ 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 pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager(overwrite=True) # read the config pygem_prms = config_manager.read_config() -def print_file_tree(start_path, indent=""): + +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) - + print(indent + '|-- ' + item) + # Recursively call this function if the item is a directory if os.path.isdir(path): - print_file_tree(path, indent + " ") - + 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(): - if key.startswith("download_warning"): + if key.startswith('download_warning'): return value return None + def save_response_content(response, destination): """Save the response content to a file.""" chunk_size = 32768 - with open(destination, "wb") as file: + with open(destination, 'wb') as file: for chunk in response.iter_content(chunk_size): 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 unique_dir = dir while os.path.exists(unique_dir): - unique_dir = f"{dir}_{counter}" + unique_dir = f'{dir}_{counter}' 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. """ # Google Drive URL template - base_url = "https://drive.google.com/uc?export=download" + base_url = 'https://drive.google.com/uc?export=download' # Make sure the output directory exists os.makedirs(output_dir, exist_ok=True) # Path to save the downloaded file - zip_path = os.path.join(output_dir, "tmp_download.zip") + zip_path = os.path.join(output_dir, 'tmp_download.zip') 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 @@ -88,7 +99,11 @@ def download_and_unzip_from_google_drive(file_id, output_dir): 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)) @@ -100,17 +115,18 @@ def download_and_unzip_from_google_drive(file_id, output_dir): except (requests.RequestException, zipfile.BadZipFile, Exception) as e: return None # Failure - + + def main(): # Define the base directory basedir = os.path.join(os.path.expanduser('~'), 'PyGEM') # Google Drive file id for sample dataset - file_id = "1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ" + 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) @@ -118,13 +134,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: - config_manager.update_config(updates={'root':f'{out}/sample_data'}) + config_manager.update_config(updates={'root': f'{out}/sample_data'}) except: pass - -if __name__ == "__main__": - main() \ No newline at end of file + + +if __name__ == '__main__': + main() diff --git a/pygem/bin/op/list_failed_simulations.py b/pygem/bin/op/list_failed_simulations.py index 707b1f12..7ed08a24 100644 --- a/pygem/bin/op/list_failed_simulations.py +++ b/pygem/bin/op/list_failed_simulations.py @@ -7,64 +7,78 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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) + "/" - + 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) - # 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] # 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)}%)') + 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,38 +86,76 @@ 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) + 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, \ + 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']: scenario = None @@ -117,16 +169,34 @@ def main(): 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_','') + 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 c72961ff..cf57db07 100644 --- a/pygem/bin/postproc/postproc_binned_monthly_mass.py +++ b/pygem/bin/postproc/postproc_binned_monthly_mass.py @@ -7,39 +7,59 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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 @@ -50,19 +70,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 @@ -86,11 +106,16 @@ 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) @@ -104,15 +129,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'] - + ### get monthly mass ### # note, binned monthly thickness and mass is currently per unit area # obtaining binned monthly mass requires knowledge of binned glacier area @@ -124,10 +149,11 @@ def get_binned_monthly(dotb_monthly, m_annual, h_annual): # 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) @@ -146,7 +172,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 @@ -162,38 +188,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)'} + '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)'} + '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)'} + '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: @@ -207,20 +246,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 @@ -244,19 +274,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() @@ -271,10 +305,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: @@ -285,9 +319,10 @@ def main(): # Parallel processing 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 +if __name__ == '__main__': + main() diff --git a/pygem/bin/postproc/postproc_compile_simulations.py b/pygem/bin/postproc/postproc_compile_simulations.py index 8b998626..cbaa4c96 100644 --- a/pygem/bin/postproc/postproc_compile_simulations.py +++ b/pygem/bin/postproc/postproc_compile_simulations.py @@ -7,67 +7,84 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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 + ( + 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/' # define base directory - base_dir = simpath + "/" + str(reg).zfill(2) + "/" + 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) @@ -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 ### @@ -99,9 +118,23 @@ def run(args): # 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] + 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] + 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) @@ -134,7 +167,9 @@ def run(args): 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 +185,42 @@ 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('__','_')) + 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('.')[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,15 +236,16 @@ 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('__','_') + 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 @@ -207,40 +258,52 @@ def run(args): 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_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,333 +312,507 @@ 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_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] 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: 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 - ) + 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['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': + # 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 - ) + 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['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': + # 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' + 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': + # 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' + 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': + # 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' + 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': + # 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' + 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, \ + 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' - #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' + 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['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': + # 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)' + 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['comment'] = ( + 'only the liquid precipitation, solid precipitation excluded' + ) ds.glac_prec_monthly.attrs['grid_mapping'] = 'crs' - - #glac_mass_monthly - elif var=='glac_mass_monthly': + + # 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 - ) + 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['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': + # 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 - ) + 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': + # 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 - ) + 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['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['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['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['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['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' 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' - + # save batch 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'] @@ -586,28 +823,41 @@ def run(args): 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 = 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") + 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 +867,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) + 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') + 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 @@ -672,11 +992,15 @@ def main(): 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: + 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}') + raise ValueError( + f'Must specify a scenario for future GCM runs\nGCMs: {gcms}\nscenarios: {scenarios}' + ) if realizations is None: realizations = [''] @@ -684,10 +1008,24 @@ def main(): 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 +1037,50 @@ 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...') 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 d9a23058..cfbab6fc 100644 --- a/pygem/bin/postproc/postproc_distribute_ice.py +++ b/pygem/bin/postproc/postproc_distribute_ice.py @@ -5,48 +5,63 @@ Distrubted under the MIT lisence """ + # Built-in libraries import argparse -import collections -import copy -import inspect +import glob 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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, or can be a directory from which to process all files)') - 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, or can be a directory to process)', + ) + 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 @@ -54,8 +69,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: @@ -63,7 +78,7 @@ 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) @@ -76,7 +91,7 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): 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['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' @@ -87,8 +102,8 @@ def pygem_to_oggm(pygem_simpath, oggm_diag=None, debug=False): 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) + f, ax = plt.subplots(1, figsize=(5, 5)) + (vol / vol[0]).plot(ax=ax) plt.show() return diag_ds @@ -96,26 +111,40 @@ 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] glac_no = pygem_fn_split[0] - glacier_rgi_table = modelsetup.selectglaciersrgitable(glac_no=[glac_no]).loc[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) ===== + # ===== 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_tidewater']: + if ( + glacier_rgi_table['TermType'] not in [1, 5] + or not pygem_prms['setup']['include_tidewater'] + ): gdir = single_flowline_glacier_directory(glacier_str) gdir.is_tidewater = False else: @@ -126,7 +155,7 @@ def run(simpath, debug=False): 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 @@ -140,13 +169,16 @@ def run(simpath, debug=False): ### # 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) @@ -159,7 +191,7 @@ def main(): if (len(args.simpath) == 1) and (os.path.isdir(args.simpath[0])): sims = glob.glob(args.simpath[0] + '/*.nc') else: - sims = args.simpath + sims = args.simpath # number of cores for parallel processing if args.ncores > 1: ncores = int(np.min([len(args.simpath), args.ncores])) @@ -173,7 +205,8 @@ def main(): with multiprocessing.Pool(ncores) as p: p.map(run_with_debug, sims) - print('Total processing time:', time.time()-time_start, 's') - -if __name__ == "__main__": + print('Total processing time:', time.time() - time_start, 's') + + +if __name__ == '__main__': main() diff --git a/pygem/bin/postproc/postproc_monthly_mass.py b/pygem/bin/postproc/postproc_monthly_mass.py index 972bb570..b7367f32 100644 --- a/pygem/bin/postproc/postproc_monthly_mass.py +++ b/pygem/bin/postproc/postproc_monthly_mass.py @@ -7,30 +7,27 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -import pygem.pygem_modelsetup as modelsetup # ----- FUNCTIONS ----- @@ -38,14 +35,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 @@ -76,10 +90,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 @@ -95,7 +113,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 @@ -110,25 +128,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'} - + '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: @@ -142,14 +168,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 @@ -169,9 +190,10 @@ 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 @@ -181,15 +203,17 @@ def run(simpath): 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 @@ -201,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 @@ -216,9 +240,10 @@ def main(): # Parallel processing 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 +if __name__ == '__main__': + main() diff --git a/pygem/bin/preproc/preproc_fetch_mbdata.py b/pygem/bin/preproc/preproc_fetch_mbdata.py index c2282c22..70d1f2b5 100644 --- a/pygem/bin/preproc/preproc_fetch_mbdata.py +++ b/pygem/bin/preproc/preproc_fetch_mbdata.py @@ -7,24 +7,22 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -import pygem.pygem_modelsetup as modelsetup def run(fp='', debug=False, overwrite=False): @@ -46,7 +44,7 @@ def run(fp='', debug=False, overwrite=False): 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() @@ -55,15 +53,18 @@ def run(fp='', debug=False, overwrite=False): 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 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}') @@ -71,21 +72,31 @@ def run(fp='', debug=False, overwrite=False): 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 - fp = f"{pygem_prms['root']}/{pygem_prms['calib']['data']['massbalance']['hugonnet2021_relpath']}/{args.fname}" + fp = f'{pygem_prms["root"]}/{pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_relpath"]}/{args.fname}' run(fp, args.debug, args.overwrite) -if __name__ == "__main__": - main() \ No newline at end of file +if __name__ == '__main__': + main() diff --git a/pygem/bin/preproc/preproc_wgms_estimate_kp.py b/pygem/bin/preproc/preproc_wgms_estimate_kp.py index 0cabd84c..de07d8d0 100644 --- a/pygem/bin/preproc/preproc_wgms_estimate_kp.py +++ b/pygem/bin/preproc/preproc_wgms_estimate_kp.py @@ -10,19 +10,22 @@ 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 + # pygem imports from pygem import class_climate from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -30,29 +33,56 @@ 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 + # 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']) - + # 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: @@ -65,7 +95,7 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w 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'): @@ -75,7 +105,7 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w 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) @@ -83,241 +113,331 @@ def subset_winter(wgms_eee_fp='', wgms_ee_fp='', wgms_e_fp='', wgms_id_fp='', w 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) - + # Drop points without data 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.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: + 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'] - + 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.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!' + 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] - + 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']) - + # 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' wgms_df[prec_cn] = 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']) rgiid = glacier_rgi_table.RGIId - + 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.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] + 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] - + # 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) - - + + 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( + ' 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/" + 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_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 = ( + 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_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 @@ -330,19 +450,26 @@ def main(): 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) - - -if __name__ == "__main__": - main() \ No newline at end of file + 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() 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 ===== + 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'] - + # ----- 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['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_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']: + 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()) + 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) + 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) + 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) + 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 + 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]) - + 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'] @@ -636,95 +876,214 @@ def run(list_packed_vars): 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]) + 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 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]) + 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'] 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()) - + # 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['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_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']: @@ -733,61 +1092,120 @@ def run(list_packed_vars): 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'] + 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 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_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_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_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_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_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_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' - + 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] @@ -803,133 +1221,236 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, prm_bndhigh = tbias_bnds[1] 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: 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)) + ( + 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 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, :] + 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']]) + 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']]) + 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 - + 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') kp_bndhigh = 1 # Check if lower bound causes good agreement modelprms['kp'] = kp_bndlow - 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), + ) while mb_mwea > mb_obs_mwea and test_count < 100: # Update temperature bias modelprms['tbias'] = modelprms['tbias'] + tbias_step @@ -937,10 +1458,24 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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: @@ -948,10 +1483,24 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, kp_bndlow = 1 # Check if upper bound causes good agreement modelprms['kp'] = kp_bndhigh - 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), + ) while mb_obs_mwea > mb_mwea and test_count < 100: # Update temperature bias modelprms['tbias'] = modelprms['tbias'] - tbias_step @@ -966,31 +1515,55 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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( + 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 - + # Lower bound modelprms['kp'] = kp_bndlow - mb_mwea_kp_low = mbEmulator.eval([modelprms['tbias'], modelprms['kp'], modelprms['ddfsnow']]) + 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']]) - + 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,60 +1573,108 @@ 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) + 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 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 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'] - - + 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 - + # 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_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 @@ -1065,8 +1686,8 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, modelprms_dict = {args.option_calibration: modelprms} 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 @@ -1075,19 +1696,28 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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_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'] @@ -1096,49 +1726,89 @@ def calc_mb_total_minelev(modelprms): 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 # 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']: + for param in ['tbias', 'kp', 'ddfsnow']: if priors[param]['type'] == 'normal': - dist = stats.norm(loc=priors[param]['mu'], scale=priors[param]['sigma']) + 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']) + 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']) + 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']) + 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,27 +1821,31 @@ 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) """ + """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['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 ----- @@ -1184,10 +1858,20 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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) + ) # --------------------------------- # ------------------ @@ -1196,9 +1880,15 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # Prior distributions (specified or informed by regions) 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']) @@ -1207,18 +1897,42 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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_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'] # 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) # ------------------ @@ -1229,10 +1943,20 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # note, temperature bias bounds will remain constant across chains if using 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, :] + 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()) # ----------------------------------- @@ -1241,116 +1965,195 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): # --- 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() + 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)) 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_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['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['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_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) - + '/') + 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) @@ -1363,52 +2166,85 @@ def must_melt(kp, tbias, ddfsnow, **kwargs): 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') - + 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' + ) + 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}') + 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}' + ) # -------------------- - - #%% ===== HUSS AND HOCK (2015) CALIBRATION ===== + # %% ===== 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']) + 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['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 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_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 @@ -1418,37 +2254,74 @@ def update_bnds(prm2opt, prm_bndlow, prm_bndhigh, prm_mid, mb_mwea_low, mb_mwea_ 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_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 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_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' - + 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] @@ -1467,23 +2340,53 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, # 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: modelprms[prm2opt] = prm_bndlow @@ -1493,33 +2396,58 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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)) + ( + 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:') - + 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) + 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) - + 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,17 +2457,28 @@ 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) + 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 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: @@ -1547,12 +2486,22 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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['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['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,20 +2511,37 @@ 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) + 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['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'] - + # ===== ROUND 3: TEMPERATURE BIAS ====== if continue_param_search: if debug: @@ -1584,52 +2550,92 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, # 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) 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) + 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)) + 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 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'] - - + # 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_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 @@ -1641,9 +2647,8 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, modelprms_dict = {args.option_calibration: modelprms} 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 @@ -1654,16 +2659,19 @@ def single_param_optimizer(modelprms_subset, mb_obs_mwea, prm2opt=None, 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['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 ---------- @@ -1673,18 +2681,22 @@ def objective(modelprms_subset): 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) # 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,37 +2710,59 @@ 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] 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) 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()) + 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) if debug: - print(' tc:', np.round(modelprms['tbias'],2), 'mb_mwea:', np.round(mb_mwea,2)) + print( + ' tc:', + np.round(modelprms['tbias'], 2), + 'mb_mwea:', + np.round(mb_mwea, 2), + ) tbias_bndhigh = modelprms['tbias'] # ===== ROUND 1: PRECIPITATION FACTOR ===== @@ -1745,12 +2779,25 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, # 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) + 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 @@ -1768,10 +2815,20 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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: @@ -1780,7 +2837,7 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, # Check if upper bound causes good agreement 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 @@ -1794,23 +2851,33 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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) - + # Set up optimization for only the precipitation factor if tbias_bndlow_opt == tbias_bndhigh_opt: modelprms_subset = [kp_init] @@ -1819,28 +2886,52 @@ 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) + 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_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 @@ -1855,12 +2946,17 @@ def run_objective(modelprms_init, mb_obs_mwea, modelprms_bnds=None, 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.') + 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.') # Global variables for Spyder development if args.ncores == 1: @@ -1868,7 +2964,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() @@ -1879,15 +2975,19 @@ def main(): glac_no = args.rgi_glac_number # format appropriately 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] + 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: 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']) + 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 @@ -1897,12 +2997,14 @@ 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) - + # Pack variables for multiprocessing list_packed_vars = [] for count, glac_no_lst in enumerate(glac_no_lsts): @@ -1911,14 +3013,15 @@ def main(): if num_cores > 1: 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 +if __name__ == '__main__': + main() diff --git a/pygem/bin/run/run_calibration_frontalablation.py b/pygem/bin/run/run_calibration_frontalablation.py index 6b3a5fb3..7862f794 100644 --- a/pygem/bin/run/run_calibration_frontalablation.py +++ b/pygem/bin/run/run_calibration_frontalablation.py @@ -7,40 +7,39 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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 ### @@ -54,45 +53,59 @@ 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 + 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 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,53 +121,74 @@ 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) + '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) + 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_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_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_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 - ) + 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 @@ -165,24 +199,29 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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_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 @@ -196,226 +235,341 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, elif args.prms_from_reg_priors: 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'] + 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'] - + 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) + 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 + 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'] 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_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] - + # 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 + # 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 - + # 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 + ) + + 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)) - 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)) - # Output of calving out_calving_forward = {} # calving flux (km3 ice/yr) 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] - + # 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) + 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 @@ -427,110 +581,151 @@ def reg_calving_flux(main_glac_rgi, calving_k, args, fa_glac_data_reg=None, 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() - + 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('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) - + # 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)) + 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, + ) 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('\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 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('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)) + 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, + ) 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 +733,104 @@ 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: + + 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) + 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)) + 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, + ) 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(' 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)) + 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, + ) 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(' 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) # 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)) + 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, + ) 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,17 +840,17 @@ 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 @@ -635,26 +858,45 @@ 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') + 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 = 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['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'] @@ -665,7 +907,10 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): 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 = 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'] @@ -673,9 +918,12 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): 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 = 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'] @@ -683,7 +931,7 @@ def merge_data(frontalablation_fp='', overwrite=False, verbose=False): 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,18 +939,29 @@ 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) @@ -716,67 +975,106 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati # Regional data 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'] = '' - + 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.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) # 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['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) + 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. - - # 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)) + 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['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) @@ -784,14 +1082,22 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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['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'] @@ -799,8 +1105,10 @@ def calib_ind_calving_k(regions, args=None, frontalablation_fp='', frontalablati 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_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,923 +1117,1693 @@ 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_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_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 + 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( + 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") - + 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() + 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: + 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)) + 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] + 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) + + 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}$'] 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.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() - - 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) - + # 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 + ) + # ----- 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] 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) - + 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 = 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'] - + 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]]) + 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 + 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: + 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_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): + 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)) - + ( + 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 + # 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") + f.write(f'{item}\n') else: output_df_missing = pd.read_csv(output_fp + output_fn_missing) # ----- 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) - + 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 = 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'] - + 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 = 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) - + # 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('mb_clim_mwea:', np.round(mb_clim_mwea, 2)) - calving_k_step_missing = (calving_k_med - calving_k_bndlow) / 20 + 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: 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) - + ( + 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)) - + + 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 + # 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)) + 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] + 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}$'] 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.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] # 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): + + 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): + + 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) @@ -1739,19 +2817,25 @@ def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablat # Update mass balance data for nglac, rgiid in enumerate(fa_glac_data.RGIId): - O1region = int(rgiid.split('-')[1].split('.')[0]) - if O1region in regions: - + 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 +2843,15 @@ 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: - + if O1region in regions: # Select subsets of data 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) @@ -1776,134 +2860,186 @@ def update_mbdata(regions=list(range(1,20)), frontalablation_fp='', frontalablat # plot calving_k by region 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] - + reg = regions_ordered[nax] + calving_k_fn = str(reg) + '-frontalablation_cal_ind.csv' - if not os.path.isfile(output_fp+calving_k_fn): + 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] + 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) - + + 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) - + + 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)) - + + # # ----- 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}$'] 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.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_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) @@ -1911,29 +3047,71 @@ def plot_calving_k_allregions(output_fp=''): 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 @@ -1944,29 +3122,57 @@ def main(): args.ncores = int(np.min([njobs, args.ncores])) # 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'] + 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/' - 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) + 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) # 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 + +if __name__ == '__main__': + main() diff --git a/pygem/bin/run/run_calibration_reg_glena.py b/pygem/bin/run/run_calibration_reg_glena.py index 18e5ca16..c674562b 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() +# 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 @@ -61,34 +64,87 @@ def getparser(): ------- Object containing arguments and their respective values. """ - parser = argparse.ArgumentParser(description="run calibration in parallel") + 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,13 +169,17 @@ 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$)') @@ -141,8 +201,7 @@ def plot_nfls_section(nfls): 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.)') @@ -155,7 +214,7 @@ 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') @@ -169,47 +228,55 @@ def surf_to_nan(surf_h, thick): # 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 """ - + """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)) - + 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 + 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 - + 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(' 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 +289,215 @@ def main(): # Calibrate each region for reg in args.rgi_region01: - 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) - - + 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_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) + 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_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) + 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_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: + + 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') - + # 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_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.' + 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') + + 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']: 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('\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) - + 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 +506,89 @@ 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)) + 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) 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 +if __name__ == '__main__': + main() diff --git a/pygem/bin/run/run_mcmc_priors.py b/pygem/bin/run/run_mcmc_priors.py index 04fc088f..6333fcb3 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -21,49 +22,89 @@ 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(): """ Use argparse to add arguments from the command line """ - parser = argparse.ArgumentParser(description="run calibration in parallel") + 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 @@ -72,15 +113,17 @@ def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): 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.reset_index(inplace=True, drop=True) priors_df.to_csv(priors_reg_outpath, index=False) @@ -88,30 +131,44 @@ def export_priors(priors_df_single, reg, regO2, priors_reg_outpath=''): 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) + # 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)) + 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}) + + 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): @@ -120,24 +177,51 @@ def plot_reg_priors(main_glac_rgi, priors_df, reg, rgi_regionsO2, fig_fp): 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) + 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 +230,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 + 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) - + 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,30 +322,47 @@ 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.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) + '/' # 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 @@ -230,79 +373,111 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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] - + # Load model parameters 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] - + 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/' 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']) - + # 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_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( + '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_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) @@ -315,45 +490,73 @@ def run(reg, option_calibration='emulator', priors_reg_outpath='', debug=False, 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_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( + '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_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() @@ -367,11 +570,18 @@ def main(): # 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) + 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 +if __name__ == '__main__': + main() diff --git a/pygem/bin/run/run_simulation.py b/pygem/bin/run/run_simulation.py index 3436797c..8f9d8fa8 100755 --- a/pygem/bin/run/run_simulation.py +++ b/pygem/bin/run/run_simulation.py @@ -16,59 +16,59 @@ # 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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['hydro_month_nh'] = 1 +cfg.PARAMS['hydro_month_sh'] = 1 cfg.PARAMS['trapezoid_lambdas'] = 1 + # ----- FUNCTIONS ----- def none_or_value(value): """Custom type function to handle 'none' or 'null' as None.""" - if value.lower() in {"none", "null"}: + if value.lower() in {'none', 'null'}: return None return value - + + def getparser(): """ Use argparse to add arguments from the command line @@ -103,79 +103,238 @@ def getparser(): ------- Object containing arguments and their respective values. """ - parser = argparse.ArgumentParser(description="Run PyGEM simulation") + 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') - parser.add_argument('-outputfn_sfix', action='store', type=str, default='', - help='append custom filename suffix to simulation output') + 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', + ) + parser.add_argument( + '-outputfn_sfix', + action='store', + type=str, + default='', + help='append custom filename suffix to simulation output', + ) # 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 @@ -209,32 +368,38 @@ def run(list_packed_vars): # ===== 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) - + # ===== LOAD CLIMATE DATA ===== # Climate class if gcm_name in ['ERA5', 'ERA-Interim', 'COAWST']: @@ -246,28 +411,38 @@ 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: @@ -277,339 +452,528 @@ def run(list_packed_vars): 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' - + # ----- 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])) + 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) + 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']: # 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) + 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': 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) + 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']) 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 # 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: 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: + 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.' + 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 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) + 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 - 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' + # 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' + ) 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_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 + + 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) - # Load OGGM glacier dynamics parameters (if necessary) 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']) - + if args.use_reg_glena: - glena_df = pd.read_csv(f"{pygem_prms['root']}/{pygem_prms['sim']['oggm_dynamics']['glena_reg_relpath']}") + 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'] + 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'] - + 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)] + 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: + 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 - + # 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 @@ -617,46 +981,67 @@ def run(list_packed_vars): 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) - + 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 + 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 - + debris.debris_binned( + gdir, fl_str='model_flowlines' + ) # add debris enhancement factors to flowlines + try: nfls = gdir.read_pickle('model_flowlines') except FileNotFoundError as 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 + tasks.init_present_time_glacier(gdir) # adds bins below nfls = gdir.read_pickle('model_flowlines') else: raise @@ -666,151 +1051,270 @@ def run(list_packed_vars): 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) - + 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 debug: 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): 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 + '/') + 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') + 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' + ) 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 + # Mass redistribution model elif args.option_dynamics == 'MassRedistributionCurves': if debug: 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) @@ -818,236 +1322,441 @@ def run(list_packed_vars): 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): 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 + '/') + 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') + 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' + ) 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) 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] + 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) - + 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_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_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_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_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_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_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_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_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_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_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: @@ -1055,256 +1764,510 @@ 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, - 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, + 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.set_fn(output_stats.get_fn().replace('SETS',f'set{n_iter}') + args.outputfn_sfix + 'all.nc') + output_stats.set_fn( + output_stats.get_fn().replace('SETS', f'set{n_iter}') + + args.outputfn_sfix + + 'all.nc' + ) output_stats.save_xr_ds() # instantiate dataset for merged simulations - output_stats = output.glacierwide_stats(glacier_rgi_table=glacier_rgi_table, - dates_table=dates_table, - nsims=nsims, - 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, + 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.set_fn(output_stats.get_fn().replace('SETS',f'{nsims}sets') + args.outputfn_sfix + 'all.nc') + output_stats.set_fn( + output_stats.get_fn().replace('SETS', f'{nsims}sets') + + args.outputfn_sfix + + 'all.nc' + ) output_stats.save_xr_ds() # ----- 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 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, - 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, + 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() # 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.set_fn(output_binned.get_fn().replace('SETS',f'set{n_iter}') + args.outputfn_sfix + 'binned.nc') + output_binned.set_fn( + output_binned.get_fn().replace( + 'SETS', f'set{n_iter}' + ) + + args.outputfn_sfix + + 'binned.nc' + ) output_binned.save_xr_ds() # 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, - 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, + 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.set_fn(output_binned.get_fn().replace('SETS',f'{nsims}sets') + args.outputfn_sfix + 'binned.nc') + output_binned.set_fn( + output_binned.get_fn().replace('SETS', f'{nsims}sets') + + args.outputfn_sfix + + 'binned.nc' + ) output_binned.save_xr_ds() except Exception as err: # LOG FAILURE - fail_fp = pygem_prms['root'] + '/Output/simulations/failed/' + reg_str + '/' + gcm_name + '/' + 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: + 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}') # Global variables for Spyder development @@ -1313,15 +2276,19 @@ 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) sys.exit(1) @@ -1330,15 +2297,19 @@ def main(): glac_no = args.rgi_glac_number # format appropriately 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] + 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: 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']) + 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 @@ -1348,7 +2319,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 @@ -1362,18 +2335,18 @@ def main(): 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))) - + 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: 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! @@ -1382,10 +2355,10 @@ def main(): for gcm_name in gcm_list: if args.scenario is None: print('Processing:', gcm_name) - elif not args.scenario is None: + 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): @@ -1393,19 +2366,20 @@ def main(): 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...') # 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 +if __name__ == '__main__': + main() diff --git a/pygem/class_climate.py b/pygem/class_climate.py index bd701d39..d0193e09 100755 --- a/pygem/class_climate.py +++ b/pygem/class_climate.py @@ -7,21 +7,27 @@ 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 + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -class GCM(): + +class GCM: """ Global climate model data properties and functions used to automatically retrieve data. - + Attributes ---------- name : str @@ -31,27 +37,27 @@ 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!' - + 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': # Standardized CESM2 Large Ensemble format (GCM/SSP) @@ -63,18 +69,48 @@ def __init__(self, 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.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': # Standardized GFDL SPEAR Large Ensemble format (GCM/SSP) @@ -86,21 +122,49 @@ def __init__(self, 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.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.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': # Variable names @@ -119,13 +183,17 @@ def __init__(self, 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'] - + 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' @@ -141,13 +209,19 @@ def __init__(self, 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.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: # Variable names @@ -158,22 +232,52 @@ def __init__(self, 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.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: # Variable names @@ -184,25 +288,38 @@ def __init__(self, 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.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.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 @@ -211,7 +328,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 @@ -227,36 +344,55 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): # Find Nearest Neighbor 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 = 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 @@ -265,19 +401,25 @@ def importGCMfxnearestneighbor_xarray(self, filename, vn, main_glac_rgi): # Otherwise, provide warning else: 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 @@ -288,7 +430,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 @@ -299,79 +441,127 @@ 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()) - + 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] + 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] + 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': 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: + 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 @@ -383,7 +573,9 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ 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 standard deviation from GCM is degrees C or K' + ) # Precipitation corrections # If the variable is precipitation elif vn in ['pr', 'tp', 'TOTPRECIP']: @@ -391,20 +583,23 @@ def importGCMvarnearestneighbor_xarray(self, filename, vn, main_glac_rgi, dates_ 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 + 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': # 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,:] + 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') return glac_variable_series, time_series diff --git a/pygem/gcmbiasadj.py b/pygem/gcmbiasadj.py index e3cc8e81..eb7e2987 100755 --- a/pygem/gcmbiasadj.py +++ b/pygem/gcmbiasadj.py @@ -7,59 +7,85 @@ 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 + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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 @@ -70,7 +96,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 @@ -79,32 +105,44 @@ 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) @@ -116,51 +154,81 @@ def temp_biasadj_HH2015(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_tab else: 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 @@ -173,31 +241,39 @@ 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: @@ -208,31 +284,50 @@ def prec_biasadj_HH2015(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_tab else: 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) + 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' - + '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 @@ -243,7 +338,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 @@ -252,24 +347,32 @@ 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: @@ -280,68 +383,112 @@ def prec_biasadj_opt1(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table else: 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))) - 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_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 + ] + # 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) + 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' - + '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%) @@ -349,7 +496,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 @@ -359,7 +506,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 ------- @@ -369,14 +516,18 @@ 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) @@ -388,65 +539,80 @@ def temp_biasadj_QDM(ref_temp, ref_elev, gcm_temp, dates_table_ref, dates_table, else: 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) + 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 + # 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%) @@ -454,7 +620,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 @@ -464,7 +630,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 ------- @@ -473,16 +639,20 @@ 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) @@ -494,50 +664,58 @@ def prec_biasadj_QDM(ref_prec, ref_elev, gcm_prec, dates_table_ref, dates_table, else: 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 @@ -546,20 +724,22 @@ 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) + 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)) - + 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) @@ -569,6 +749,6 @@ def monthly_avg_array_rolled(ref_array, dates_table_ref, dates_table, gcm_starty else: 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 03c22a26..a66a5edf 100755 --- a/pygem/glacierdynamics.py +++ b/pygem/glacierdynamics.py @@ -5,19 +5,21 @@ 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__ + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -26,23 +28,32 @@ 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 @@ -66,8 +77,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 @@ -76,21 +93,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_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. @@ -102,8 +120,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) @@ -111,21 +129,21 @@ 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.') - - - 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 @@ -158,15 +176,18 @@ 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' @@ -174,13 +195,12 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, 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] 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: @@ -201,8 +221,7 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, 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['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 @@ -224,13 +243,10 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, 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['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['description'] = 'Glacier volume below ' diag_ds['volume_bwl_m3'].attrs['unit'] = 'm 3' diag_ds['area_m2'] = ('time', np.zeros(nm) * np.nan) @@ -240,13 +256,15 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, 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['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['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' @@ -255,17 +273,16 @@ def run_until_and_store(self, y1, run_path=None, diag_path=None, # 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 - + 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 - + self.run_until(yr, run_single_year=True) # Model run if mo == 1: @@ -279,46 +296,45 @@ 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.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) + 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') @@ -326,145 +342,190 @@ def run_until_and_store(self, y1, run_path=None, diag_path=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) - + # 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(' 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) # 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 @@ -479,11 +540,13 @@ def _get_annual_frontalablation(self, heights, year=None, fls=None, fl_id=None, 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_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 @@ -499,57 +562,77 @@ 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( + '\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']: 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. @@ -567,249 +650,350 @@ 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) - + # 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() 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) + '):') - + 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_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( + '\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') - + # 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('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'])) + 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) # 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]) 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( + 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) + 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( + '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] + + 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 # 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 @@ -819,35 +1003,54 @@ def _massredistributionHuss(self, section_t0, thick_t0, width_t0, glac_bin_massb 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) + 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 @@ -868,16 +1071,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') - # 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 @@ -892,36 +1095,42 @@ 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) # Volume change [m3 ice] 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') 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) @@ -929,20 +1138,24 @@ def _massredistributioncurveHuss(self, section_t0, thick_t0, width_t0, glac_idx_ 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-----') - + # 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 2b143162..9d2c439c 100644 --- a/pygem/massbalance.py +++ b/pygem/massbalance.py @@ -5,18 +5,23 @@ 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 + from pygem.setup.config import ConfigManager +from pygem.utils._funcs import annualweightedmean_array + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -#%% + +# %% class PyGEMMassBalance(MassBalanceModel): """Mass-balance computed from the Python Glacier Evolution Model. @@ -24,15 +29,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 ---------- @@ -68,7 +83,11 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, 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: @@ -97,35 +116,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) @@ -135,10 +158,10 @@ 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) @@ -155,29 +178,51 @@ def __init__(self, gdir, modelprms, glacier_rgi_table, # Refreezing specific layers 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): + 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] @@ -188,7 +233,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 @@ -196,80 +241,114 @@ 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_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 + 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: # 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: # 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: # Elevation range based on all flowlines @@ -291,69 +370,120 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, 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 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']]) + ( + 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: # 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: # Seed randomness for repeatability, but base it on step to ensure the daily variability is not @@ -362,149 +492,297 @@ def get_annual_mb(self, heights, year=None, fls=None, fl_id=None, # 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_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']: 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 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] @@ -512,117 +790,207 @@ 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] + 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)) + 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) @@ -634,32 +1002,40 @@ 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 @@ -678,19 +1054,26 @@ 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) 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 @@ -705,154 +1088,210 @@ 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_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_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_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): @@ -886,23 +1325,37 @@ 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 + 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: surfacetype[surfacetype == 2] = 3 @@ -910,8 +1363,9 @@ def _surfacetypebinsinitial(self, elev_bins): # 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. @@ -955,29 +1409,36 @@ 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: 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. @@ -1003,12 +1464,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'] 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 1f41c27c..f628672f 100644 --- a/pygem/mcmc.py +++ b/pygem/mcmc.py @@ -7,32 +7,37 @@ 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 + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() torch.set_default_dtype(torch.float64) -plt.rcParams["font.family"] = "arial" +plt.rcParams['font.family'] = 'arial' 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. @@ -53,11 +58,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): """ @@ -71,8 +79,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): """ @@ -93,28 +107,32 @@ def log_truncated_normal(x, **kwargs): 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 + '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) @@ -127,7 +145,7 @@ def __init__(self, obs, priors, mb_func, mb_args=None, potential_fxns=None, **kw # 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()]) - + # 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()): @@ -136,9 +154,9 @@ 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: @@ -147,13 +165,17 @@ def check_priors(self): 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']) + 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): @@ -164,7 +186,9 @@ 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): @@ -181,21 +205,26 @@ 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: @@ -213,7 +242,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: @@ -233,22 +262,32 @@ 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)) @@ -257,16 +296,18 @@ def sample(self, m_0, log_posterior, n_samples=1000, h=0.1, burnin=0, thin_facto # 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 @@ -276,7 +317,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 @@ -284,32 +325,34 @@ 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: @@ -319,15 +362,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. @@ -350,11 +397,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 = 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 @@ -368,14 +415,16 @@ 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() @@ -384,53 +433,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].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].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].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=.5, borderaxespad=0, 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.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) @@ -453,14 +559,23 @@ 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.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) @@ -471,4 +586,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 ec20d847..d4da1b4b 100755 --- a/pygem/oggm_compat.py +++ b/pygem/oggm_compat.py @@ -7,20 +7,26 @@ 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 + from pygem.setup.config import ConfigManager + +# from oggm.shop import rgitopo +from pygem.shop import debris, icethickness, mbdata + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -30,10 +36,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 @@ -57,19 +69,19 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit rgi_id = 'RGI60-' + rgi_id.split('.')[0].zfill(2) + '.' + rgi_id.split('.')[1] else: 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 - + # Has internet cfg.PARAMS['has_internet'] = has_internet - + # Set border boundary cfg.PARAMS['border'] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow @@ -88,15 +100,20 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit except: reset = True - + if reset: # Start after the prepro task level 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')): @@ -105,20 +122,25 @@ def single_flowline_glacier_directory(rgi_id, reset=pygem_prms['oggm']['overwrit 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 not os.path.isfile(gdir.get_filepath('debris_hd')): + if not os.path.isfile(gdir.get_filepath('debris_ed')) or not 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! @@ -148,14 +170,14 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg 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 - + # Has internet cfg.PARAMS['has_internet'] = has_internet - + # Set border boundary cfg.PARAMS['border'] = prepro_border # Usually we recommend to set dl_verify to True - here it is quite slow @@ -165,7 +187,7 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg cfg.PARAMS['use_multiple_flowlines'] = False # temporary directory for testing (deleted on computer restart) cfg.PATHS['working_dir'] = working_dir - + # check if gdir is already processed if not reset: try: @@ -174,15 +196,20 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg except: reset = True - + if reset: # Start after the prepro task level 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!') @@ -193,7 +220,9 @@ def single_flowline_glacier_directory_with_calving(rgi_id, reset=pygem_prms['ogg # 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}) + workflow.execute_entity_task( + mbdata.mb_df_to_gdir, gdir, **{'facorrected': facorrected} + ) return gdir @@ -203,15 +232,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, @@ -219,25 +251,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): @@ -313,8 +348,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 ---------- @@ -343,8 +378,7 @@ def __init__(self, gdir, grad=3., h_perc=60, sigma_ela=100., seed=None): 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): @@ -365,7 +399,6 @@ 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 diff --git a/pygem/output.py b/pygem/output.py index 1d428720..0ebc658a 100644 --- a/pygem/output.py +++ b/pygem/output.py @@ -11,22 +11,35 @@ 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 +import warnings 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, warnings +from scipy.stats import median_abs_deviation + import pygem from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -__all__ = ["single_glacier", "glacierwide_stats", "binned_stats", "set_fn", "get_fn", - "set_modelprms", "create_xr_ds", "get_xr_ds", "save_xr_ds", "calc_stats_array"] +__all__ = [ + 'single_glacier', + 'glacierwide_stats', + 'binned_stats', + 'calc_stats_array', +] + @dataclass class single_glacier: @@ -63,16 +76,17 @@ class single_glacier: option_bias_adjustment : int Bias adjustment method applied to the climate input data """ - glacier_rgi_table : pd.DataFrame - dates_table : pd.DataFrame - 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 + 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 @@ -92,7 +106,7 @@ def __post_init__(self): self.pygem_version = pygem.__version__ 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.reg_str = str(self.glacier_rgi_table.O1Region).zfill(2) self.outdir = pygem_prms['root'] + '/Output/simulations/' self.set_fn() self._set_time_vals() @@ -151,17 +165,31 @@ def _set_time_vals(self): """Set output dataset time and year values from dates_table.""" 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)] + 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)] + 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] + 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])) + ) def _model_params_record(self): """Build model parameters attribute dictionary to be saved to output dataset.""" @@ -189,47 +217,68 @@ def _update_modelparams_record(self): def _init_dicts(self): """Initialize output coordinate and attribute dictionaries.""" 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', + }, + } + def create_xr_ds(self): """Create an xarrray dataset with placeholders for data arrays.""" # Add variables to empty dataset and merge together @@ -237,9 +286,16 @@ 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_ @@ -253,33 +309,39 @@ 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.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['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.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), + } def get_xr_ds(self): """Return the xarray dataset.""" return self.output_xr_ds - + def save_xr_ds(self): """Save the xarray dataset.""" # export netcdf - self.output_xr_ds.to_netcdf(self.outdir + self.outfn, encoding=self.encoding) + self.output_xr_ds.to_netcdf(self.outdir + self.outfn, encoding=self.encoding) # close datasets self.output_xr_ds.close() @@ -297,7 +359,7 @@ def __post_init__(self): Initializes additional attributes after the dataclass fields are set. This method: - - Calls the parent class `__post_init__` to initialize glacier values, + - Calls the parent class `__post_init__` to initialize glacier values, time stamps, and instantiate output dataset dictionarie. - Sets the output directory specific to glacier-wide statistics. - Updates the output dictionaries with required fields. @@ -317,285 +379,408 @@ def _set_outdir(self): def _update_dicts(self): """Update coordinate and attribute dictionaries specific to glacierwide_stats outputs""" - self.output_coords_dict['glac_runoff_monthly'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) + 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)]) + '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)]) + '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'} - + '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 nsims > 1, store median-absolute deviation metrics if self.nsims > 1: - self.output_coords_dict['glac_runoff_monthly_mad'] = collections.OrderedDict([('glac', self.glac_values), - ('time', self.time_values)]) + 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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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'} - + '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', + } + # optionally store extra variables 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_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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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'} + '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 nsims > 1, store median-absolute deviation metrics 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_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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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)]) + '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'} - + '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): @@ -603,7 +788,7 @@ class binned_stats(single_glacier): Single glacier binned dataset. This class extends `single_glacier` to store and manage binned glacier output data. - + Attributes ---------- nbins : int @@ -620,7 +805,7 @@ def __post_init__(self): Initializes additional attributes after the dataclass fields are set. This method: - - Calls the parent class `__post_init__` to initialize glacier values, + - Calls the parent class `__post_init__` to initialize glacier values, time stamps, and instantiate output dataset dictionaries. - Creates an array of bin indices based on the number of bins. - Sets the output directory specific to binned statistics. @@ -642,98 +827,176 @@ def _set_outdir(self): def _update_dicts(self): """Update coordinate and attribute dictionaries specific to glacierwide_stats outputs""" - self.output_coords_dict['bin_distance'] = collections.OrderedDict([('glac', self.glac_values), ('bin', self.bin_values)]) + 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)]) + '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)])) + '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)])) + '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)])) + '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)])) + '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'} - + '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', + } + # optionally store binned mass balance components 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)])) + 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)])) + '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)])) + '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'} - + 'long_name': 'binned monthly refreeze, in water equivalent', + 'units': 'm', + 'temporal_resolution': 'monthly', + 'comment': 'monthly refreeze from the PyGEM mass balance module', + } + # if nsims > 1, store median-absolute deviation metrics 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_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)])) + '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'} + '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)])) + 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'} + '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', + } def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): @@ -752,7 +1015,7 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): stats : np.array, or None Statistics related to a given variable. """ - + # dictionary of functions to call for each stat in `stats_cns` stat_funcs = { 'mean': lambda x: np.nanmean(x, axis=1), @@ -762,13 +1025,17 @@ def calc_stats_array(data, stats_cns=pygem_prms['sim']['out']['sim_stats']): 'median': lambda x: np.nanmedian(x, axis=1), '75%': lambda x: np.nanpercentile(x, 75, axis=1), '97.5%': lambda x: np.nanpercentile(x, 97.5, axis=1), - 'mad': lambda x: median_abs_deviation(x, axis=1, nan_policy='omit') + 'mad': lambda x: median_abs_deviation(x, axis=1, nan_policy='omit'), } - + # calculate statustics for each stat in `stats_cns` with warnings.catch_warnings(): - warnings.simplefilter("ignore", RuntimeWarning) # Suppress All-NaN Slice Warnings - stats_list = [stat_funcs[stat](data) for stat in stats_cns if stat in stat_funcs] - + warnings.simplefilter( + 'ignore', RuntimeWarning + ) # Suppress All-NaN Slice Warnings + stats_list = [ + stat_funcs[stat](data) for stat in stats_cns if stat in stat_funcs + ] + # stack stats_list to numpy array - return np.column_stack(stats_list) if stats_list else None \ No newline at end of file + return np.column_stack(stats_list) if stats_list else None diff --git a/pygem/pygem_modelsetup.py b/pygem/pygem_modelsetup.py index 3182669e..8de10b07 100755 --- a/pygem/pygem_modelsetup.py +++ b/pygem/pygem_modelsetup.py @@ -7,21 +7,30 @@ 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 + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -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. @@ -52,7 +61,7 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge 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('-')]) @@ -66,7 +75,9 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge 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 @@ -76,11 +87,13 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge 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 + 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 @@ -92,12 +105,14 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge if pygem_prms['time']['option_leapyear'] == 0: # First, change 'daysinmonth' number mask1 = dates_table['daysinmonth'] == 29 - dates_table.loc[mask1,'daysinmonth'] = 28 + 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), @@ -112,7 +127,10 @@ def datesmodelrun(startyear=pygem_prms['climate']['ref_startyear'], endyear=pyge 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']): + 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: @@ -135,12 +153,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] @@ -150,25 +192,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. @@ -191,7 +241,9 @@ def import_Husstable(rgi_table, filepath, filedict, drop_col_names, indexname=py 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]) @@ -225,11 +277,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 @@ -266,16 +318,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. @@ -310,11 +369,10 @@ 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'): @@ -323,75 +381,106 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' csv_regionO1 = pd.read_csv(rgi_fp + rgi_fn) except: 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 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': 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] 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) # Record the reference date 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['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[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']) + 360 + glacier_table.loc[glacier_table['CenLon'] < 0, 'CenLon_360'] + ) # Subset glaciers based on their terminus type termtype_values = [] if include_landterm: @@ -409,11 +498,13 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' 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 @@ -421,10 +512,13 @@ def selectglaciersrgitable(glac_no=None, rgi_regionsO1=None, rgi_regionsO2='all' 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 @@ -447,14 +541,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 @@ -464,8 +558,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 = [] @@ -479,19 +573,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: @@ -504,7 +598,9 @@ 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: @@ -513,10 +609,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..60570ff8 100755 --- a/pygem/scraps/dummy_task_module.py +++ b/pygem/scraps/dummy_task_module.py @@ -1,13 +1,17 @@ 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=[]) diff --git a/pygem/scraps/run.py b/pygem/scraps/run.py index cca1669a..5955c1cb 100755 --- a/pygem/scraps/run.py +++ b/pygem/scraps/run.py @@ -1,6 +1,6 @@ # 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() @@ -30,9 +30,11 @@ # 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') 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 0eeb3869..11262932 100644 --- a/pygem/setup/config.py +++ b/pygem/setup/config.py @@ -5,14 +5,18 @@ Distrubted under the MIT lisence """ + import os import shutil + import ruamel.yaml -__all__ = ["ConfigManager"] +__all__ = ['ConfigManager'] + class ConfigManager: """Manages PyGEMs configuration file, ensuring it exists, reading, updating, and validating its contents.""" + def __init__(self, config_filename='config.yaml', base_dir=None, overwrite=False): """ Initialize the ConfigManager class. @@ -25,10 +29,12 @@ def __init__(self, config_filename='config.yaml', base_dir=None, overwrite=False self.config_filename = config_filename self.base_dir = base_dir or os.path.join(os.path.expanduser('~'), 'PyGEM') self.config_path = os.path.join(self.base_dir, self.config_filename) - self.source_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.yaml") + self.source_config_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), 'config.yaml' + ) self.overwrite = overwrite self._ensure_config() - + def _ensure_config(self): """Ensure the configuration file exists, creating or overwriting it if necessary""" if not os.path.isfile(self.config_path) or self.overwrite: @@ -39,8 +45,8 @@ def _copy_source_config(self): os.makedirs(self.base_dir, exist_ok=True) shutil.copy(self.source_config_path, self.config_path) - print(f"Copied default configuration to {self.config_path}") - + print(f'Copied default configuration to {self.config_path}') + def read_config(self, validate=True): """Read the configuration file and return its contents as a dictionary while preserving formatting. Parameters: @@ -65,297 +71,300 @@ def _write_config(self, config): ryaml.preserve_quotes = True with open(self.config_path, 'w') as file: ryaml.dump(config, file) - + def update_config(self, updates): """Update multiple keys in the YAML configuration file while preserving quotes and original types. - + Parameters: updates (dict): Key-Value pairs to be updated """ config = self.read_config(validate=False) - + for key, value in updates.items(): if key not in self.EXPECTED_TYPES: - raise KeyError(f"Unrecognized configuration key: {key}") + raise KeyError(f'Unrecognized configuration key: {key}') keys = key.split('.') d = config for sub_key in keys[:-1]: d = d[sub_key] d[keys[-1]] = value - + self._validate_config(config) self._write_config(config) - + def _validate_config(self, config): """Validate the configuration dictionary against expected types and required keys. - + Parameters: config (dict): The configuration dictionary to be validated. """ for key, expected_type in self.EXPECTED_TYPES.items(): - keys = key.split(".") + keys = key.split('.') sub_data = config for sub_key in keys: if isinstance(sub_data, dict) and sub_key in sub_data: sub_data = sub_data[sub_key] else: - raise KeyError(f"Missing required key in configuration: {key}") + raise KeyError(f'Missing required key in configuration: {key}') if not isinstance(sub_data, expected_type): - raise TypeError(f"Invalid type for '{key}': expected {expected_type}, not {type(sub_data)}") + raise TypeError( + f"Invalid type for '{key}': expected {expected_type}, not {type(sub_data)}" + ) # Check elements inside lists (if defined) if key in self.LIST_ELEMENT_TYPES and isinstance(sub_data, list): elem_type = self.LIST_ELEMENT_TYPES[key] if not all(isinstance(item, elem_type) for item in sub_data): - raise TypeError(f"Invalid type for elements in '{key}': expected all elements to be {elem_type}, but got {sub_data}") - + raise TypeError( + f"Invalid type for elements in '{key}': expected all elements to be {elem_type}, but got {sub_data}" + ) # expected config types EXPECTED_TYPES = { - "root": str, - "user": dict, - "user.name": (str, type(None)), - "user.institution": (str, type(None)), - "user.email": (str, type(None)), - "setup": dict, - "setup.rgi_region01": list, - "setup.rgi_region02": str, - "setup.glac_no_skip": (list, type(None)), - "setup.glac_no": (list, type(None)), - "setup.min_glac_area_km2": int, - "setup.include_landterm": bool, - "setup.include_laketerm": bool, - "setup.include_tidewater": bool, - "setup.include_frontalablation": bool, - "oggm": dict, - "oggm.base_url": str, - "oggm.logging_level": str, - "oggm.border": int, - "oggm.oggm_gdir_relpath": str, - "oggm.overwrite_gdirs": bool, - "oggm.has_internet": bool, - "climate": dict, - "climate.ref_gcm_name": str, - "climate.ref_startyear": int, - "climate.ref_endyear": int, - "climate.ref_wateryear": str, - "climate.ref_spinupyears": int, - "climate.gcm_name": str, - "climate.scenario": (str, type(None)), - "climate.gcm_startyear": int, - "climate.gcm_endyear": int, - "climate.gcm_wateryear": str, - "climate.constantarea_years": int, - "climate.gcm_spinupyears": int, - "climate.hindcast": bool, - "climate.paths": dict, - "climate.paths.era5_relpath": str, - "climate.paths.era5_temp_fn": str, - "climate.paths.era5_tempstd_fn": str, - "climate.paths.era5_prec_fn": str, - "climate.paths.era5_elev_fn": str, - "climate.paths.era5_pressureleveltemp_fn": str, - "climate.paths.era5_lr_fn": str, - "climate.paths.cmip5_relpath": str, - "climate.paths.cmip5_fp_var_ending": str, - "climate.paths.cmip5_fp_fx_ending": str, - "climate.paths.cmip6_relpath": str, - "climate.paths.cesm2_relpath": str, - "climate.paths.cesm2_fp_var_ending": str, - "climate.paths.cesm2_fp_fx_ending": str, - "climate.paths.gfdl_relpath": str, - "climate.paths.gfdl_fp_var_ending": str, - "climate.paths.gfdl_fp_fx_ending": str, - "calib": dict, - "calib.option_calibration": str, - "calib.priors_reg_fn": str, - "calib.HH2015_params": dict, - "calib.HH2015_params.tbias_init": int, - "calib.HH2015_params.tbias_step": int, - "calib.HH2015_params.kp_init": float, - "calib.HH2015_params.kp_bndlow": float, - "calib.HH2015_params.kp_bndhigh": int, - "calib.HH2015_params.ddfsnow_init": float, - "calib.HH2015_params.ddfsnow_bndlow": float, - "calib.HH2015_params.ddfsnow_bndhigh": float, - "calib.HH2015mod_params": dict, - "calib.HH2015mod_params.tbias_init": int, - "calib.HH2015mod_params.tbias_step": float, - "calib.HH2015mod_params.kp_init": int, - "calib.HH2015mod_params.kp_bndlow": float, - "calib.HH2015mod_params.kp_bndhigh": int, - "calib.HH2015mod_params.ddfsnow_init": float, - "calib.HH2015mod_params.method_opt": str, - "calib.HH2015mod_params.params2opt": list, - "calib.HH2015mod_params.ftol_opt": float, - "calib.HH2015mod_params.eps_opt": float, - "calib.emulator_params": dict, - "calib.emulator_params.emulator_sims": int, - "calib.emulator_params.overwrite_em_sims": bool, - "calib.emulator_params.opt_hh2015_mod": bool, - "calib.emulator_params.tbias_step": float, - "calib.emulator_params.tbias_init": int, - "calib.emulator_params.kp_init": int, - "calib.emulator_params.kp_bndlow": float, - "calib.emulator_params.kp_bndhigh": int, - "calib.emulator_params.ddfsnow_init": float, - "calib.emulator_params.option_areaconstant": bool, - "calib.emulator_params.tbias_disttype": str, - "calib.emulator_params.tbias_sigma": int, - "calib.emulator_params.kp_gamma_alpha": int, - "calib.emulator_params.kp_gamma_beta": int, - "calib.emulator_params.ddfsnow_disttype": str, - "calib.emulator_params.ddfsnow_mu": float, - "calib.emulator_params.ddfsnow_sigma": float, - "calib.emulator_params.ddfsnow_bndlow": int, - "calib.emulator_params.ddfsnow_bndhigh": float, - "calib.emulator_params.method_opt": str, - "calib.emulator_params.params2opt": list, - "calib.emulator_params.ftol_opt": float, - "calib.emulator_params.eps_opt": float, - "calib.MCMC_params": dict, - "calib.MCMC_params.option_use_emulator": bool, - "calib.MCMC_params.emulator_sims": int, - "calib.MCMC_params.tbias_step": float, - "calib.MCMC_params.tbias_stepsmall": float, - "calib.MCMC_params.option_areaconstant": bool, - "calib.MCMC_params.mcmc_step": float, - "calib.MCMC_params.n_chains": int, - "calib.MCMC_params.mcmc_sample_no": int, - "calib.MCMC_params.mcmc_burn_pct": int, - "calib.MCMC_params.thin_interval": int, - "calib.MCMC_params.ddfsnow_disttype": str, - "calib.MCMC_params.ddfsnow_mu": float, - "calib.MCMC_params.ddfsnow_sigma": float, - "calib.MCMC_params.ddfsnow_bndlow": int, - "calib.MCMC_params.ddfsnow_bndhigh": float, - "calib.MCMC_params.kp_disttype": str, - "calib.MCMC_params.tbias_disttype": str, - "calib.MCMC_params.tbias_mu": int, - "calib.MCMC_params.tbias_sigma": int, - "calib.MCMC_params.tbias_bndlow": int, - "calib.MCMC_params.tbias_bndhigh": int, - "calib.MCMC_params.kp_gamma_alpha": int, - "calib.MCMC_params.kp_gamma_beta": int, - "calib.MCMC_params.kp_lognorm_mu": int, - "calib.MCMC_params.kp_lognorm_tau": int, - "calib.MCMC_params.kp_mu": int, - "calib.MCMC_params.kp_sigma": float, - "calib.MCMC_params.kp_bndlow": float, - "calib.MCMC_params.kp_bndhigh": float, - "calib.data": dict, - "calib.data.massbalance": dict, - "calib.data.massbalance.hugonnet2021_relpath": str, - "calib.data.massbalance.hugonnet2021_fn": str, - "calib.data.massbalance.hugonnet2021_facorrected_fn": str, - "calib.data.frontalablation": dict, - "calib.data.frontalablation.frontalablation_relpath": str, - "calib.data.frontalablation.frontalablation_cal_fn": str, - "calib.data.icethickness": dict, - "calib.data.icethickness.h_consensus_relpath": str, - "calib.icethickness_cal_frac_byarea": float, - "sim": dict, - "sim.option_dynamics": (str, type(None)), - "sim.option_bias_adjustment": int, - "sim.nsims": int, - "sim.out": dict, - "sim.out.sim_stats": list, - "sim.out.export_all_simiters": bool, - "sim.out.export_extra_vars": bool, - "sim.out.export_binned_data": bool, - "sim.out.export_binned_components": bool, - "sim.out.export_binned_area_threshold": int, - "sim.oggm_dynamics": dict, - "sim.oggm_dynamics.cfl_number": float, - "sim.oggm_dynamics.cfl_number_calving": float, - "sim.oggm_dynamics.glena_reg_relpath": str, - "sim.oggm_dynamics.use_reg_glena": bool, - "sim.oggm_dynamics.fs": int, - "sim.oggm_dynamics.glen_a_multiplier": int, - "sim.icethickness_advancethreshold": int, - "sim.terminus_percentage": int, - "sim.params": dict, - "sim.params.use_constant_lapserate": bool, - "sim.params.kp": int, - "sim.params.tbias": int, - "sim.params.ddfsnow": float, - "sim.params.ddfsnow_iceratio": float, - "sim.params.precgrad": float, - "sim.params.lapserate": float, - "sim.params.tsnow_threshold": int, - "sim.params.calving_k": float, - "mb": dict, - "mb.option_surfacetype_initial": int, - "mb.include_firn": bool, - "mb.include_debris": bool, - "mb.debris_relpath": str, - "mb.option_elev_ref_downscale": str, - "mb.option_temp2bins": int, - "mb.option_adjusttemp_surfelev": int, - "mb.option_prec2bins": int, - "mb.option_preclimit": int, - "mb.option_accumulation": int, - "mb.option_ablation": int, - "mb.option_ddf_firn": int, - "mb.option_refreezing": str, - "mb.Woodard_rf_opts": dict, - "mb.Woodard_rf_opts.rf_month": int, - "mb.HH2015_rf_opts": dict, - "mb.HH2015_rf_opts.rf_layers": int, - "mb.HH2015_rf_opts.rf_dz": int, - "mb.HH2015_rf_opts.rf_dsc": int, - "mb.HH2015_rf_opts.rf_meltcrit": float, - "mb.HH2015_rf_opts.pp": float, - "mb.HH2015_rf_opts.rf_dens_top": int, - "mb.HH2015_rf_opts.rf_dens_bot": int, - "mb.HH2015_rf_opts.option_rf_limit_meltsnow": int, - "rgi": dict, - "rgi.rgi_relpath": str, - "rgi.rgi_lat_colname": str, - "rgi.rgi_lon_colname": str, - "rgi.elev_colname": str, - "rgi.indexname": str, - "rgi.rgi_O1Id_colname": str, - "rgi.rgi_glacno_float_colname": str, - "rgi.rgi_cols_drop": list, - "time": dict, - "time.option_leapyear": int, - "time.startmonthday": str, - "time.endmonthday": str, - "time.wateryear_month_start": int, - "time.winter_month_start": int, - "time.summer_month_start": int, - "time.option_dates": int, - "time.timestep": str, - "constants": dict, - "constants.density_ice": int, - "constants.density_water": int, - "constants.area_ocean": float, - "constants.k_ice": float, - "constants.k_air": float, - "constants.ch_ice": int, - "constants.ch_air": int, - "constants.Lh_rf": int, - "constants.tolerance": float, - "constants.gravity": float, - "constants.pressure_std": int, - "constants.temp_std": float, - "constants.R_gas": float, - "constants.molarmass_air": float, - "debug": dict, - "debug.refreeze": bool, - "debug.mb": bool, + 'root': str, + 'user': dict, + 'user.name': (str, type(None)), + 'user.institution': (str, type(None)), + 'user.email': (str, type(None)), + 'setup': dict, + 'setup.rgi_region01': list, + 'setup.rgi_region02': str, + 'setup.glac_no_skip': (list, type(None)), + 'setup.glac_no': (list, type(None)), + 'setup.min_glac_area_km2': int, + 'setup.include_landterm': bool, + 'setup.include_laketerm': bool, + 'setup.include_tidewater': bool, + 'setup.include_frontalablation': bool, + 'oggm': dict, + 'oggm.base_url': str, + 'oggm.logging_level': str, + 'oggm.border': int, + 'oggm.oggm_gdir_relpath': str, + 'oggm.overwrite_gdirs': bool, + 'oggm.has_internet': bool, + 'climate': dict, + 'climate.ref_gcm_name': str, + 'climate.ref_startyear': int, + 'climate.ref_endyear': int, + 'climate.ref_wateryear': str, + 'climate.ref_spinupyears': int, + 'climate.gcm_name': str, + 'climate.scenario': (str, type(None)), + 'climate.gcm_startyear': int, + 'climate.gcm_endyear': int, + 'climate.gcm_wateryear': str, + 'climate.constantarea_years': int, + 'climate.gcm_spinupyears': int, + 'climate.hindcast': bool, + 'climate.paths': dict, + 'climate.paths.era5_relpath': str, + 'climate.paths.era5_temp_fn': str, + 'climate.paths.era5_tempstd_fn': str, + 'climate.paths.era5_prec_fn': str, + 'climate.paths.era5_elev_fn': str, + 'climate.paths.era5_pressureleveltemp_fn': str, + 'climate.paths.era5_lr_fn': str, + 'climate.paths.cmip5_relpath': str, + 'climate.paths.cmip5_fp_var_ending': str, + 'climate.paths.cmip5_fp_fx_ending': str, + 'climate.paths.cmip6_relpath': str, + 'climate.paths.cesm2_relpath': str, + 'climate.paths.cesm2_fp_var_ending': str, + 'climate.paths.cesm2_fp_fx_ending': str, + 'climate.paths.gfdl_relpath': str, + 'climate.paths.gfdl_fp_var_ending': str, + 'climate.paths.gfdl_fp_fx_ending': str, + 'calib': dict, + 'calib.option_calibration': str, + 'calib.priors_reg_fn': str, + 'calib.HH2015_params': dict, + 'calib.HH2015_params.tbias_init': int, + 'calib.HH2015_params.tbias_step': int, + 'calib.HH2015_params.kp_init': float, + 'calib.HH2015_params.kp_bndlow': float, + 'calib.HH2015_params.kp_bndhigh': int, + 'calib.HH2015_params.ddfsnow_init': float, + 'calib.HH2015_params.ddfsnow_bndlow': float, + 'calib.HH2015_params.ddfsnow_bndhigh': float, + 'calib.HH2015mod_params': dict, + 'calib.HH2015mod_params.tbias_init': int, + 'calib.HH2015mod_params.tbias_step': float, + 'calib.HH2015mod_params.kp_init': int, + 'calib.HH2015mod_params.kp_bndlow': float, + 'calib.HH2015mod_params.kp_bndhigh': int, + 'calib.HH2015mod_params.ddfsnow_init': float, + 'calib.HH2015mod_params.method_opt': str, + 'calib.HH2015mod_params.params2opt': list, + 'calib.HH2015mod_params.ftol_opt': float, + 'calib.HH2015mod_params.eps_opt': float, + 'calib.emulator_params': dict, + 'calib.emulator_params.emulator_sims': int, + 'calib.emulator_params.overwrite_em_sims': bool, + 'calib.emulator_params.opt_hh2015_mod': bool, + 'calib.emulator_params.tbias_step': float, + 'calib.emulator_params.tbias_init': int, + 'calib.emulator_params.kp_init': int, + 'calib.emulator_params.kp_bndlow': float, + 'calib.emulator_params.kp_bndhigh': int, + 'calib.emulator_params.ddfsnow_init': float, + 'calib.emulator_params.option_areaconstant': bool, + 'calib.emulator_params.tbias_disttype': str, + 'calib.emulator_params.tbias_sigma': int, + 'calib.emulator_params.kp_gamma_alpha': int, + 'calib.emulator_params.kp_gamma_beta': int, + 'calib.emulator_params.ddfsnow_disttype': str, + 'calib.emulator_params.ddfsnow_mu': float, + 'calib.emulator_params.ddfsnow_sigma': float, + 'calib.emulator_params.ddfsnow_bndlow': int, + 'calib.emulator_params.ddfsnow_bndhigh': float, + 'calib.emulator_params.method_opt': str, + 'calib.emulator_params.params2opt': list, + 'calib.emulator_params.ftol_opt': float, + 'calib.emulator_params.eps_opt': float, + 'calib.MCMC_params': dict, + 'calib.MCMC_params.option_use_emulator': bool, + 'calib.MCMC_params.emulator_sims': int, + 'calib.MCMC_params.tbias_step': float, + 'calib.MCMC_params.tbias_stepsmall': float, + 'calib.MCMC_params.option_areaconstant': bool, + 'calib.MCMC_params.mcmc_step': float, + 'calib.MCMC_params.n_chains': int, + 'calib.MCMC_params.mcmc_sample_no': int, + 'calib.MCMC_params.mcmc_burn_pct': int, + 'calib.MCMC_params.thin_interval': int, + 'calib.MCMC_params.ddfsnow_disttype': str, + 'calib.MCMC_params.ddfsnow_mu': float, + 'calib.MCMC_params.ddfsnow_sigma': float, + 'calib.MCMC_params.ddfsnow_bndlow': int, + 'calib.MCMC_params.ddfsnow_bndhigh': float, + 'calib.MCMC_params.kp_disttype': str, + 'calib.MCMC_params.tbias_disttype': str, + 'calib.MCMC_params.tbias_mu': int, + 'calib.MCMC_params.tbias_sigma': int, + 'calib.MCMC_params.tbias_bndlow': int, + 'calib.MCMC_params.tbias_bndhigh': int, + 'calib.MCMC_params.kp_gamma_alpha': int, + 'calib.MCMC_params.kp_gamma_beta': int, + 'calib.MCMC_params.kp_lognorm_mu': int, + 'calib.MCMC_params.kp_lognorm_tau': int, + 'calib.MCMC_params.kp_mu': int, + 'calib.MCMC_params.kp_sigma': float, + 'calib.MCMC_params.kp_bndlow': float, + 'calib.MCMC_params.kp_bndhigh': float, + 'calib.data': dict, + 'calib.data.massbalance': dict, + 'calib.data.massbalance.hugonnet2021_relpath': str, + 'calib.data.massbalance.hugonnet2021_fn': str, + 'calib.data.massbalance.hugonnet2021_facorrected_fn': str, + 'calib.data.frontalablation': dict, + 'calib.data.frontalablation.frontalablation_relpath': str, + 'calib.data.frontalablation.frontalablation_cal_fn': str, + 'calib.data.icethickness': dict, + 'calib.data.icethickness.h_consensus_relpath': str, + 'calib.icethickness_cal_frac_byarea': float, + 'sim': dict, + 'sim.option_dynamics': (str, type(None)), + 'sim.option_bias_adjustment': int, + 'sim.nsims': int, + 'sim.out': dict, + 'sim.out.sim_stats': list, + 'sim.out.export_all_simiters': bool, + 'sim.out.export_extra_vars': bool, + 'sim.out.export_binned_data': bool, + 'sim.out.export_binned_components': bool, + 'sim.out.export_binned_area_threshold': int, + 'sim.oggm_dynamics': dict, + 'sim.oggm_dynamics.cfl_number': float, + 'sim.oggm_dynamics.cfl_number_calving': float, + 'sim.oggm_dynamics.glena_reg_relpath': str, + 'sim.oggm_dynamics.use_reg_glena': bool, + 'sim.oggm_dynamics.fs': int, + 'sim.oggm_dynamics.glen_a_multiplier': int, + 'sim.icethickness_advancethreshold': int, + 'sim.terminus_percentage': int, + 'sim.params': dict, + 'sim.params.use_constant_lapserate': bool, + 'sim.params.kp': int, + 'sim.params.tbias': int, + 'sim.params.ddfsnow': float, + 'sim.params.ddfsnow_iceratio': float, + 'sim.params.precgrad': float, + 'sim.params.lapserate': float, + 'sim.params.tsnow_threshold': int, + 'sim.params.calving_k': float, + 'mb': dict, + 'mb.option_surfacetype_initial': int, + 'mb.include_firn': bool, + 'mb.include_debris': bool, + 'mb.debris_relpath': str, + 'mb.option_elev_ref_downscale': str, + 'mb.option_temp2bins': int, + 'mb.option_adjusttemp_surfelev': int, + 'mb.option_prec2bins': int, + 'mb.option_preclimit': int, + 'mb.option_accumulation': int, + 'mb.option_ablation': int, + 'mb.option_ddf_firn': int, + 'mb.option_refreezing': str, + 'mb.Woodard_rf_opts': dict, + 'mb.Woodard_rf_opts.rf_month': int, + 'mb.HH2015_rf_opts': dict, + 'mb.HH2015_rf_opts.rf_layers': int, + 'mb.HH2015_rf_opts.rf_dz': int, + 'mb.HH2015_rf_opts.rf_dsc': int, + 'mb.HH2015_rf_opts.rf_meltcrit': float, + 'mb.HH2015_rf_opts.pp': float, + 'mb.HH2015_rf_opts.rf_dens_top': int, + 'mb.HH2015_rf_opts.rf_dens_bot': int, + 'mb.HH2015_rf_opts.option_rf_limit_meltsnow': int, + 'rgi': dict, + 'rgi.rgi_relpath': str, + 'rgi.rgi_lat_colname': str, + 'rgi.rgi_lon_colname': str, + 'rgi.elev_colname': str, + 'rgi.indexname': str, + 'rgi.rgi_O1Id_colname': str, + 'rgi.rgi_glacno_float_colname': str, + 'rgi.rgi_cols_drop': list, + 'time': dict, + 'time.option_leapyear': int, + 'time.startmonthday': str, + 'time.endmonthday': str, + 'time.wateryear_month_start': int, + 'time.winter_month_start': int, + 'time.summer_month_start': int, + 'time.option_dates': int, + 'time.timestep': str, + 'constants': dict, + 'constants.density_ice': int, + 'constants.density_water': int, + 'constants.area_ocean': float, + 'constants.k_ice': float, + 'constants.k_air': float, + 'constants.ch_ice': int, + 'constants.ch_air': int, + 'constants.Lh_rf': int, + 'constants.tolerance': float, + 'constants.gravity': float, + 'constants.pressure_std': int, + 'constants.temp_std': float, + 'constants.R_gas': float, + 'constants.molarmass_air': float, + 'debug': dict, + 'debug.refreeze': bool, + 'debug.mb': bool, } # expected types of elements in lists LIST_ELEMENT_TYPES = { - "setup.rgi_region01": int, - "setup.glac_no_skip": float, - "setup.glac_no": float, - "calib.HH2015mod_params.params2opt": str, - "calib.emulator_params.params2opt": str, - "sim.out.sim_stats": str, - "rgi.rgi_cols_drop": str, + 'setup.rgi_region01': int, + 'setup.glac_no_skip': float, + 'setup.glac_no': float, + 'calib.HH2015mod_params.params2opt': str, + 'calib.emulator_params.params2opt': str, + 'sim.out.sim_stats': str, + 'rgi.rgi_cols_drop': str, } diff --git a/pygem/shop/debris.py b/pygem/shop/debris.py index 13b3f933..5754c3fe 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 + # pygem imports from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config @@ -33,73 +34,95 @@ 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: +if 'debris_hd' not 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_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): +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." + + 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] - + + 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: + else: hd_fn = None - + if hd_fn is not None: 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') - + # 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: # 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' if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) + 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: + else: ed_fn = None - + if ed_fn is not None: rasterio_to_gdir(gdir, ed_fn, 'debris_ed', resampling='bilinear') if add_to_gridded and ed_fn is not None: @@ -109,28 +132,35 @@ def debris_to_gdir(gdir, debris_dir=f"{pygem_prms['root']}/{pygem_prms['mb']['de 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' if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) + 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'): """Bin debris thickness and enhancement factors. - + Updates the 'inversion_flowlines' save file. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` @@ -140,40 +170,48 @@ 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: + 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]) @@ -187,16 +225,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 6d7660d3..9e1b9b7e 100755 --- a/pygem/shop/icethickness.py +++ b/pygem/shop/icethickness.py @@ -5,64 +5,86 @@ 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 + # pygem imports from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() -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): +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 = 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: 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') @@ -71,34 +93,44 @@ def consensus_gridded(gdir, h_consensus_fp=f"{pygem_prms['root']}/{pygem_prms['c 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) - + # glacier_mass = (data_scaled * pixel_m2).sum() * pygem_prms['constants']['density_ice'] + # print(glacier_mass) + # Write data vn = 'consensus_h' if vn in nc.variables: v = nc.variables[vn] else: - v = nc.createVariable(vn, 'f8', ('y', 'x', ), zlib=True) + 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']) def consensus_binned(gdir): """Bin consensus ice thickness ice estimates. - + Updates the 'inversion_flowlines' save file. - + Parameters ---------- gdir : :py:class:`oggm.GlacierDirectory` @@ -106,9 +138,11 @@ def consensus_binned(gdir): """ 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 @@ -120,25 +154,28 @@ def consensus_binned(gdir): 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 diff --git a/pygem/shop/mbdata.py b/pygem/shop/mbdata.py index ba87ffcd..b62dc856 100755 --- a/pygem/shop/mbdata.py +++ b/pygem/shop/mbdata.py @@ -5,65 +5,84 @@ 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 from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.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']): +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` where to write the data """ # 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 = f'{pygem_prms["root"]}/{pygem_prms["calib"]["data"]["massbalance"]["hugonnet2021_relpath"]}' + 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}" + 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 +90,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,12 +113,16 @@ 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, + '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, @@ -111,29 +134,29 @@ def mb_df_to_gdir(gdir, mb_dataset='Hugonnet2021', facorrected=pygem_prms['setup 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 +164,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 +173,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 +200,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 +221,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 +238,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 +253,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 70f1f9fc..b7be9611 100644 --- a/pygem/shop/oib.py +++ b/pygem/shop/oib.py @@ -7,22 +7,35 @@ 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 + from pygem.setup.config import ConfigManager + # instantiate ConfigManager config_manager = ConfigManager() # read the config pygem_prms = config_manager.read_config() + class oib: 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.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.rgi6id = rgi6id self.rgi7id = rgi7id @@ -33,17 +46,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 @@ -52,37 +70,43 @@ 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: + elif len(rgi7id) == 0: raise IndexError(f'No matching RGI7Id for {self.rgi6id}') - elif len(rgi7id)>1: + 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: + elif len(rgi6id) == 0: raise IndexError(f'No matching RGI6Id for {self.rgi7id}') - elif len(rgi6id)>1: + elif len(rgi6id) > 1: raise IndexError(f'More than one matching RGI6Id for {self.rgi7id}') - def _date_check(self, dt_obj): """ if survey date in given month =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.axvline(idx, c='k', ls=':') plt.legend(loc='upper right') plt.show() @@ -191,26 +236,44 @@ 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)) 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) + 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, @@ -221,7 +284,9 @@ def _dbl_diff(self, months=range(1,13)): # 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] @@ -230,12 +295,16 @@ 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: + 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: @@ -245,32 +314,40 @@ def _dbl_diff(self, months=range(1,13)): 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: # 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 -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.9.6" pytest = ">=8.3.4" pytest-cov = ">=6.0.0" nbmake = ">=1.5.5" @@ -58,7 +59,39 @@ duplicate_gdirs = "pygem.bin.op.duplicate_gdirs:main" requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" +[tool.ruff] +line-length = 88 # Default + +[tool.ruff.format] +quote-style = "single" + +[tool.ruff.lint] +select = [ + "B", # flake8-bugbear + "C", # mccabe complexity + "E", "W", # Pycodestyle + "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 +] + [tool.coverage.report] omit = ["pygem/tests/*"] show_missing = true -skip_empty = true \ No newline at end of file +skip_empty = true diff --git a/setup.py b/setup.py index 3f33ef3b..839e64b8 100644 --- a/setup.py +++ b/setup.py @@ -5,8 +5,8 @@ Distrubted under the MIT lisence """ -from setuptools import setup -if __name__ == "__main__": +from setuptools import setup - setup() \ No newline at end of file +if __name__ == '__main__': + setup()